admin/features/chat/groupMessagesForDisplay.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

89 lines
2.6 KiB
TypeScript

import { formatChatDayLabel, toLocalDayKey } from '@/lib/formatters'
interface DisplayableChatMessage {
id: string
senderId: string
createdAt: string
}
export interface ChatDateSeparatorItem {
kind: 'date'
key: string
label: string
}
export interface ChatMessageDisplayItem<TMessage extends DisplayableChatMessage = DisplayableChatMessage> {
kind: 'message'
key: string
message: TMessage
showSenderName: boolean
showAvatar: boolean
isClusterStart: boolean
isClusterEnd: boolean
}
export type ChatDisplayItem<TMessage extends DisplayableChatMessage = DisplayableChatMessage> =
| ChatDateSeparatorItem
| ChatMessageDisplayItem<TMessage>
const SAME_SENDER_GAP_MS = 5 * 60 * 1000
const getTime = (value: string) => {
const time = new Date(value).getTime()
return Number.isFinite(time) ? time : 0
}
/**
* Builds Telegram-like display rows: day separators + clustered messages.
* Name on first of consecutive same-sender run; avatar on last of that run.
*/
export const groupMessagesForDisplay = <TMessage extends DisplayableChatMessage>(
messages: TMessage[],
options?: { showSenderNames?: boolean; now?: Date }
): ChatDisplayItem<TMessage>[] => {
const showSenderNames = options?.showSenderNames ?? false
const now = options?.now ?? new Date()
const items: ChatDisplayItem<TMessage>[] = []
let lastDayKey: string | null = null
messages.forEach((message, index) => {
const dayKey = toLocalDayKey(message.createdAt) ?? message.id
if (dayKey !== lastDayKey) {
items.push({
kind: 'date',
key: `date-${dayKey}`,
label: formatChatDayLabel(message.createdAt, now),
})
lastDayKey = dayKey
}
const previousSameSender =
index > 0 &&
messages[index - 1].senderId === message.senderId &&
toLocalDayKey(messages[index - 1].createdAt) === toLocalDayKey(message.createdAt) &&
getTime(message.createdAt) - getTime(messages[index - 1].createdAt) <= SAME_SENDER_GAP_MS
const nextSameSender =
index < messages.length - 1 &&
messages[index + 1].senderId === message.senderId &&
toLocalDayKey(messages[index + 1].createdAt) === toLocalDayKey(message.createdAt) &&
getTime(messages[index + 1].createdAt) - getTime(message.createdAt) <= SAME_SENDER_GAP_MS
const isClusterStart = !previousSameSender
const isClusterEnd = !nextSameSender
items.push({
kind: 'message',
key: message.id,
message,
showSenderName: showSenderNames && isClusterStart,
showAvatar: showSenderNames && isClusterEnd,
isClusterStart,
isClusterEnd,
})
})
return items
}