import { useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AlertTriangle, ArrowLeft, Edit, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Separator, Skeleton, } from '@vector/ui'; import { getPart, deletePart } from '../lib/api/parts.js'; import { ApiRequestError } from '../lib/api/client.js'; import { queryKeys } from '../lib/queryKeys.js'; import { useAuth } from '../contexts/AuthContext.js'; import { PartStateBadge } from '../components/parts/PartStateBadge.js'; import { PartEventTimeline } from '../components/parts/PartEventTimeline.js'; import { PartFormDialog } from '../components/parts/PartFormDialog.js'; import { TagPicker } from '../components/tags/TagPicker.js'; import { ConfirmDialog } from '../components/ConfirmDialog.js'; function DetailRow({ label, value }: { label: string; value: React.ReactNode }) { return (
{label}
{value}
); } export default function PartDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { user } = useAuth(); const isAdmin = user?.role === 'ADMIN'; const [editOpen, setEditOpen] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const { data: part, isPending, isError, error } = useQuery({ queryKey: queryKeys.parts.detail(id!), queryFn: () => getPart(id!), enabled: Boolean(id), }); const deleteMutation = useMutation({ mutationFn: () => deletePart(id!), onSuccess: () => { toast.success('Part deleted'); queryClient.invalidateQueries({ queryKey: queryKeys.parts.all }); navigate('/parts', { replace: true }); }, onError: (err) => { toast.error(err instanceof ApiRequestError ? err.body.message : 'Delete failed'); }, }); if (isPending) { return (
); } if (isError || !part) { const msg = error instanceof ApiRequestError ? error.body.message : 'Part not found.'; return ( Part unavailable {msg} ); } const eolDate = part.partModel.eolDate ? new Date(part.partModel.eolDate) : null; const pastEol = eolDate ? eolDate.getTime() < Date.now() : false; return (

{part.serialNumber}

{part.manufacturer.name} · {part.partModel.mpn}

{isAdmin && ( )}
{pastEol && eolDate && (
{part.partModel.mpn} reached EOL {eolDate.toLocaleDateString()}. {' '} Plan a replacement for this part.
)}
Summary
{part.serialNumber}} /> {part.manufacturer.name} } /> } /> {part.host.assetId} / {part.host.name} ) : part.custodian ? ( Custody: {part.custodian.username} ) : part.bin?.fullPath ? ( {part.bin.fullPath} ) : ( Unassigned ) } /> ${part.price.toFixed(2)} ) : ( ) } /> {eolDate.toLocaleDateString()} ) : ( ) } />
{part.notes && ( <>

Notes

{part.notes}

)}

Tags

History Every field change is logged here.
deleteMutation.mutate()} />
); }