import { useState } from 'react'; import { Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { ArrowRight, ArrowRightLeft, LogIn, LogOut, Pencil, type LucideIcon, } from 'lucide-react'; import { Button, Skeleton } from '@vector/ui'; import { listHostTimeline } from '../../lib/api/hosts.js'; import { queryKeys } from '../../lib/queryKeys.js'; import type { HostTimelineEntry } from '../../lib/api/types.js'; const ENTRY_ICON: Record = { HOST_EVENT: Pencil, REPAIR: ArrowRightLeft, PART_ARRIVED: LogIn, PART_DEPARTED: LogOut, }; const HOST_EVENT_TITLE: Record = { CREATED: 'Created', STATE_CHANGED: 'State changed', STACK_CHANGED: 'Stack changed', FIELD_UPDATED: 'Field updated', }; function formatWhen(iso: string) { return new Date(iso).toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } function EntryRow({ entry }: { entry: HostTimelineEntry }) { switch (entry.type) { case 'HOST_EVENT': { const { hostEvent } = entry; return ( <>
{HOST_EVENT_TITLE[hostEvent.type] ?? hostEvent.type} {hostEvent.field && ( · {hostEvent.field} )} {(hostEvent.oldValue || hostEvent.newValue) && ( {hostEvent.oldValue ?? '—'} {hostEvent.newValue ?? '—'} )}
{formatWhen(entry.at)} {hostEvent.user?.username ? ` · ${hostEvent.user.username}` : ''}
); } case 'REPAIR': { const { repair } = entry; return ( <>
Repair {repair.brokenPart.serialNumber} → BROKEN · {repair.replacement.serialNumber} → DEPLOYED
{formatWhen(entry.at)} {repair.performedBy?.username ? ` · ${repair.performedBy.username}` : ''}
); } case 'PART_ARRIVED': case 'PART_DEPARTED': { const { part } = entry; const label = entry.type === 'PART_ARRIVED' ? 'Part deployed' : 'Part departed'; return ( <>
{label} {part.serialNumber} · {part.mpn}
{formatWhen(entry.at)}
); } } } function entryKey(entry: HostTimelineEntry): string { switch (entry.type) { case 'HOST_EVENT': return `he-${entry.hostEvent.id}`; case 'REPAIR': return `r-${entry.repair.id}`; case 'PART_ARRIVED': return `pa-${entry.partEventId}`; case 'PART_DEPARTED': return `pd-${entry.partEventId}`; } } export function HostTimeline({ hostId }: { hostId: string }) { const [page, setPage] = useState(1); const pageSize = 20; const query = useQuery({ queryKey: queryKeys.hosts.timeline(hostId, { page, pageSize }), queryFn: () => listHostTimeline(hostId, { page, pageSize }), placeholderData: (prev) => prev, }); if (query.isPending) { return (
{Array.from({ length: 4 }).map((_, i) => ( ))}
); } if (query.isError) { return

Could not load history.

; } const entries = query.data?.data ?? []; const total = query.data?.total ?? 0; const pageCount = Math.max(1, Math.ceil(total / pageSize)); if (entries.length === 0) { return

No activity yet.

; } return (
    {entries.map((entry) => { const Icon = ENTRY_ICON[entry.type]; return (
  1. ); })}
{pageCount > 1 && (
Page {page} of {pageCount}
)}
); }