import React, { useState, useMemo } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Flame, CheckCircle2, AlertTriangle, Clock, Layers, Search, RefreshCw, ChevronDown, ChevronUp, PackageCheck, ShieldCheck, Wrench, Scissors, Info } from 'lucide-react'; import { useKitsProducao, PecaKitInfo } from '@/hooks/useKitsProducao'; import { useOFsAtivas, OFAtiva } from '@/hooks/useOFsAtivas'; interface MapaTermicoKitsViewProps { initialOF?: string; initialFase?: string; readOnlyOF?: boolean; onSelectPecaParaApontar?: (peca: PecaKitInfo) => void; } export const MapaTermicoKitsView: React.FC = ({ initialOF = '', initialFase = 'all', readOnlyOF = false, onSelectPecaParaApontar, }) => { const { ofsAtivas, isLoading: loadingOFs } = useOFsAtivas(); const [selectedOF, setSelectedOF] = useState(initialOF || ''); const [selectedFase, setSelectedFase] = useState(initialFase); const [busca, setBusca] = useState(''); const [filtroStatus, setFiltroStatus] = useState<'todos' | 'liberados' | 'parciais' | 'travados'>('todos'); const [pecasExpandidas, setPecasExpandidas] = useState>(new Set()); // Atualizar OF padrão se carregar depois React.useEffect(() => { if (initialOF) { setSelectedOF(initialOF); } else if (!selectedOF && ofsAtivas.length > 0) { setSelectedOF(ofsAtivas[0].of_number); } }, [initialOF, ofsAtivas, selectedOF]); const { listaKits, resumoKits, loading, refetch } = useKitsProducao(selectedOF); // Extrair fases únicas da lista de kits carregada de toda a OF const fasesDisponiveis = useMemo(() => { const s = new Set(); listaKits.forEach((k) => { if (k.etapa_fase && k.etapa_fase.trim()) s.add(k.etapa_fase.trim()); }); return Array.from(s).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })); }, [listaKits]); // Alternar expansão de componentes de uma peça const toggleExpand = (pecaId: string) => { setPecasExpandidas((prev) => { const next = new Set(prev); if (next.has(pecaId)) { next.delete(pecaId); } else { next.add(pecaId); } return next; }); }; const expandirTodas = () => { setPecasExpandidas(new Set(listaKits.map((k) => k.peca_id))); }; const recolherTodas = () => { setPecasExpandidas(new Set()); }; // Filtragem combinada (Fase + Status + Busca textual) const pecasFiltradas = useMemo(() => { return listaKits.filter((item) => { // Filtro de fase if (selectedFase && selectedFase !== 'all' && selectedFase !== 'todas') { if (item.etapa_fase !== selectedFase) return false; } // Filtro de status if (filtroStatus === 'liberados') { if (item.kits_completos_cortados <= 0) return false; } else if (filtroStatus === 'parciais') { if ( item.kits_completos_cortados >= item.quantidade_total || item.status_termico === 'vermelho' ) return false; } else if (filtroStatus === 'travados') { if (item.status_termico !== 'vermelho') return false; } // Filtro de busca textual if (busca.trim()) { const t = busca.toLowerCase().trim(); const bateMarca = item.marca.toLowerCase().includes(t); const bateDesc = (item.descricao || '').toLowerCase().includes(t); const batePerfil = (item.perfil_principal || '').toLowerCase().includes(t); const bateComp = item.componentes.some((c) => c.marca_componente.toLowerCase().includes(t) || (c.perfil || '').toLowerCase().includes(t) ); if (!bateMarca && !bateDesc && !batePerfil && !bateComp) return false; } return true; }); }, [listaKits, selectedFase, filtroStatus, busca]); // Formatação de pesos em kg const formatarKg = (valorKg: number) => { if (!valorKg || valorKg <= 0) return '0 kg'; return `${valorKg.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} kg`; }; // KPIs calculados com base nas peças filtradas const resumoExibido = useMemo(() => { let total100 = 0; let totalParciais = 0; let totalTravados = 0; let pesoTot = 0; let pesoCort = 0; let peso100 = 0; let pesoUnLiberadas = 0; let pesoParciais = 0; let pesoNaoIniciados = 0; let unTot = 0; let unLib = 0; pecasFiltradas.forEach((item) => { unTot += item.quantidade_total; unLib += item.kits_completos_cortados; pesoTot += item.peso_total_peca; pesoCort += item.peso_cortado_componentes; pesoUnLiberadas += item.peso_kits_liberados; if (item.status_termico === 'verde') { total100++; peso100 += item.peso_total_peca; } else if (item.status_termico === 'verde_parcial' || item.status_termico === 'amarelo') { totalParciais++; pesoParciais += item.peso_total_peca; } else { totalTravados++; pesoNaoIniciados += item.peso_total_peca; } }); return { totalPecas: pecasFiltradas.length, totalKits100Porcento: total100, totalKitsParciais: totalParciais, totalKitsNaoIniciados: totalTravados, pesoTotalOFKg: pesoTot, pesoCortadoTotalKg: pesoCort, peso100PorcentoKg: peso100, pesoUnidadesLiberadasKg: pesoUnLiberadas, pesoParciaisKg: pesoParciais, pesoNaoIniciadosKg: pesoNaoIniciados, percentualGeralOF: pesoTot > 0 ? Math.min(100, Math.round((pesoCort / pesoTot) * 100)) : 0, totalUnidadesTotal: unTot, totalUnidadesLiberadasSolda: unLib, }; }, [pecasFiltradas]); return (
{/* ──────────────── Barra de Controles e Seletores ──────────────── */}

Mapa Térmico de Kits de Produção Corte ➔ Solda

Prontidão física de montagem: kits cortados disponíveis para liberar a Solda

{/* Seletor de OF */}
{readOnlyOF ? (
OF {selectedOF}
) : ( )}
{/* Seletor de Fase */}
{/* Busca Rápida */}
setBusca(e.target.value)} className="h-9 pl-9 text-xs" />
{/* ──────────────── Cards de KPIs do Mapa Térmico ──────────────── */} {resumoExibido && (
{/* KPI 1: 100% Liberadas */}
100% Liberadas {formatarKg(resumoExibido.peso100PorcentoKg)}
{resumoExibido.totalKits100Porcento}
peças com todos os kits cortados
{/* KPI 2: Kits Montáveis Liberados */}
Unidades Liberadas {formatarKg(resumoExibido.pesoUnidadesLiberadasKg)}
{resumoExibido.totalUnidadesLiberadasSolda} / {resumoExibido.totalUnidadesTotal} un
kits prontos na bancada de solda
{/* KPI 3: Em Andamento / Quase Prontos */}
Corte em Andamento {formatarKg(resumoExibido.pesoParciaisKg)}
{resumoExibido.totalKitsParciais}
kits parciais (prioridade de corte)
{/* KPI 4: Travados / 0% */}
Não Iniciados {formatarKg(resumoExibido.pesoNaoIniciadosKg)}
{resumoExibido.totalKitsNaoIniciados}
sem nenhum componente cortado
)} {/* ──────────────── Filtros por Status Térmico & Ações em Massa ──────────────── */}
{/* ──────────────── Lista de Peças com Mapa Térmico ──────────────── */} {!loading && listaKits.length > 0 && !listaKits.some((k) => k.componentes && k.componentes.length > 0) && (
Modo Peça Direta: Esta OF não possui subcomponentes cadastrados no sistema. O avanço no Corte e a liberação das peças para a Solda são gerenciados diretamente pelo apontamento das peças principais.
)} {loading ? (
Calculando matriz de kits e completude...
) : pecasFiltradas.length === 0 ? (

Nenhuma peça encontrada

Tente ajustar a busca ou os filtros de status

) : (
{pecasFiltradas.map((peca) => { const isExpanded = pecasExpandidas.has(peca.peca_id); const totalKits = peca.quantidade_total; const kitsProntos = peca.kits_completos_cortados; const pct = peca.percentual_termico; const eh100 = kitsProntos >= totalKits; const temParcial = kitsProntos > 0 && kitsProntos < totalKits; const gargalo = peca.componente_gargalo; // Cores térmicas let barColor = 'bg-rose-500'; let badgeBg = 'bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950/60 dark:text-rose-300 dark:border-rose-800'; if (eh100) { barColor = 'bg-emerald-500'; badgeBg = 'bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border-emerald-800'; } else if (temParcial) { barColor = 'bg-emerald-500'; badgeBg = 'bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-950/60 dark:text-blue-300 dark:border-blue-800'; } else if (pct >= 40) { barColor = 'bg-amber-500'; badgeBg = 'bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950/60 dark:text-amber-300 dark:border-amber-800'; } return (
{/* Linha Principal do Card */}
toggleExpand(peca.peca_id)} className="p-4 cursor-pointer flex flex-col sm:flex-row sm:items-center justify-between gap-3 hover:bg-slate-50/50 dark:hover:bg-slate-800/40 transition-colors select-none" > {/* Lado Esquerdo: Identificação da Peça */}
= 40 ? 'bg-amber-500 text-slate-950' : 'bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400' }`} > {pct}%
{peca.marca} {peca.etapa_fase.toLowerCase().startsWith('fase') ? peca.etapa_fase : `Fase ${peca.etapa_fase}`} {peca.perfil_principal && ( {peca.perfil_principal} )} {peca.tem_componentes === false && ( S/M (Sem Montagem) )}
{peca.descricao || 'Peça Estrutural'} {peca.peso_unitario > 0 && ( • {peca.peso_unitario.toFixed(1)} kg/un (Total: {peca.peso_total_peca.toFixed(1)} kg) )}
{/* Lado Direito: Status dos Kits e Métricas */}
{eh100 ? `✅ 100% Liberado (${kitsProntos}/${totalKits} un)` : temParcial ? `🟢 ${kitsProntos}/${totalKits} kits liberados na Solda` : pct >= 40 ? `🟡 0/${totalKits} kits (Corte ${pct}%)` : `🔴 0/${totalKits} kits (Não iniciado)`}
{/* Gargalo em Destaque */} {gargalo && kitsProntos < totalKits && (
Gargalo: {gargalo.marca_componente} (faltam {gargalo.falta_para_prox_kit} un para próx. kit)
)}
{isExpanded ? : }
{/* Barra Térmica de Progresso */}
{/* ──────────────── Detalhamento dos Componentes do Kit (Expansível) ──────────────── */} {isExpanded && (
Componentes Requeridos para {totalKits}x {peca.marca} ({peca.componentes.length} itens no kit): {onSelectPecaParaApontar && ( )}
{peca.componentes.length === 0 ? (

Esta peça não possui subcomponentes cadastrados (é fabricada como peça inteira).

) : (
{peca.componentes.map((comp) => { const isGargalo = gargalo?.id === comp.id && kitsProntos < totalKits; const comp100 = comp.concluido; return (
{comp.marca_componente} {comp.perfil && ( {comp.perfil} )} {isGargalo && ( Gargalo )}
{comp.quantidade_por_peca} un/peça • Total da OF: {comp.quantidade_total_necessaria} un
{comp.quantidade_cortada} / {comp.quantidade_total_necessaria} un
Dá para {comp.kits_possiveis} {comp.kits_possiveis === 1 ? 'kit' : 'kits'} ({comp.percentual_cortado}%)
); })}
)}
)}
); })}
)}
); };