/** Formatting helpers for chat timeline + customer list. */ /** ms-epoch → `YYYY-MM-DD HH:mm:ss` in local time. `--` for missing/invalid. */ export function formatTime(ts?: number | null): string { if (!ts || !Number.isFinite(ts) || ts <= 0) return '--' const d = new Date(ts) if (Number.isNaN(d.getTime())) return '--' const pad = (n: number) => String(n).padStart(2, '0') return ( `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` + `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` ) } /** ms-epoch → short `MM-DD HH:mm` for dense timelines. */ export function formatTimeShort(ts?: number | null): string { if (!ts || !Number.isFinite(ts) || ts <= 0) return '' const d = new Date(ts) if (Number.isNaN(d.getTime())) return '' const pad = (n: number) => String(n).padStart(2, '0') return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } /** * For voice messages the backend stores duration in seconds (best-effort). * Render as `N"` or `M'SS"` to mirror the messaging-app look. */ export function formatDuration(seconds?: number | null): string { if (!seconds || !Number.isFinite(seconds) || seconds <= 0) return '' const s = Math.round(seconds) if (s < 60) return `${s}"` const m = Math.floor(s / 60) const rem = s % 60 return `${m}'${String(rem).padStart(2, '0')}"` } /** Friendly label for a Channel value. */ export function channelLabel(channel?: string | null): string { switch (channel) { case 'wechat': return '微信' case 'whatsapp': return 'WhatsApp' default: return channel ?? '未知' } } // --------------------------------------------------------------------------- // Phone / voice call rendering // --------------------------------------------------------------------------- /** Call-type (PhoneCall.channel) label — 电话 vs 语音通话. */ export function callTypeLabel(channel?: string | null): string { switch (channel) { case 'phone_call': return '电话' case 'voice_call': return '语音通话' default: return channel ?? '未知' } } /** Call direction. ``incoming_call``: 1 = 呼入 (incoming), 0 = 呼出 (outgoing). */ export function callDirectionLabel(incoming?: number | null): string { if (incoming == null) return '--' return incoming === 1 ? '呼入' : '呼出' } /** * Call status. The upstream schema uses 1 = 已接通 (answered/connected) and * 0 = 未接通 (missed/failed); other codes fall back to the raw value so the UI * never hides information. */ export function callStatusLabel(status?: number | null): string { if (status == null) return '--' switch (status) { case 1: return '已接通' case 0: return '未接通' default: return `状态 ${status}` } } /** Render call_duration (seconds) as `N"s` / `M'SS"`; ``--`` when missing. */ export function formatCallDuration(seconds?: number | null): string { if (!seconds || !Number.isFinite(seconds) || seconds <= 0) return '--' return formatDuration(seconds) } // --------------------------------------------------------------------------- // SMS rendering // --------------------------------------------------------------------------- /** * SMS direction. Conventions: ``direction`` 1 = 发送 (outbound, staff → * customer), 0 = 接收 (inbound). Other values fall back to the raw code. */ export function smsDirectionLabel(direction?: number | null): string { if (direction == null) return '--' switch (direction) { case 1: return '发送' case 0: return '接收' default: return `方向 ${direction}` } }