feat: display date icon and label on cards

This commit is contained in:
Henry
2025-12-04 14:09:04 +00:00
parent 7bc83e70a9
commit 3d5f4f63a2
6 changed files with 54 additions and 5 deletions

View File

@@ -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/, "")}

View File

@@ -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,
}; };
} }

View File

@@ -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" />

View File

@@ -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>
)} )}

View File

@@ -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();
}} }}

View File

@@ -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>
); );