import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { Link, useOutletContext } from 'react-router-dom'; import { AlertTriangle, ArrowLeft, ClipboardCheck, Factory, Loader2, Pencil, Plus, Save, Settings2, X } from 'lucide-react'; import DateRangePicker from '../components/DateRangePicker'; import { createProductionWorkstation, fetchProductionOrders, fetchProductionWorkstations, updateProductionWorkstation } from '../dataService'; import type { DateRange, ProductionOrderSummary, ProductionWorkstation } from '../types'; const emptySummary: ProductionOrderSummary = { orders: [], counts: { all: 0, open: 0, cutting: 0, sewing: 0, review: 0, finished: 0, canceled: 0 } }; const inputClassName = 'mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary'; const formatQuantity = (value: number) => new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 2 }).format(value); const formatDateTime = (value: string | null) => value ? new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(value)) : '—'; const ProductionQuality = () => { const { dateRange, setDateRange, refreshInterval, setRefreshInterval } = useOutletContext<{ dateRange: DateRange; setDateRange: (range: DateRange) => void; refreshInterval: number; setRefreshInterval: (interval: number) => void; }>(); const [summary, setSummary] = useState(emptySummary); const [workstations, setWorkstations] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [message, setMessage] = useState(''); const [editingStation, setEditingStation] = useState(null); const [stationForm, setStationForm] = useState({ name: '', code: '', color: '#38bdf8', sortOrder: '50', isActive: true }); const load = useCallback(async () => { setIsLoading(true); try { const [nextSummary, nextWorkstations] = await Promise.all([ fetchProductionOrders(dateRange, undefined, { force: true }), fetchProductionWorkstations(true) ]); setSummary(nextSummary); setWorkstations(nextWorkstations); } catch (error) { setMessage(error instanceof Error ? error.message : 'Não foi possível carregar a qualidade da produção.'); } finally { setIsLoading(false); } }, [dateRange]); useEffect(() => { const timer = window.setTimeout(() => void load(), 0); return () => window.clearTimeout(timer); }, [load]); useEffect(() => { if (!refreshInterval) return undefined; const interval = window.setInterval(() => void load(), refreshInterval); return () => window.clearInterval(interval); }, [load, refreshInterval]); const defects = useMemo(() => summary.orders.flatMap(order => order.defects.map(defect => ({ ...defect, orderId: order.id, orderNumber: order.number || `OP #${order.id}`, productDescription: order.productDescription, unit: order.unit }))), [summary.orders]); const totalDefectiveQuantity = useMemo(() => defects.reduce((total, defect) => total + defect.quantity, 0), [defects]); const affectedOrders = useMemo(() => new Set(defects.map(defect => defect.orderId)).size, [defects]); const fabricHoleDefects = useMemo(() => defects.filter(defect => /\b(?:furo|furos|furado|perfura[çc][ãa]o)\b/i.test(defect.defectType)), [defects]); const fabricHoleQuantity = useMemo(() => fabricHoleDefects.reduce((total, defect) => total + defect.quantity, 0), [fabricHoleDefects]); const fabricHoleLots = useMemo(() => new Set(fabricHoleDefects.map(defect => defect.lotCode).filter(Boolean)).size, [fabricHoleDefects]); const workstationSummary = useMemo(() => { const grouped = new Map(); defects.forEach(defect => { const label = defect.workstationName || 'Sem posto'; const current = grouped.get(label) || { label, quantity: 0, occurrences: 0 }; current.quantity += defect.quantity; current.occurrences += 1; grouped.set(label, current); }); return [...grouped.values()].sort((a, b) => b.quantity - a.quantity); }, [defects]); const defectTypeSummary = useMemo(() => { const grouped = new Map(); defects.forEach(defect => { const label = defect.defectType || 'Sem tipo'; const current = grouped.get(label) || { label, quantity: 0, occurrences: 0 }; current.quantity += defect.quantity; current.occurrences += 1; grouped.set(label, current); }); return [...grouped.values()].sort((a, b) => b.quantity - a.quantity); }, [defects]); const recentDefects = useMemo(() => [...defects].sort((a, b) => new Date(b.occurredAt || 0).getTime() - new Date(a.occurredAt || 0).getTime()).slice(0, 12), [defects]); const resetStationForm = () => { setEditingStation(null); setStationForm({ name: '', code: '', color: '#38bdf8', sortOrder: String((workstations.at(-1)?.sortOrder || 0) + 10), isActive: true }); }; const editStation = (station: ProductionWorkstation) => { setEditingStation(station); setStationForm({ name: station.name, code: station.code, color: station.color, sortOrder: String(station.sortOrder), isActive: station.isActive }); }; const saveStation = async (event: FormEvent) => { event.preventDefault(); setIsSaving(true); setMessage(''); try { const payload = { ...stationForm, sortOrder: Number(stationForm.sortOrder || 0) }; if (editingStation) await updateProductionWorkstation(editingStation.id, payload); else await createProductionWorkstation(payload); await load(); setMessage(editingStation ? 'Posto de trabalho atualizado.' : 'Posto de trabalho criado.'); resetStationForm(); } catch (error) { setMessage(error instanceof Error ? error.message : 'Não foi possível salvar o posto de trabalho.'); } finally { setIsSaving(false); } }; return (
Ordens de Produção

Qualidade e postos

Acompanhe defeitos por origem e mantenha as etapas reais da operação.

void load()} />
{message &&
{message}
} {isLoading ?
: <>

Defeitos registrados

{defects.length}

Ocorrências no período

Peças afetadas

{formatQuantity(totalDefectiveQuantity)}

Somadas pelos apontamentos

OPs com ocorrência

{affectedOrders}

De {summary.orders.length} OPs no período

Furos na malha

{formatQuantity(fabricHoleQuantity)}

{fabricHoleDefects.length} apontamentos · {fabricHoleLots} {fabricHoleLots === 1 ? 'lote' : 'lotes'}

Levantamento de defeitos

Priorize o maior impacto antes de atacar ocorrências isoladas.

{workstationSummary.length ? workstationSummary.map(item =>

{item.label}

{item.occurrences} {item.occurrences === 1 ? 'ocorrência' : 'ocorrências'}

{formatQuantity(item.quantity)}

) :

Nenhum defeito registrado no período.

}

Tipos mais recorrentes

Use estes dados para orientar revisão, fornecedor e correção de processo.

{defectTypeSummary.length ? defectTypeSummary.map(item =>

{item.label}

{item.occurrences} {item.occurrences === 1 ? 'ocorrência' : 'ocorrências'}

{formatQuantity(item.quantity)}

) :

Nenhum tipo de defeito registrado no período.

}

Furos na malha

Apontamentos cujo tipo de defeito contém “furo”, “furado” ou “perfuração”. Registre sempre o lote para identificar a origem.

{formatQuantity(fabricHoleQuantity)} peças
{fabricHoleDefects.length ? fabricHoleDefects.slice(0, 10).map(defect =>
{defect.orderNumber}{defect.lotCode && Lote {defect.lotCode}}

{defect.productDescription}

{defect.workstationName || 'Sem posto'} · {defect.origin || 'origem não informada'}

{formatQuantity(defect.quantity)} {defect.unit}

) :

Nenhum furo de malha registrado no período. Ao apontar o defeito na OP, use “Furo na malha” e informe o lote.

}

Ocorrências recentes

Rastreabilidade por OP, produto, posto e lote.

{recentDefects.length ? recentDefects.map(defect =>
{defect.orderNumber}{defect.workstationName || 'Sem posto'}

{defect.defectType}

{defect.productDescription} · {defect.origin || 'origem não informada'}{defect.lotCode ? ` · lote ${defect.lotCode}` : ''}

{defect.notes &&

{defect.notes}

}

{formatQuantity(defect.quantity)} {defect.unit}

{formatDateTime(defect.occurredAt)}

) :

Nenhuma ocorrência registrada no período.

}

Postos de trabalho

Etapas disponíveis para o apontamento da operação.

{editingStation ? 'Editar posto' : 'Novo posto'}

{editingStation && }
{workstations.map(station =>

{station.name}

{station.code} · posição {station.sortOrder}{station.isActive ? '' : ' · inativo'}

)}
}
); }; export default ProductionQuality;