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- <philon-@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
philon-
parent
3582499a3c
commit
ce16bf5aa4
@@ -55,7 +55,12 @@
|
|||||||
"suffix": "ms"
|
"suffix": "ms"
|
||||||
},
|
},
|
||||||
"second": { "one": "Second", "plural": "Seconds" },
|
"second": { "one": "Second", "plural": "Seconds" },
|
||||||
"day": { "one": "Day", "plural": "Days" },
|
"day": {
|
||||||
|
"one": "Day",
|
||||||
|
"plural": "Days",
|
||||||
|
"today": "Today",
|
||||||
|
"yesterday": "Yesterday"
|
||||||
|
},
|
||||||
"month": { "one": "Month", "plural": "Months" },
|
"month": { "one": "Month", "plural": "Months" },
|
||||||
"year": { "one": "Year", "plural": "Years" },
|
"year": { "one": "Year", "plural": "Years" },
|
||||||
"snr": "SNR",
|
"snr": "SNR",
|
||||||
|
|||||||
@@ -1,12 +1,80 @@
|
|||||||
import { MessageItem } from "@components/PageComponents/Messages/MessageItem.tsx";
|
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 { Message } from "@core/stores/messageStore/types.ts";
|
||||||
|
import type { TFunction } from "i18next";
|
||||||
import { InboxIcon } from "lucide-react";
|
import { InboxIcon } from "lucide-react";
|
||||||
|
import { Fragment, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface ChannelChatProps {
|
export interface ChannelChatProps {
|
||||||
messages?: Message[];
|
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 }) => (
|
||||||
|
<li aria-label={label}>
|
||||||
|
<div className="my-2 flex h-3 items-center justify-center">
|
||||||
|
<Separator className="bg-slate-100 dark:bg-slate-800" />
|
||||||
|
<div className="mx-5 whitespace-nowrap text-center text-xs text-slate-400">
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
<Separator className="bg-slate-100 dark:bg-slate-800" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
const EmptyState = () => {
|
const EmptyState = () => {
|
||||||
const { t } = useTranslation("messages");
|
const { t } = useTranslation("messages");
|
||||||
return (
|
return (
|
||||||
@@ -18,7 +86,37 @@ const EmptyState = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
|
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 (
|
return (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<EmptyState />
|
<EmptyState />
|
||||||
@@ -28,12 +126,18 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="flex flex-col-reverse flex-grow overflow-y-auto px-3 py-2">
|
<ul className="flex flex-col-reverse flex-grow overflow-y-auto px-3 py-2">
|
||||||
{messages?.map((message) => (
|
{groups.map(({ dayKey, label, items }) => (
|
||||||
|
<Fragment key={dayKey}>
|
||||||
|
{/* Render messages first, then delimiter — with flex-col-reverse this shows the delimiter above that day's messages */}
|
||||||
|
{items.map((message) => (
|
||||||
<MessageItem
|
<MessageItem
|
||||||
key={message.messageId ?? `${message.from}-${message.date}`}
|
key={message.messageId ?? `${message.from}-${message.date}`}
|
||||||
message={message}
|
message={message}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<DateDelimiter label={label} />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user