import { useState, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { queryOptions } from '@tanstack/react-query' import { repairNoteListOptions } from '@/api/repairs' import { api } from '@/lib/api-client' import { useAuthStore } from '@/stores/auth.store' import { generateAndUploadPdf } from './generate-pdf' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { FileText, Download, Check, Eye } from 'lucide-react' import { toast } from 'sonner' import type { RepairTicket, RepairLineItem } from '@/types/repair' interface FileRecord { id: string path: string filename: string category: 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, }) } interface PdfModalProps { ticket: RepairTicket lineItems: RepairLineItem[] ticketId: string } export function PdfModal({ ticket, lineItems, ticketId }: PdfModalProps) { const [open, setOpen] = useState(false) const [generating, setGenerating] = useState(false) const token = useAuthStore((s) => s.token) // Fetch notes and photos const { data: notesData } = useQuery(repairNoteListOptions(ticketId)) const { data: filesData } = useQuery(ticketFilesOptions(ticketId)) const allNotes = notesData?.data ?? [] const customerNotes = allNotes.filter((n) => n.visibility === 'customer') const allPhotos = (filesData?.data ?? []).filter((f) => f.category !== 'document') // Selection state const [selectedNoteIds, setSelectedNoteIds] = useState>(new Set()) const [selectedPhotoIds, setSelectedPhotoIds] = useState>(new Set()) const [includeLineItems, setIncludeLineItems] = useState(true) // Default: select all customer-visible notes and completed photos useEffect(() => { if (open) { setSelectedNoteIds(new Set(customerNotes.map((n) => n.id))) const completedPhotos = allPhotos.filter((p) => p.category === 'completed') setSelectedPhotoIds(new Set(completedPhotos.length > 0 ? completedPhotos.map((p) => p.id) : [])) setIncludeLineItems(true) } }, [open, notesData, filesData]) function toggleNote(id: string) { setSelectedNoteIds((prev) => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) } function togglePhoto(id: string) { setSelectedPhotoIds((prev) => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) } function selectAllNotes() { setSelectedNoteIds(new Set(customerNotes.map((n) => n.id))) } function selectNone() { setSelectedNoteIds(new Set()) } async function handleGenerate() { setGenerating(true) try { const selectedNotes = allNotes.filter((n) => selectedNoteIds.has(n.id)) await generateAndUploadPdf( { ticket, lineItems: includeLineItems ? lineItems : [], notes: selectedNotes, includeNotes: selectedNotes.length > 0, }, ticketId, token, ) toast.success('PDF generated and saved to documents') setOpen(false) } catch { toast.error('Failed to generate PDF') } finally { setGenerating(false) } } function formatDate(dateStr: string) { return new Date(dateStr).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) } return ( Generate PDF — Ticket #{ticket.ticketNumber}
{/* Line Items toggle */}
{/* Notes selection */}
|
{customerNotes.length === 0 ? (

No customer-visible notes

) : (
{customerNotes.map((note) => ( ))}
)}
{/* Photos selection */} {allPhotos.length > 0 && (
{allPhotos.map((photo) => ( ))}
)} {/* Summary */}
PDF will include: ticket details, {includeLineItems ? `${lineItems.length} line items` : 'no line items'}, {selectedNoteIds.size} notes{selectedPhotoIds.size > 0 ? `, ${selectedPhotoIds.size} photos` : ''}
{/* Generate button */}
) } function AuthThumbnail({ path }: { path: string }) { 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 }