feat: display date icon and label on cards
This commit is contained in:
@@ -110,17 +110,18 @@ const DateSelector = ({ selectedDate, onDateSelect }: DateSelectorProps) => {
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex aspect-square items-center justify-center rounded-lg focus:z-10",
|
"flex aspect-square items-center justify-center rounded-lg focus:z-10",
|
||||||
day.isSelected
|
day.isSelected
|
||||||
? "bg-dark-1000 text-dark-50 hover:bg-dark-1000 dark:bg-dark-1000 dark:text-dark-50 dark:hover:bg-dark-1000"
|
? "bg-dark-1000 hover:bg-dark-1000 dark:bg-dark-1000 dark:hover:bg-dark-1000"
|
||||||
: "bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200",
|
: "bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<time
|
<time
|
||||||
dateTime={day.date}
|
dateTime={day.date}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"mx-auto flex size-7 items-center justify-center rounded-full",
|
"mx-auto flex size-7 items-center justify-center rounded-full text-light-900 dark:text-dark-900",
|
||||||
day.isCurrentMonth
|
day.isCurrentMonth
|
||||||
? "text-light-900 dark:text-dark-900"
|
? "text-light-900 dark:text-dark-900"
|
||||||
: "text-light-700 dark:text-dark-600",
|
: "text-light-700 dark:text-dark-600",
|
||||||
|
day.isSelected && "text-light-1000 dark:text-dark-50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{day.date.split("-").pop()?.replace(/^0/, "")}
|
{day.date.split("-").pop()?.replace(/^0/, "")}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import type { Locale as DateFnsLocale } from "date-fns";
|
||||||
import { useLingui } from "@lingui/react";
|
import { useLingui } from "@lingui/react";
|
||||||
|
import { de, enGB, es, fr, it, nl, pl, ru } from "date-fns/locale";
|
||||||
|
|
||||||
import type { Locale } from "~/locales";
|
import type { Locale } from "~/locales";
|
||||||
import { useLinguiContext } from "~/providers/lingui";
|
import { useLinguiContext } from "~/providers/lingui";
|
||||||
@@ -8,6 +10,19 @@ export function useLocalisation() {
|
|||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
const { locale, setLocale, availableLocales } = useLinguiContext();
|
const { locale, setLocale, availableLocales } = useLinguiContext();
|
||||||
|
|
||||||
|
const dateLocaleMap: Partial<Record<Locale, DateFnsLocale>> = {
|
||||||
|
en: enGB,
|
||||||
|
fr,
|
||||||
|
de,
|
||||||
|
es,
|
||||||
|
it,
|
||||||
|
nl,
|
||||||
|
ru,
|
||||||
|
pl,
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentDateLocale = dateLocaleMap[locale] ?? enGB;
|
||||||
|
|
||||||
const handleSetLocale = async (newLocale: Locale) => {
|
const handleSetLocale = async (newLocale: Locale) => {
|
||||||
await activateLocale(newLocale);
|
await activateLocale(newLocale);
|
||||||
setLocale(newLocale);
|
setLocale(newLocale);
|
||||||
@@ -15,9 +30,9 @@ export function useLocalisation() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
|
dateLocale: currentDateLocale,
|
||||||
setLocale: handleSetLocale,
|
setLocale: handleSetLocale,
|
||||||
availableLocales,
|
availableLocales,
|
||||||
formatDate: i18n.date,
|
|
||||||
formatNumber: i18n.number,
|
formatNumber: i18n.number,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import { format, isBefore, isSameYear, startOfDay } from "date-fns";
|
||||||
import { HiOutlinePaperClip } from "react-icons/hi";
|
import { HiOutlinePaperClip } from "react-icons/hi";
|
||||||
import { HiBars3BottomLeft, HiChatBubbleLeft } from "react-icons/hi2";
|
import {
|
||||||
|
HiBars3BottomLeft,
|
||||||
|
HiChatBubbleLeft,
|
||||||
|
HiOutlineClock,
|
||||||
|
} from "react-icons/hi2";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import Avatar from "~/components/Avatar";
|
import Avatar from "~/components/Avatar";
|
||||||
import Badge from "~/components/Badge";
|
import Badge from "~/components/Badge";
|
||||||
import CircularProgress from "~/components/CircularProgress";
|
import CircularProgress from "~/components/CircularProgress";
|
||||||
import LabelIcon from "~/components/LabelIcon";
|
import LabelIcon from "~/components/LabelIcon";
|
||||||
|
import { useLocalisation } from "~/hooks/useLocalisation";
|
||||||
import { getAvatarUrl } from "~/utils/helpers";
|
import { getAvatarUrl } from "~/utils/helpers";
|
||||||
|
|
||||||
const Card = ({
|
const Card = ({
|
||||||
@@ -15,6 +22,7 @@ const Card = ({
|
|||||||
description,
|
description,
|
||||||
comments,
|
comments,
|
||||||
attachments,
|
attachments,
|
||||||
|
dueDate,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
labels: { name: string; colourCode: string | null }[];
|
labels: { name: string; colourCode: string | null }[];
|
||||||
@@ -36,7 +44,11 @@ const Card = ({
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
comments: { publicId: string }[];
|
comments: { publicId: string }[];
|
||||||
attachments?: { publicId: string }[];
|
attachments?: { publicId: string }[];
|
||||||
|
dueDate?: Date | null;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { dateLocale } = useLocalisation();
|
||||||
|
const showYear = dueDate ? !isSameYear(dueDate, new Date()) : false;
|
||||||
|
const isOverdue = dueDate ? isBefore(dueDate, startOfDay(new Date())) : false;
|
||||||
const completedItems = checklists.reduce((acc, checklist) => {
|
const completedItems = checklists.reduce((acc, checklist) => {
|
||||||
return acc + checklist.items.filter((item) => item.completed).length;
|
return acc + checklist.items.filter((item) => item.completed).length;
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -51,6 +63,7 @@ const Card = ({
|
|||||||
const hasDescription =
|
const hasDescription =
|
||||||
description && description.replace(/<[^>]*>/g, "").trim().length > 0;
|
description && description.replace(/<[^>]*>/g, "").trim().length > 0;
|
||||||
const hasAttachments = attachments && attachments.length > 0;
|
const hasAttachments = attachments && attachments.length > 0;
|
||||||
|
const hasDueDate = !!dueDate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col rounded-md border border-light-200 bg-light-50 px-3 py-2 text-sm text-neutral-900 dark:border-dark-200 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300">
|
<div className="flex flex-col rounded-md border border-light-200 bg-light-50 px-3 py-2 text-sm text-neutral-900 dark:border-dark-200 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300">
|
||||||
@@ -60,6 +73,7 @@ const Card = ({
|
|||||||
checklists.length > 0 ||
|
checklists.length > 0 ||
|
||||||
hasDescription ||
|
hasDescription ||
|
||||||
comments.length > 0 ||
|
comments.length > 0 ||
|
||||||
|
hasDueDate ||
|
||||||
hasAttachments ? (
|
hasAttachments ? (
|
||||||
<div className="mt-2 flex flex-col justify-end">
|
<div className="mt-2 flex flex-col justify-end">
|
||||||
<div className="space-x-0.5">
|
<div className="space-x-0.5">
|
||||||
@@ -77,6 +91,23 @@ const Card = ({
|
|||||||
<HiBars3BottomLeft className="h-4 w-4" />
|
<HiBars3BottomLeft className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{hasDueDate && dueDate && (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"flex items-center gap-1",
|
||||||
|
isOverdue
|
||||||
|
? "text-red-600 dark:text-red-400"
|
||||||
|
: "text-light-700 dark:text-dark-800",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HiOutlineClock className="h-4 w-4" />
|
||||||
|
<span className="text-[11px]">
|
||||||
|
{format(dueDate, showYear ? "do MMM yyyy" : "do MMM", {
|
||||||
|
locale: dateLocale,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{comments.length > 0 && (
|
{comments.length > 0 && (
|
||||||
<div className="flex items-center gap-1 text-light-700 dark:text-dark-800">
|
<div className="flex items-center gap-1 text-light-700 dark:text-dark-800">
|
||||||
<HiChatBubbleLeft className="h-4 w-4" />
|
<HiChatBubbleLeft className="h-4 w-4" />
|
||||||
|
|||||||
@@ -567,6 +567,7 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
}
|
}
|
||||||
comments={card.comments ?? []}
|
comments={card.comments ?? []}
|
||||||
attachments={card.attachments}
|
attachments={card.attachments}
|
||||||
|
dueDate={card.dueDate ?? null}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function DueDateSelector({
|
|||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={handleBackdropClick} />
|
<div className="fixed inset-0 z-10" onClick={handleBackdropClick} />
|
||||||
<div
|
<div
|
||||||
className="absolute -left-4 top-full z-20 mt-2 rounded-md border border-light-200 bg-light-50 shadow-lg dark:border-dark-200 dark:bg-dark-100"
|
className="absolute -left-8 top-full z-20 mt-2 rounded-md border border-light-200 bg-light-50 shadow-lg dark:border-dark-200 dark:bg-dark-100"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ export default function PublicBoardView() {
|
|||||||
description={card.description}
|
description={card.description}
|
||||||
comments={card.comments ?? []}
|
comments={card.comments ?? []}
|
||||||
attachments={card.attachments}
|
attachments={card.attachments}
|
||||||
|
dueDate={card.dueDate ?? null}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user