feat: display date updates in card activity
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
|
import type { Locale as DateFnsLocale } from "date-fns";
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import { Trans } from "@lingui/react/macro";
|
import { Trans } from "@lingui/react/macro";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { format, formatDistanceToNow, isSameYear } from "date-fns";
|
||||||
import { de, enGB, es, fr, it, nl } from "date-fns/locale";
|
|
||||||
import {
|
import {
|
||||||
HiOutlineArrowLeft,
|
HiOutlineArrowLeft,
|
||||||
HiOutlineArrowRight,
|
HiOutlineArrowRight,
|
||||||
HiOutlineCheckCircle,
|
HiOutlineCheckCircle,
|
||||||
|
HiOutlineClock,
|
||||||
HiOutlinePencil,
|
HiOutlinePencil,
|
||||||
HiOutlinePlus,
|
HiOutlinePlus,
|
||||||
HiOutlineTag,
|
HiOutlineTag,
|
||||||
@@ -24,15 +25,6 @@ import Comment from "./Comment";
|
|||||||
type ActivityType =
|
type ActivityType =
|
||||||
NonNullable<GetCardByIdOutput>["activities"][number]["type"];
|
NonNullable<GetCardByIdOutput>["activities"][number]["type"];
|
||||||
|
|
||||||
const dateLocaleMap = {
|
|
||||||
en: enGB,
|
|
||||||
fr: fr,
|
|
||||||
de: de,
|
|
||||||
es: es,
|
|
||||||
it: it,
|
|
||||||
nl: nl,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const truncate = (value: string | null, maxLength = 50) => {
|
const truncate = (value: string | null, maxLength = 50) => {
|
||||||
if (!value) return value;
|
if (!value) return value;
|
||||||
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
||||||
@@ -47,6 +39,8 @@ const getActivityText = ({
|
|||||||
isSelf,
|
isSelf,
|
||||||
label,
|
label,
|
||||||
fromTitle,
|
fromTitle,
|
||||||
|
toDueDate,
|
||||||
|
dateLocale,
|
||||||
}: {
|
}: {
|
||||||
type: ActivityType;
|
type: ActivityType;
|
||||||
toTitle: string | null;
|
toTitle: string | null;
|
||||||
@@ -56,6 +50,9 @@ const getActivityText = ({
|
|||||||
isSelf: boolean;
|
isSelf: boolean;
|
||||||
label: string | null;
|
label: string | null;
|
||||||
fromTitle?: string | null;
|
fromTitle?: string | null;
|
||||||
|
fromDueDate?: Date | null;
|
||||||
|
toDueDate?: Date | null;
|
||||||
|
dateLocale: DateFnsLocale;
|
||||||
}) => {
|
}) => {
|
||||||
const ACTIVITY_TYPE_MAP = {
|
const ACTIVITY_TYPE_MAP = {
|
||||||
"card.created": t`created the card`,
|
"card.created": t`created the card`,
|
||||||
@@ -74,6 +71,9 @@ const getActivityText = ({
|
|||||||
"card.updated.checklist.item.completed": t`completed a checklist item`,
|
"card.updated.checklist.item.completed": t`completed a checklist item`,
|
||||||
"card.updated.checklist.item.uncompleted": t`marked a checklist item as incomplete`,
|
"card.updated.checklist.item.uncompleted": t`marked a checklist item as incomplete`,
|
||||||
"card.updated.checklist.item.deleted": t`deleted a checklist item`,
|
"card.updated.checklist.item.deleted": t`deleted a checklist item`,
|
||||||
|
"card.updated.dueDate.added": t`set the due date`,
|
||||||
|
"card.updated.dueDate.updated": t`updated the due date`,
|
||||||
|
"card.updated.dueDate.removed": t`removed the due date`,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (!(type in ACTIVITY_TYPE_MAP)) return null;
|
if (!(type in ACTIVITY_TYPE_MAP)) return null;
|
||||||
@@ -209,6 +209,38 @@ const getActivityText = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "card.updated.dueDate.added" && toDueDate) {
|
||||||
|
const showYear = !isSameYear(toDueDate, new Date());
|
||||||
|
const formattedDate = format(
|
||||||
|
toDueDate,
|
||||||
|
showYear ? "do MMM yyyy" : "do MMM",
|
||||||
|
{ locale: dateLocale },
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Trans>
|
||||||
|
changed the due date to <TextHighlight>{formattedDate}</TextHighlight>
|
||||||
|
</Trans>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "card.updated.dueDate.updated" && toDueDate) {
|
||||||
|
const showYear = !isSameYear(toDueDate, new Date());
|
||||||
|
const formattedDate = format(
|
||||||
|
toDueDate,
|
||||||
|
showYear ? "do MMM yyyy" : "do MMM",
|
||||||
|
{ locale: dateLocale },
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Trans>
|
||||||
|
changed the due date to <TextHighlight>{formattedDate}</TextHighlight>
|
||||||
|
</Trans>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "card.updated.dueDate.removed") {
|
||||||
|
return <Trans>removed the due date</Trans>;
|
||||||
|
}
|
||||||
|
|
||||||
return baseText;
|
return baseText;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -229,6 +261,9 @@ const ACTIVITY_ICON_MAP: Partial<Record<ActivityType, React.ReactNode | null>> =
|
|||||||
"card.updated.checklist.item.completed": <HiOutlineCheckCircle />,
|
"card.updated.checklist.item.completed": <HiOutlineCheckCircle />,
|
||||||
"card.updated.checklist.item.uncompleted": <HiOutlineCheckCircle />,
|
"card.updated.checklist.item.uncompleted": <HiOutlineCheckCircle />,
|
||||||
"card.updated.checklist.item.deleted": <HiOutlineTrash />,
|
"card.updated.checklist.item.deleted": <HiOutlineTrash />,
|
||||||
|
"card.updated.dueDate.added": <HiOutlineClock />,
|
||||||
|
"card.updated.dueDate.updated": <HiOutlineClock />,
|
||||||
|
"card.updated.dueDate.removed": <HiOutlineClock />,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const getActivityIcon = (
|
const getActivityIcon = (
|
||||||
@@ -260,9 +295,7 @@ const ActivityList = ({
|
|||||||
isViewOnly?: boolean;
|
isViewOnly?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = authClient.useSession();
|
const { data } = authClient.useSession();
|
||||||
const { locale } = useLocalisation();
|
const { dateLocale } = useLocalisation();
|
||||||
|
|
||||||
const currentDateLocale = dateLocaleMap[locale] || enGB;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4 pt-4">
|
<div className="flex flex-col space-y-4 pt-4">
|
||||||
@@ -276,6 +309,9 @@ const ActivityList = ({
|
|||||||
isSelf: activity.member?.user?.id === data?.user.id,
|
isSelf: activity.member?.user?.id === data?.user.id,
|
||||||
label: activity.label?.name ?? null,
|
label: activity.label?.name ?? null,
|
||||||
fromTitle: activity.fromTitle ?? null,
|
fromTitle: activity.fromTitle ?? null,
|
||||||
|
fromDueDate: activity.fromDueDate ?? null,
|
||||||
|
toDueDate: activity.toDueDate ?? null,
|
||||||
|
dateLocale: dateLocale,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (activity.type === "card.updated.comment.added")
|
if (activity.type === "card.updated.comment.added")
|
||||||
@@ -330,7 +366,7 @@ const ActivityList = ({
|
|||||||
<span className="space-x-1 text-light-900 dark:text-dark-800">
|
<span className="space-x-1 text-light-900 dark:text-dark-800">
|
||||||
{formatDistanceToNow(new Date(activity.createdAt), {
|
{formatDistanceToNow(new Date(activity.createdAt), {
|
||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
locale: currentDateLocale,
|
locale: dateLocale,
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default function MemberSelector({
|
|||||||
createNewItemLabel={t`Invite member`}
|
createNewItemLabel={t`Invite member`}
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-50 py-1 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-200 dark:border-dark-50 dark:text-dark-1000 dark:hover:border-dark-200 dark:hover:bg-dark-100">
|
<div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-50 py-1 pl-2 text-left text-xs text-neutral-900 hover:border-light-300 hover:bg-light-200 dark:border-dark-50 dark:text-dark-1000 dark:hover:border-dark-200 dark:hover:bg-dark-100">
|
||||||
{selectedMembers.length ? (
|
{selectedMembers.length ? (
|
||||||
<div className="isolate flex justify-end -space-x-1 overflow-hidden">
|
<div className="isolate flex justify-end -space-x-1 overflow-hidden">
|
||||||
{selectedMembers.map(({ value, imageUrl }) => (
|
{selectedMembers.map(({ value, imageUrl }) => (
|
||||||
|
|||||||
@@ -539,6 +539,8 @@ export const getWithListAndMembersByPublicId = async (
|
|||||||
toTitle: true,
|
toTitle: true,
|
||||||
fromDescription: true,
|
fromDescription: true,
|
||||||
toDescription: true,
|
toDescription: true,
|
||||||
|
fromDueDate: true,
|
||||||
|
toDueDate: true,
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
fromList: {
|
fromList: {
|
||||||
|
|||||||
Reference in New Issue
Block a user