import { useMemo, useState } from 'react' import { Link } from 'react-router' import { useQuery, useQueries } from '@tanstack/react-query' import { apiFetch, apiFetchBlob } from '@/lib/api' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import type { Invoice, WorkOrder, Client, Vehicle } from '@/lib/types' type PeriodKey = 'month' | '30d' | '90d' | 'year' | 'all' type DocFilter = 'all' | 'quote' | 'invoice' const PERIOD_OPTIONS: { key: PeriodKey; label: string }[] = [ { key: 'month', label: 'Mês atual' }, { key: '30d', label: '30 dias' }, { key: '90d', label: '90 dias' }, { key: 'year', label: '12 meses' }, { key: 'all', label: 'Todo o período' }, ] function getPeriodStart(period: PeriodKey, now = new Date()) { const start = new Date(now) if (period === 'all') return null if (period === 'month') { start.setDate(1) start.setHours(0, 0, 0, 0) return start } if (period === '30d') { start.setDate(now.getDate() - 30) return start } if (period === '90d') { start.setDate(now.getDate() - 90) return start } start.setMonth(now.getMonth() - 12) return start } export default function InvoicesPage() { const [viewError, setViewError] = useState('') const [period, setPeriod] = useState('month') const [clientFilter, setClientFilter] = useState('all') const [docFilter, setDocFilter] = useState('all') const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const { data: invoices = [], isLoading } = useQuery({ queryKey: ['invoices'], queryFn: () => apiFetch('/invoices'), }) const { data: workOrders = [] } = useQuery({ queryKey: ['work-orders-for-transactions'], queryFn: () => apiFetch('/work-orders'), }) const { data: clients = [] } = useQuery({ queryKey: ['clients'], queryFn: () => apiFetch('/clients'), }) const clientNameById = Object.fromEntries(clients.map((c) => [c.id, c.name])) const woById = Object.fromEntries(workOrders.map((wo) => [wo.id, wo])) const uniqueClientIds = Array.from( new Set(workOrders.map((wo) => wo.client_id).filter((id): id is string => !!id)) ) const vehiclesByClientQ = useQueries({ queries: uniqueClientIds.map((clientId) => ({ queryKey: ['vehicles', clientId, 'transactions'], queryFn: () => apiFetch(`/clients/${clientId}/vehicles`), staleTime: 5 * 60 * 1000, })), }) const vehicleById = useMemo(() => { const map: Record = {} for (const q of vehiclesByClientQ) { for (const v of (q.data ?? [])) map[v.id] = v } return map }, [vehiclesByClientQ]) const filtered = useMemo(() => { const start = getPeriodStart(period) return invoices.filter((inv) => { if (docFilter !== 'all' && inv.type !== docFilter) return false const wo = woById[inv.work_order_id] if (clientFilter !== 'all' && wo?.client_id !== clientFilter) return false const issued = new Date(inv.issued_at) if (start && issued < start) return false if (dateFrom) { const from = new Date(`${dateFrom}T00:00:00`) if (issued < from) return false } if (dateTo) { const to = new Date(`${dateTo}T23:59:59`) if (issued > to) return false } return true }) }, [invoices, woById, period, clientFilter, docFilter, dateFrom, dateTo]) const sorted = [...filtered].sort( (a, b) => new Date(b.issued_at).getTime() - new Date(a.issued_at).getTime() ) const totalQuotes = sorted.filter((d) => d.type === 'quote').length const totalInvoices = sorted.filter((d) => d.type === 'invoice').length async function openPDF(inv: Invoice) { setViewError('') try { const blob = await apiFetchBlob(`/invoices/${inv.id}/pdf`) const url = URL.createObjectURL(blob) window.open(url, '_blank', 'noopener,noreferrer') setTimeout(() => URL.revokeObjectURL(url), 60_000) } catch (err) { setViewError((err as Error).message) } } return (

Transações

Consulta e impressão de documentos gerados pelo fluxo das OTs.

Ir para Ordens de Trabalho →
setDateFrom(e.target.value)} className="w-full rounded-md border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-white" />
setDateTo(e.target.value)} className="w-full rounded-md border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-white" />
{totalQuotes} orçamentos {totalInvoices} faturas {sorted.length} documento(s) no filtro atual
{viewError &&

{viewError}

} {isLoading ? (

A carregar...

) : sorted.length === 0 ? (

Nenhum documento encontrado com os filtros selecionados.

) : (
{sorted.map((inv) => { const wo = woById[inv.work_order_id] const clientName = wo?.client_id ? (clientNameById[wo.client_id] ?? '—') : '—' const vehiclePlate = wo?.vehicle_id ? (vehicleById[wo.vehicle_id]?.plate ?? '—') : '—' return ( ) })}
Tipo OT / Cliente / Viatura Emitida
{inv.type === 'quote' ? ( Orçamento ) : ( Fatura )} #{inv.number}
#{wo?.number ?? '—'}
{clientName}
{vehiclePlate}
{new Intl.DateTimeFormat('pt-PT').format(new Date(inv.issued_at))}
)}
) }