// Tab "Lịch sử" — render unified changelog từ /api/contracts/{id}/changelogs. // Group by ngày, mỗi entry: icon entityType + summary + actor + timestamp + // optional fieldChanges expand. import { useState } from 'react' import { useQuery } from '@tanstack/react-query' import { FileText, ListChecks, GitBranch, MessageSquare, Paperclip, ChevronRight, ChevronDown, User as UserIcon, } from 'lucide-react' import { api } from '@/lib/api' import { cn } from '@/lib/cn' import { ChangelogEntityTypeLabel, ChangelogActionLabel, type ContractChangelog, } from '@/types/contract-details' import { ContractPhaseLabel } from '@/types/contracts' const fmt = (s: string) => new Date(s).toLocaleString('vi-VN') const ICON_BY_TYPE: Record> = { 1: FileText, // Contract 2: ListChecks, // Detail 3: GitBranch, // Workflow 4: MessageSquare, // Comment 5: Paperclip, // Attachment } const TONE_BY_TYPE: Record = { 1: 'text-brand-600 bg-brand-50', 2: 'text-emerald-600 bg-emerald-50', 3: 'text-fuchsia-600 bg-fuchsia-50', 4: 'text-amber-600 bg-amber-50', 5: 'text-slate-600 bg-slate-100', } export function ContractChangelogsTab({ contractId }: { contractId: string }) { const q = useQuery({ queryKey: ['contract-changelogs', contractId], queryFn: async () => (await api.get(`/contracts/${contractId}/changelogs`)).data, }) if (q.isLoading) return
Đang tải lịch sử…
if (!q.data || q.data.length === 0) { return
Chưa có thao tác nào.
} return (
    {q.data.map(entry => )}
) } function ChangelogItem({ entry }: { entry: ContractChangelog }) { const [expanded, setExpanded] = useState(false) const Icon = ICON_BY_TYPE[entry.entityType] ?? FileText const tone = TONE_BY_TYPE[entry.entityType] ?? 'text-slate-600 bg-slate-100' const hasDetails = !!entry.fieldChangesJson || !!entry.contextNote return (
  • {ChangelogEntityTypeLabel[entry.entityType] ?? '—'} · {ChangelogActionLabel[entry.action] ?? '—'} {entry.phaseAtChange != null && ( {ContractPhaseLabel[entry.phaseAtChange] ?? `Phase ${entry.phaseAtChange}`} )}
    {entry.summary ?? '(Không có mô tả)'}
    {entry.userName ?? 'Hệ thống'} {fmt(entry.createdAt)} {hasDetails && ( )}
    {expanded && (
    {entry.contextNote && (
    Ghi chú:
    {entry.contextNote}
    )} {entry.fieldChangesJson && }
    )}
  • ) } function FieldChangesView({ json }: { json: string }) { type Change = { field: string; old: unknown; new: unknown } let changes: Change[] = [] try { const parsed = JSON.parse(json) if (Array.isArray(parsed)) { changes = parsed.map((c: { Field?: string; field?: string; Old?: unknown; old?: unknown; New?: unknown; new?: unknown }) => ({ field: c.Field ?? c.field ?? '', old: c.Old ?? c.old, new: c.New ?? c.new, })) } } catch { return
    (JSON parse fail)
    } if (changes.length === 0) return null return (
    Thay đổi field:
    {changes.map((c, i) => ( ))}
    Field Mới
    {c.field} {String(c.old ?? '—')} {String(c.new ?? '—')}
    ) }