From ce16bf5aa4d12465d1b6b515015bdc7c7f83c630 Mon Sep 17 00:00:00 2001 From: Jeremy Gallant <8975765+philon-@users.noreply.github.com> Date: Tue, 2 Sep 2025 05:00:05 +0200 Subject: [PATCH] Chat message date display (#824) * Date handling * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: philon- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../web/public/i18n/locales/en/common.json | 7 +- .../PageComponents/Messages/ChannelChat.tsx | 118 ++++++++++++++++-- 2 files changed, 117 insertions(+), 8 deletions(-) diff --git a/packages/web/public/i18n/locales/en/common.json b/packages/web/public/i18n/locales/en/common.json index 84d6a49f..33aafd28 100644 --- a/packages/web/public/i18n/locales/en/common.json +++ b/packages/web/public/i18n/locales/en/common.json @@ -55,7 +55,12 @@ "suffix": "ms" }, "second": { "one": "Second", "plural": "Seconds" }, - "day": { "one": "Day", "plural": "Days" }, + "day": { + "one": "Day", + "plural": "Days", + "today": "Today", + "yesterday": "Yesterday" + }, "month": { "one": "Month", "plural": "Months" }, "year": { "one": "Year", "plural": "Years" }, "snr": "SNR", diff --git a/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx b/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx index dcfc3e5a..3910828b 100644 --- a/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx +++ b/packages/web/src/components/PageComponents/Messages/ChannelChat.tsx @@ -1,12 +1,80 @@ import { MessageItem } from "@components/PageComponents/Messages/MessageItem.tsx"; +import { Separator } from "@components/UI/Seperator"; import type { Message } from "@core/stores/messageStore/types.ts"; +import type { TFunction } from "i18next"; import { InboxIcon } from "lucide-react"; +import { Fragment, useMemo } from "react"; import { useTranslation } from "react-i18next"; export interface ChannelChatProps { messages?: Message[]; } +function toTs(d: Message["date"]): number { + return typeof d === "number" ? d : Date.parse(String(d)); +} + +function startOfLocalDay(ts: number): number { + const d = new Date(ts); + d.setHours(0, 0, 0, 0); + return d.getTime(); +} + +function formatDateLabelFromDayKey( + dayKey: number, + t: TFunction<"common", undefined>, + fmt: Intl.DateTimeFormat, +): string { + const todayKey = startOfLocalDay(Date.now()); + const yestKey = todayKey - 24 * 60 * 60 * 1000; + + if (dayKey === todayKey) { + return t("unit.day.today"); // "Today" from common.json + } + if (dayKey === yestKey) { + return t("unit.day.yesterday"); // "Yesterday" from common.json + } + return fmt.format(new Date(dayKey)); +} + +type DayGroup = { dayKey: number; label: string; items: Message[] }; + +function groupMessagesByDay( + messages: Message[], + t: TFunction<"common", undefined>, + fmt: Intl.DateTimeFormat, +): DayGroup[] { + const out: DayGroup[] = []; + + for (const msg of messages) { + const ts = toTs(msg.date); + const dayKey = startOfLocalDay(ts); + const last = out[out.length - 1]; + if (last && last.dayKey === dayKey) { + last.items.push(msg); + } else { + out.push({ + dayKey, + label: formatDateLabelFromDayKey(dayKey, t, fmt), + items: [msg], + }); + } + } + return out; +} + +const DateDelimiter = ({ label }: { label: string }) => ( +
  • +
    + +
    + {label} +
    + +
    +
  • +); + const EmptyState = () => { const { t } = useTranslation("messages"); return ( @@ -18,7 +86,37 @@ const EmptyState = () => { }; export const ChannelChat = ({ messages = [] }: ChannelChatProps) => { - if (!messages?.length) { + const { i18n, t } = useTranslation(); + + const locale = useMemo( + () => + i18n.language || + (typeof navigator !== "undefined" ? navigator.language : "en-US"), + [i18n.language], + ); + + const dayLabelFmt = useMemo( + () => + new Intl.DateTimeFormat(locale, { + year: "numeric", + month: "long", + day: "numeric", + }), + [locale], + ); + + // Sort messages by date in case they are stored out of order + const sorted = useMemo( + () => [...messages].sort((a, b) => toTs(b.date) - toTs(a.date)), + [messages], + ); + + const groups = useMemo( + () => groupMessagesByDay(sorted, t, dayLabelFmt), + [sorted, dayLabelFmt, t], + ); + + if (!messages.length) { return (
    @@ -27,12 +125,18 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => { } return ( -
      - {messages?.map((message) => ( - +
        + {groups.map(({ dayKey, label, items }) => ( + + {/* Render messages first, then delimiter — with flex-col-reverse this shows the delimiter above that day's messages */} + {items.map((message) => ( + + ))} + + ))}
      );