"use client"; import { useEffect, useMemo, useState } from "react"; import { Bug, CheckCircle2, MessageSquare, RefreshCcw } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; import type { UserFeedbackEntry, UserFeedbackPayload } from "@/types/ops"; const STATUS_OPTIONS = [ { key: "", label: "全部" }, { key: "open", label: "新建" }, { key: "triaged", label: "已确认" }, { key: "investigating", label: "处理中" }, { key: "resolved", label: "已解决" }, { key: "closed", label: "关闭" }, ] as const; const NEXT_STATUS: Record = { open: "triaged", triaged: "investigating", investigating: "resolved", resolved: "closed", closed: "open", }; function compactDate(value?: string) { if (!value) return "—"; return value.slice(0, 19).replace("T", " "); } function categoryLabel(value?: string) { const key = String(value || "").toLowerCase(); if (key === "bug") return "Bug"; if (key === "data") return "数据"; if (key === "idea") return "建议"; if (key === "payment") return "支付"; if (key === "account") return "账号"; return "其他"; } function statusLabel(value?: string) { const key = String(value || "open").toLowerCase(); if (key === "open") return "新建"; if (key === "triaged") return "已确认"; if (key === "investigating") return "处理中"; if (key === "resolved") return "已解决"; if (key === "closed") return "关闭"; return key; } function statusTone(value?: string) { const key = String(value || "open").toLowerCase(); if (key === "open") return "border-red-200 bg-red-50 text-red-700"; if (key === "triaged") return "border-amber-200 bg-amber-50 text-amber-700"; if (key === "investigating") return "border-blue-200 bg-blue-50 text-blue-700"; if (key === "resolved") return "border-emerald-200 bg-emerald-50 text-emerald-700"; return "border-slate-200 bg-slate-50 text-slate-600"; } function contextSummary(context?: Record) { if (!context) return "—"; const city = String(context.city || context.display_city || "").trim(); const slot = context.slot_index != null ? `slot ${context.slot_index}` : ""; const source = String(context.source || "").trim(); const pieces = [city, slot, source].filter(Boolean); return pieces.length ? pieces.join(" · ") : "terminal"; } function feedbackActionLabel(status?: string) { const next = NEXT_STATUS[String(status || "open").toLowerCase()] || "triaged"; return statusLabel(next); } export function FeedbackPageClient() { const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [filter, setFilter] = useState(""); const [payload, setPayload] = useState(null); const [updatingId, setUpdatingId] = useState(null); const load = async () => { setLoading(true); setError(""); try { const data = (await opsApi.feedback(120, filter)) as UserFeedbackPayload; setPayload(data); } catch (err) { setError(String(err).slice(0, 220)); } finally { setLoading(false); } }; useEffect(() => { void load(); }, [filter]); const rows = payload?.feedback || []; const counts = payload?.status_counts || {}; const openCount = Number(counts.open || 0); const activeCount = Number(counts.open || 0) + Number(counts.triaged || 0) + Number(counts.investigating || 0); const categoryCounts = useMemo(() => { const acc: Record = {}; rows.forEach((row) => { const key = String(row.category || "other"); acc[key] = (acc[key] || 0) + 1; }); return acc; }, [rows]); const advanceStatus = async (row: UserFeedbackEntry) => { const next = NEXT_STATUS[String(row.status || "open").toLowerCase()] || "triaged"; setUpdatingId(row.id); try { await opsApi.updateFeedbackStatus(row.id, next); await load(); } finally { setUpdatingId(null); } }; if (loading && !payload) { return
加载中...
; } return (

用户反馈

{error && (
加载失败:{error}
)}
新反馈
{openCount}
处理中
{activeCount}
已解决
{Number(counts.resolved || 0)}
当前列表
{rows.length}
Bug {categoryCounts.bug || 0} · 数据 {categoryCounts.data || 0} · 建议 {categoryCounts.idea || 0}
反馈收件箱
{STATUS_OPTIONS.map((item) => ( ))}
{rows.length === 0 ? (
暂无反馈。
) : (
{rows.map((row) => ( ))}
状态 类型 内容 上下文 用户 时间 操作
{statusLabel(row.status)} {categoryLabel(row.category)}
{row.message || "—"}
{row.contact &&
联系:{row.contact}
}
{contextSummary(row.context)}
{Boolean(row.context?.detail_error) && (
{String(row.context?.detail_error || "").slice(0, 120)}
)}
{row.user_email || row.user_id || "—"} {compactDate(row.created_at)}
)}
); }