import { useRef, useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { queryOptions } from '@tanstack/react-query' import { api } from '@/lib/api-client' import { useAuthStore } from '@/stores/auth.store' import { Button } from '@/components/ui/button' import { FileText, Plus, Trash2 } from 'lucide-react' import { toast } from 'sonner' function AuthImage({ path, alt, className, onClick }: { path: string; alt: string; className?: string; onClick?: () => void }) { const token = useAuthStore((s) => s.token) const [src, setSrc] = useState(null) useEffect(() => { let cancelled = false let blobUrl: string | null = null async function load() { try { const res = await fetch(`/v1/files/serve/${path}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }) if (!res.ok || cancelled) return const blob = await res.blob() if (!cancelled) { blobUrl = URL.createObjectURL(blob) setSrc(blobUrl) } } catch { /* ignore */ } } load() return () => { cancelled = true if (blobUrl) URL.revokeObjectURL(blobUrl) } }, [path, token]) if (!src) return
return {alt} } async function openSignedFile(fileId: string) { try { const res = await api.get<{ url: string }>(`/v1/files/signed-url/${fileId}`) window.open(res.url, '_blank') } catch { toast.error('Failed to open file') } } interface FileRecord { id: string path: string category: string filename: string } function ticketFilesOptions(ticketId: string) { return queryOptions({ queryKey: ['files', 'repair_ticket', ticketId], queryFn: () => api.get<{ data: FileRecord[] }>('/v1/files', { entityType: 'repair_ticket', entityId: ticketId }), enabled: !!ticketId, }) } const PHOTO_CATEGORIES = [ { key: 'intake', label: 'Intake Photos', description: 'Condition at intake' }, { key: 'in_progress', label: 'Work in Progress', description: 'During repair' }, { key: 'completed', label: 'Completed', description: 'Final result' }, { key: 'document', label: 'Documents', description: 'Signed approvals, quotes, receipts' }, ] as const interface TicketPhotosProps { ticketId: string currentStatus: string } export function TicketPhotos({ ticketId, currentStatus }: TicketPhotosProps) { const queryClient = useQueryClient() const token = useAuthStore((s) => s.token) const { data: filesData } = useQuery(ticketFilesOptions(ticketId)) const files = filesData?.data ?? [] // Determine which category to highlight based on current status const activeCategory = ['in_progress', 'pending_parts'].includes(currentStatus) ? 'in_progress' : ['ready', 'picked_up', 'delivered'].includes(currentStatus) ? 'completed' : 'intake' return (
{PHOTO_CATEGORIES.map((cat) => ( f.category === cat.key)} isActive={activeCategory === cat.key} token={token} onUpdate={() => queryClient.invalidateQueries({ queryKey: ['files', 'repair_ticket', ticketId] })} /> ))}
) } function PhotoSection({ ticketId, category, label, description, photos, isActive, token, onUpdate, }: { ticketId: string category: string label: string description: string photos: FileRecord[] isActive: boolean token: string | null onUpdate: () => void }) { const fileInputRef = useRef(null) const deleteMutation = useMutation({ mutationFn: (fileId: string) => api.del(`/v1/files/${fileId}`), onSuccess: () => { onUpdate() toast.success('Photo removed') }, onError: (err) => toast.error(err.message), }) async function handleUpload(e: React.ChangeEvent) { const files = Array.from(e.target.files ?? []) for (const file of files) { const formData = new FormData() formData.append('file', file) formData.append('entityType', 'repair_ticket') formData.append('entityId', ticketId) formData.append('category', category) try { const res = await fetch('/v1/files', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData, }) if (!res.ok) { const err = await res.json() throw new Error(err.error?.message ?? 'Upload failed') } } catch (err) { toast.error(err instanceof Error ? err.message : 'Upload failed') } } onUpdate() toast.success(`${files.length} photo(s) uploaded`) e.target.value = '' } return (

{label}

{description}

{photos.length === 0 ? (

No photos

) : (
{photos.map((photo) => { const isPdf = photo.filename?.endsWith('.pdf') || photo.path?.endsWith('.pdf') return (
{isPdf ? ( ) : ( )}
) })}
)}
) }