'use client'; import { useEffect, useMemo, useState, use } from 'react'; import dynamicImport from 'next/dynamic'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; import Sidebar from '@/components/Sidebar'; import MessageContent from '@/components/MessageContent'; import { ArrowLeft, BarChart3, Calendar, Check, Copy, History, ListFilter, MessageSquare, Star, Trophy, } from 'lucide-react'; import type { EChartsOption } from 'echarts'; const ReactECharts = dynamicImport(() => import('echarts-for-react'), { ssr: false }); export const dynamic = 'force-dynamic'; type DailyHistory = { date: string; total: number }; type Detail = { ok: boolean; chatroom_id: string; date: string; stats: { chat: string; total: number; by_hour: Array<{ hour: number; count: number }>; by_type: Array<{ type: string; count: number }>; top_senders: Array<{ sender: string; count: number }>; } | null; recent: Array<{ local_id: number; sender: string; content: string; time: string; timestamp: number; type: string; }>; daily_history: DailyHistory[]; }; export default function GroupDetailPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = use(params); const chatroomId = decodeURIComponent(id); const searchParams = useSearchParams(); const requestedDate = searchParams.get('date'); const today = useMemo(() => { const d = new Date(); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; }, []); const [date, setDate] = useState(requestedDate ?? today); const [data, setData] = useState(null); const [fav, setFav] = useState(false); const [err, setErr] = useState(null); const [loading, setLoading] = useState(false); const [copied, setCopied] = useState(false); const load = async (d: string) => { setLoading(true); setErr(null); try { const r = await fetch(`/api/group/${encodeURIComponent(chatroomId)}?date=${d}&limit=500`); const j = (await r.json()) as Detail; if (!j.ok) { setErr('详情加载失败'); } else { setData(j); } } catch (e) { setErr(e instanceof Error ? e.message : '未知错误'); } finally { setLoading(false); } }; useEffect(() => { queueMicrotask(() => void load(date)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [chatroomId, date]); useEffect(() => { if (requestedDate || !data || date !== today || (data.stats?.total ?? 0) > 0) return; const latest = data.daily_history .filter((d) => d.total > 0) .sort((a, b) => b.date.localeCompare(a.date))[0]; if (latest && latest.date !== date) queueMicrotask(() => setDate(latest.date)); }, [data, date, requestedDate, today]); useEffect(() => { (async () => { try { const r = await fetch(`/api/group-tags?chatroom_id=${encodeURIComponent(chatroomId)}`); const j = await r.json(); if (j.ok && Array.isArray(j.group_ids)) setFav(false); // tags only, fav read separately if needed } catch {} })(); }, [chatroomId]); const toggleFav = async () => { const next = !fav; setFav(next); await fetch('/api/group-tags', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ chatroom_id: chatroomId, fav: next }), }); }; const copyMessages = async () => { const messages = data?.recent ?? []; if (messages.length === 0) return; const title = data?.stats?.chat ?? chatroomId; const text = [ `# ${title} ${date}`, '', ...messages.map((m) => { const time = m.time.slice(11, 16); const content = m.content.replace(/\s+/g, ' ').trim(); return `[${time}] ${m.sender}: ${content}`; }), ].join('\n'); await navigator.clipboard.writeText(text); setCopied(true); window.setTimeout(() => setCopied(false), 1500); }; const hourOption: EChartsOption | null = data?.stats ? { grid: { top: 20, right: 16, bottom: 28, left: 36 }, tooltip: { trigger: 'axis', backgroundColor: '#101812', borderColor: '#27342c', textStyle: { color: '#edf1e8' }, }, xAxis: { type: 'category', data: data.stats.by_hour.map((h) => `${h.hour}:00`), axisLine: { lineStyle: { color: '#27342c' } }, axisLabel: { color: '#737f75', fontSize: 10 }, }, yAxis: { type: 'value', splitLine: { lineStyle: { color: 'rgba(154,174,158,0.12)' } }, axisLabel: { color: '#737f75', fontSize: 10 }, }, series: [ { type: 'bar', data: data.stats.by_hour.map((h) => h.count), itemStyle: { color: '#7dd3a8' }, barWidth: 12, }, ], } : null; const dailyOption: EChartsOption | null = data?.daily_history && data.daily_history.length > 0 ? { grid: { top: 20, right: 16, bottom: 30, left: 36 }, tooltip: { trigger: 'axis', backgroundColor: '#101812', borderColor: '#27342c', textStyle: { color: '#edf1e8' }, }, xAxis: { type: 'category', data: data.daily_history.map((d) => d.date.slice(5)), axisLine: { lineStyle: { color: '#27342c' } }, axisLabel: { color: '#737f75', fontSize: 10 }, }, yAxis: { type: 'value', splitLine: { lineStyle: { color: 'rgba(154,174,158,0.12)' } }, axisLabel: { color: '#737f75', fontSize: 10 }, }, series: [ { type: 'bar', data: data.daily_history.map((d) => ({ value: d.total, itemStyle: { color: d.date === date ? '#7dd3a8' : '#28372f' }, })), barWidth: 14, }, ], } : null; return (
Group Brief
{data?.stats?.chat ?? (loading ? '加载中…' : chatroomId)}
{date} · 当日 {data?.stats?.total ?? 0} 条 · 历史 {data?.daily_history?.length ?? 0} 天
setDate(e.target.value)} className="theme-date-input bg-transparent text-[12px] outline-none" />
{err &&
{err}
} {/* 历史日活跃柱图 */} {dailyOption && (
历史每日消息量
共 {data!.daily_history.length} 天 · 点选日期查看
{ const matched = data?.daily_history.find((d) => d.date.slice(5) === e.name); if (matched) setDate(matched.date); }, }} />
)} {/* 当日 24 小时分布 */} {hourOption && (data?.stats?.total ?? 0) > 0 && (
{date} 24 小时分布
)} {/* Top 发言人 + 消息类型 */} {data?.stats && data.stats.total > 0 && (
Top 发言人
{data.stats.top_senders.slice(0, 12).map((s, i) => (
{i + 1}. {s.sender} {s.count}
))}
消息类型
{data.stats.by_type.map((t, i) => (
{t.type} {t.count}
))}
)} {/* 当日完整消息列表 */}
{date} 完整消息
{loading ? '加载中…' : `共 ${data?.recent.length ?? 0} 条`}
{!loading && data?.recent && data.recent.length === 0 ? (
当日无消息
) : (
{(data?.recent ?? []).map((m) => (
{m.sender}
{m.type} {m.time.slice(11)}
))}
)}
); }