import React, { useState, useEffect, useMemo } from 'react'; import { Search, FileText, Download, Mail, X, CheckCircle2, Clock, Circle, AlertCircle, MessageCircle, Filter, RotateCcw, Layers } from 'lucide-react'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; import { OFAtiva } from '@/hooks/useOFsAtivas'; import { useAuth } from '@/hooks/useAuth'; import { supabase } from '@/integrations/supabase/client'; import { smartAudio } from '@/utils/smartAudio'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { toast } from 'sonner'; interface SmartConsultaRelatoriosFlowProps { obra: OFAtiva; onVoltarHub: () => void; abrirDiretoTurno?: boolean; } interface PecaStatus { id: string; marca: string; descricao: string; etapa_fase: string; perfil_principal?: string; material?: string; quantidade: number; peso_unitario: number; detalhamento: number; corte: number; montagem: number; solda: number; pintura: number; expedido: number; montado_obra: number; tem_componentes?: boolean | null; } export const SmartConsultaRelatoriosFlow: React.FC = ({ obra, onVoltarHub, abrirDiretoTurno = false, }) => { const { user } = useAuth(); // Aba ativa: 'consulta' | 'relatorios' const [abaAtiva, setAbaAtiva] = useState<'consulta' | 'relatorios'>( abrirDiretoTurno ? 'relatorios' : 'consulta' ); // Estados da Consulta e Filtros const [busca, setBusca] = useState(''); const [filtroFase, setFiltroFase] = useState('todas'); const [filtroPerfilRapido, setFiltroPerfilRapido] = useState(''); const [filtroStatus, setFiltroStatus] = useState<'todos' | 'fabricando' | 'pronto' | 'embarcado' | 'montado'>('todos'); const [pecasStatus, setPecasStatus] = useState([]); const [loading, setLoading] = useState(true); // Estados de Relatórios const [gerandoPDF, setGerandoPDF] = useState(false); // Carregar dados de peças, processos e apontamentos da OF const carregarDadosCompletos = React.useCallback(async () => { try { setLoading(true); const [resPecas, resProcessos, resApontamentos, resExpedidos, resMontados] = await Promise.all([ // 1. Peças da OF supabase .from('pecas') .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal, material, tem_componentes') .eq('of_number', obra.of_number), // 2. Processos de fabricação (para mapear nome sem depender de foreign key) supabase .from('processos_fabricacao') .select('id, nome, ordem'), // 3. Apontamentos da OF supabase .from('apontamentos_producao') .select('peca_id, processo_id, quantidade_produzida') .eq('of_number', obra.of_number), // 4. Expedição em Romaneios supabase .from('itens_romaneio_pecas') .select(` marca, quantidade_expedida, romaneios_expedicao!inner(of_number) `) .eq('romaneios_expedicao.of_number', obra.of_number), // 5. Montagem em Obra (RDO) supabase .from('apontamentos_peca_obra') .select(` marca_peca, quantidade, diario_obra_rdo!inner(of_number) `) .eq('diario_obra_rdo.of_number', obra.of_number), ]); // Mapear processos por ID -> Nome em minúsculas const mapaNomeProcesso = new Map(); (resProcessos.data || []).forEach((proc) => { mapaNomeProcesso.set(proc.id, proc.nome.toLowerCase().trim()); }); // Mapear apontamentos por peca_id const mapaProcPeca = new Map< string, { detalhamento: number; corte: number; montagem: number; solda: number; pintura: number } >(); (resApontamentos.data || []).forEach((ap: any) => { const pecaId = ap.peca_id; const nomeProc = mapaNomeProcesso.get(ap.processo_id) || ''; const qtd = Number(ap.quantidade_produzida || 0); const atual = mapaProcPeca.get(pecaId) || { detalhamento: 0, corte: 0, montagem: 0, solda: 0, pintura: 0, }; if (nomeProc.includes('detalh')) { atual.detalhamento += qtd; } else if (nomeProc.includes('corte')) { atual.corte += qtd; } else if (nomeProc.includes('montag')) { atual.montagem += qtd; } else if (nomeProc.includes('solda')) { atual.solda += qtd; } else if (nomeProc.includes('pint') || nomeProc.includes('galv') || nomeProc.includes('jateam')) { atual.pintura += qtd; } mapaProcPeca.set(pecaId, atual); }); // Mapear expedição por marca const mapaExp = new Map(); (resExpedidos.data || []).forEach((item: any) => { const marca = item.marca; mapaExp.set(marca, (mapaExp.get(marca) || 0) + Number(item.quantidade_expedida || 0)); }); // Mapear montagem por marca const mapaMont = new Map(); (resMontados.data || []).forEach((item: any) => { const marca = item.marca_peca; mapaMont.set(marca, (mapaMont.get(marca) || 0) + Number(item.quantidade || 0)); }); // Montar PecaStatus com ordenação natural alfanumérica const lista: PecaStatus[] = (resPecas.data || []).map((p) => { const procs = mapaProcPeca.get(p.id) || { detalhamento: 0, corte: 0, montagem: 0, solda: 0, pintura: 0, }; return { id: p.id, marca: p.marca || '', descricao: p.descricao || '', etapa_fase: p.etapa_fase || '1', perfil_principal: p.perfil_principal || '', material: p.material || '', quantidade: Number(p.quantidade || 0), peso_unitario: Number(p.peso_unitario || 0), detalhamento: procs.detalhamento, corte: procs.corte, montagem: procs.montagem, solda: procs.solda, pintura: procs.pintura, expedido: mapaExp.get(p.marca) || 0, montado_obra: mapaMont.get(p.marca) || 0, tem_componentes: p.tem_componentes, }; }); // ORDENAÇÃO NATURAL: 1, 2, 3... 10... ou P1, P2, P10, V1, V101 lista.sort((a, b) => a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }) ); setPecasStatus(lista); } catch (e) { console.error('Erro ao carregar dados de status:', e); toast.error('Erro ao consultar status das peças'); } finally { setLoading(false); } }, [obra.of_number]); useEffect(() => { carregarDadosCompletos(); }, [carregarDadosCompletos]); // Lista de fases únicas para filtro const fasesUnicas = useMemo(() => { const setF = new Set(); pecasStatus.forEach((p) => { if (p.etapa_fase && p.etapa_fase.trim()) { setF.add(p.etapa_fase.trim()); } }); return Array.from(setF).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }) ); }, [pecasStatus]); // Filtragem da lista refinada por Fase, Perfil Rápido, Texto e Status const pecasFiltradas = useMemo(() => { return pecasStatus.filter((p) => { // 1. Filtro por Fase if (filtroFase !== 'todas') { if ((p.etapa_fase || '').trim() !== filtroFase) return false; } // 2. Filtro rápido de Perfil / Material (W/HP, L, CH, U, RED, TUB) if (filtroPerfilRapido) { const perfilNorm = (p.perfil_principal || '').toLowerCase(); const descNorm = (p.descricao || '').toLowerCase(); const matNorm = (p.material || '').toLowerCase(); const buscaP = filtroPerfilRapido.toLowerCase(); let bateuPerfil = false; if (buscaP === 'w_hp') { bateuPerfil = perfilNorm.startsWith('w') || perfilNorm.startsWith('hp') || perfilNorm.includes('perfil w') || perfilNorm.includes('hp') || descNorm.startsWith('w') || descNorm.startsWith('hp'); } else if (buscaP === 'l') { bateuPerfil = perfilNorm.startsWith('l') || perfilNorm.includes('cant') || descNorm.startsWith('l') || descNorm.includes('cant'); } else if (buscaP === 'ch') { bateuPerfil = perfilNorm.includes('chapa') || perfilNorm.includes('ch.') || perfilNorm.startsWith('ch') || perfilNorm.startsWith('pl') || descNorm.includes('chapa') || descNorm.includes('ch.') || descNorm.includes('arruela') || matNorm.includes('chapa'); } else if (buscaP === 'u') { bateuPerfil = perfilNorm.startsWith('u') || perfilNorm.startsWith('c') || perfilNorm.includes('perfil u') || perfilNorm.includes('viga u') || descNorm.startsWith('u') || descNorm.includes('perfil u'); } else if (buscaP === 'red') { bateuPerfil = perfilNorm.includes('red') || perfilNorm.includes('ferro') || perfilNorm.includes('barra') || perfilNorm.includes('ø') || descNorm.includes('red') || descNorm.includes('ferro') || descNorm.includes('barra') || descNorm.includes('chumb') || descNorm.includes('ø'); } else if (buscaP === 'tub') { bateuPerfil = perfilNorm.includes('tubo') || perfilNorm.includes('tubular') || perfilNorm.startsWith('tub') || descNorm.includes('tubo') || descNorm.includes('tubular'); } else { bateuPerfil = perfilNorm.includes(buscaP) || descNorm.includes(buscaP) || matNorm.includes(buscaP); } if (!bateuPerfil) return false; } // 3. Filtro texto (busca por marca, perfil, descrição ou material) if (busca.trim()) { const t = busca.toLowerCase().trim(); const match = p.marca.toLowerCase().includes(t) || (p.perfil_principal && p.perfil_principal.toLowerCase().includes(t)) || (p.descricao && p.descricao.toLowerCase().includes(t)) || (p.material && p.material.toLowerCase().includes(t)) || p.etapa_fase.toLowerCase().includes(t); if (!match) return false; } // 4. Filtro por status if (filtroStatus === 'todos') return true; if (filtroStatus === 'montado') return p.montado_obra >= p.quantidade; if (filtroStatus === 'embarcado') return p.expedido >= p.quantidade; if (filtroStatus === 'pronto') return p.pintura >= p.quantidade || p.solda >= p.quantidade; if (filtroStatus === 'fabricando') return (p.corte > 0 || p.detalhamento > 0) && p.pintura < p.quantidade; return true; }); }, [pecasStatus, busca, filtroFase, filtroPerfilRapido, filtroStatus]); // Limpar todos os filtros const handleLimparFiltros = () => { smartAudio.playClick(); setBusca(''); setFiltroFase('todas'); setFiltroPerfilRapido(''); setFiltroStatus('todos'); toast.info('Filtros restaurados'); }; const temFiltroAtivo = busca !== '' || filtroFase !== 'todas' || filtroPerfilRapido !== '' || filtroStatus !== 'todos'; // Badge da etapa const getBadgeEtapa = (concluido: number, total: number) => { if (concluido >= total && total > 0) { return { icon: , cor: 'text-emerald-700 dark:text-emerald-400', label: `${concluido}` }; } if (concluido > 0) { return { icon: , cor: 'text-amber-700 dark:text-amber-400', label: `${concluido}/${total}` }; } return { icon: , cor: 'text-slate-400 dark:text-slate-500', label: '0' }; }; // ───────────────────────────────────────────────────────────── // GERAÇÃO DE RELATÓRIOS EM PDF (jsPDF) // ───────────────────────────────────────────────────────────── // 1. Relatório do Turno de Hoje const gerarRelatorioTurnoPDF = async (acao: 'download' | 'whatsapp' | 'email') => { try { setGerandoPDF(true); const hoje = new Date().toISOString().split('T')[0]; const { data: meusApontamentos, error } = await supabase .from('apontamentos_producao') .select(` quantidade_produzida, data_apontamento, created_at, pecas ( marca, descricao, peso_unitario, etapa_fase ), processos_fabricacao ( nome ) `) .eq('of_number', obra.of_number) .eq('data_apontamento', hoje) .eq('created_by', user?.id || ''); if (error) throw error; const doc = new jsPDF(); const titulo = `RELATÓRIO DO TURNO - MODO SMART`; const dataFormatada = new Date().toLocaleDateString('pt-BR'); const horaFormatada = new Date().toLocaleTimeString('pt-BR'); // 1. TOPO LIMPO (FUNDO BRANCO, AZUL MARINHO & VERDE - ZERO DESPERDÍCIO DE TONER) doc.setFont('helvetica', 'bold'); doc.setFontSize(16); doc.setTextColor(15, 43, 92); // Azul Marinho doc.text(titulo, 14, 16); doc.setFontSize(9); doc.setFont('helvetica', 'bold'); doc.setTextColor(5, 150, 105); // Verde doc.text(`TURNO DO OPERADOR`, 150, 16); doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5); doc.setTextColor(31, 41, 55); // Preto / Grafite doc.text(`OF: ${obra.of_number} | Cliente: ${obra.cliente || 'Industrial'}`, 14, 23); doc.text(`Operador: ${user?.email || 'Chão de Fábrica'} | Data: ${dataFormatada} às ${horaFormatada}`, 14, 29); // Linha divisória de topo fina: Azul Marinho com terminal em Verde doc.setDrawColor(30, 58, 138); // Azul Marinho doc.setLineWidth(1.2); doc.line(14, 33, 165, 33); doc.setDrawColor(5, 150, 105); // Verde doc.setLineWidth(1.2); doc.line(165, 33, 196, 33); let totalPecas = 0; let totalKg = 0; const tableRows = (meusApontamentos || []).map((ap: any) => { const marca = ap.pecas?.marca || 'N/A'; const fase = ap.pecas?.etapa_fase || 'Geral'; const processo = ap.processos_fabricacao?.nome || 'Produção'; const qtd = Number(ap.quantidade_produzida || 0); const pesoUnit = Number(ap.pecas?.peso_unitario || 0); const pesoTotal = qtd * pesoUnit; totalPecas += qtd; totalKg += pesoTotal; return [ marca, fase, processo, qtd.toString(), `${pesoUnit.toFixed(1)} kg`, `${pesoTotal.toFixed(1)} kg`, ]; }); autoTable(doc, { startY: 38, head: [['Marca', 'Fase', 'Processo', 'Qtd', 'Peso Unit.', 'Peso Total']], body: tableRows, theme: 'plain', headStyles: { fillColor: [248, 250, 252], // Fundo branco/gelo suave textColor: [15, 43, 92], // Azul Marinho fontStyle: 'bold', lineWidth: 0.4, lineColor: [30, 58, 138], // Linha azul marinho }, styles: { fontSize: 8.5, textColor: [31, 41, 55], // Preto nítido cellPadding: 2.5, lineWidth: 0.1, lineColor: [226, 232, 240], // Linha cinza clara }, columnStyles: { 0: { cellWidth: 26, fontStyle: 'bold', textColor: [15, 43, 92] }, // Marca em Azul Marinho 1: { cellWidth: 22, halign: 'center' }, 2: { cellWidth: 45 }, 3: { cellWidth: 20, halign: 'center', fontStyle: 'bold' }, 4: { cellWidth: 32, halign: 'right' }, 5: { cellWidth: 35, halign: 'right', fontStyle: 'bold', textColor: [22, 101, 52] }, // Peso em Verde }, }); const finalY = (doc as any).lastAutoTable?.finalY || 80; // Quadro de Resumo com Fundo Branco, Barra e Borda Verde const boxResumoY = finalY + 5; doc.setFillColor(5, 150, 105); // Barra lateral verde doc.roundedRect(14, boxResumoY, 3, 16, 1, 1, 'F'); doc.setDrawColor(5, 150, 105); doc.setLineWidth(0.4); doc.roundedRect(17, boxResumoY, 179, 16, 1.5, 1.5, 'S'); doc.setFont('helvetica', 'bold'); doc.setFontSize(8.5); doc.setTextColor(22, 101, 52); // Verde doc.text(`RESUMO DO TURNO:`, 22, boxResumoY + 6); doc.setFont('helvetica', 'bold'); doc.setFontSize(9.5); doc.setTextColor(15, 23, 42); // Preto doc.text( `Total de Peças Apontadas: ${totalPecas} un | Peso Total: ${totalKg.toFixed(1)} kg`, 22, boxResumoY + 12 ); const nomeArquivo = `Turno_${obra.of_number}_${hoje}.pdf`; if (acao === 'download') { doc.save(nomeArquivo); smartAudio.playSuccess(); toast.success('Relatório em PDF baixado com sucesso!'); } else if (acao === 'whatsapp') { smartAudio.playClick(); const textoMsg = encodeURIComponent( `*TRACKSTEEL - RELATÓRIO DO TURNO*\n` + `*Obra:* OF ${obra.of_number} - ${obra.cliente || ''}\n` + `*Data:* ${dataFormatada}\n` + `*Operador:* ${user?.email || 'Chão de Fábrica'}\n` + `*Produção:* ${totalPecas} peças apontadas (${totalKg.toFixed(1)} kg)\n\n` + `_Registrado com sucesso via Modo Smart TrackSteel._` ); window.open(`https://wa.me/?text=${textoMsg}`, '_blank'); } else if (acao === 'email') { smartAudio.playClick(); const assunto = encodeURIComponent(`Relatório de Turno - OF ${obra.of_number}`); const corpo = encodeURIComponent( `Segue o resumo do turno operacional:\n\n` + `Obra: OF ${obra.of_number}\n` + `Data: ${dataFormatada}\n` + `Total de Peças Apontadas: ${totalPecas} un\n` + `Peso Total: ${totalKg.toFixed(1)} kg\n` ); window.open(`mailto:?subject=${assunto}&body=${corpo}`, '_blank'); } } catch (e: any) { smartAudio.playAlert(); console.error('Erro ao gerar relatório:', e); toast.error('Erro ao gerar relatório: ' + (e?.message || '')); } finally { setGerandoPDF(false); } }; // 2. Relatório Geral da Obra const gerarRelatorioGeralObraPDF = () => { try { setGerandoPDF(true); const doc = new jsPDF(); const dataFormatada = new Date().toLocaleDateString('pt-BR'); // Topo limpo sem blocos pretos doc.setFont('helvetica', 'bold'); doc.setFontSize(16); doc.setTextColor(15, 43, 92); // Azul Marinho doc.text(`RELATÓRIO GERAL DE FABRICAÇÃO`, 14, 16); doc.setFontSize(9); doc.setFont('helvetica', 'bold'); doc.setTextColor(5, 150, 105); // Verde doc.text(`OF: ${obra.of_number}`, 165, 16); doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5); doc.setTextColor(31, 41, 55); // Preto doc.text(`Cliente: ${obra.cliente || 'Industrial'} | Emissão: ${dataFormatada}`, 14, 23); // Linha divisória fina: Azul Marinho + Verde doc.setDrawColor(30, 58, 138); doc.setLineWidth(1.2); doc.line(14, 28, 165, 28); doc.setDrawColor(5, 150, 105); doc.setLineWidth(1.2); doc.line(165, 28, 196, 28); const tableRows = pecasStatus.map((p) => [ p.marca, p.perfil_principal || p.descricao, p.etapa_fase || '1', p.quantidade.toString(), p.detalhamento.toString(), p.corte.toString(), p.solda.toString(), p.pintura.toString(), p.expedido.toString(), p.montado_obra.toString(), ]); autoTable(doc, { startY: 33, head: [['Marca', 'Perfil / Peça', 'Fase', 'Total', 'Det.', 'Corte', 'Solda', 'Pint.', 'Exp.', 'Obra']], body: tableRows, theme: 'plain', headStyles: { fillColor: [248, 250, 252], textColor: [15, 43, 92], fontStyle: 'bold', lineWidth: 0.4, lineColor: [30, 58, 138], }, styles: { fontSize: 7.5, textColor: [31, 41, 55], cellPadding: 2, lineWidth: 0.1, lineColor: [226, 232, 240], }, columnStyles: { 0: { fontStyle: 'bold', textColor: [15, 43, 92] }, 1: { cellWidth: 38 }, 2: { halign: 'center' }, 3: { halign: 'center', fontStyle: 'bold' }, 4: { halign: 'center' }, 5: { halign: 'center' }, 6: { halign: 'center' }, 7: { halign: 'center' }, 8: { halign: 'center' }, 9: { halign: 'center' }, 10: { halign: 'center', textColor: [22, 101, 52] }, }, }); doc.save(`Progresso_Geral_OF_${obra.of_number}.pdf`); smartAudio.playSuccess(); toast.success('Relatório Geral da Obra gerado!'); } catch (e: any) { smartAudio.playAlert(); toast.error('Erro ao gerar relatório: ' + e?.message); } finally { setGerandoPDF(false); } }; return (
{/* Navegação entre Abas: Consulta Rápida vs Relatórios */}
{/* ───────────────────────────────────────────────────────────── ABA 1: CONSULTA RÁPIDA DE PEÇAS COM FILTROS AVANÇADOS E TIMELINE ───────────────────────────────────────────────────────────── */} {abaAtiva === 'consulta' && (
{/* Campo de Busca Rápida */}
setBusca(e.target.value)} placeholder="Buscar por Marca, Perfil (W, L...), Descrição..." className="h-13 pl-11 pr-11 bg-white dark:bg-slate-800/90 border-slate-300 dark:border-slate-700 text-slate-800 dark:text-slate-100 text-sm sm:text-base font-medium rounded-2xl placeholder:text-slate-400 dark:placeholder:text-slate-500 shadow-sm" /> {busca && ( )}
{/* FILTRO 1: Pílulas Rápidas de Perfis Abreviações (W/HP, L, CH, U, RED, TUB) */}
PERFIL: {[ { id: 'w_hp', label: 'W/HP' }, { id: 'l', label: 'L' }, { id: 'ch', label: 'CH' }, { id: 'u', label: 'U' }, { id: 'red', label: 'RED' }, { id: 'tub', label: 'TUB' }, ].map((p) => { const ativo = filtroPerfilRapido === p.id; return ( ); })}
{/* FILTRO 2: Fases da Obra (Com botão 'Todas' travado/fixo) */}
FASE: {fasesUnicas.length > 0 &&
} {/* Demais fases rolando horizontalmente */}
{fasesUnicas.map((fase) => ( ))}
{/* FILTRO 3: Status Fabril (Com botão 'Todos' travado/fixo sem scroll) e Botão Limpar */}
{/* Botão "Todos" fixo/travado à esquerda */} {/* Divisor vertical */}
{/* Demais botões roláveis horizontalmente */}
{[ { id: 'fabricando', label: 'Em Fabricação' }, { id: 'pronto', label: 'Pronto Pátio' }, { id: 'embarcado', label: 'Embarcado' }, { id: 'montado', label: 'Montado' }, ].map((f) => ( ))}
{/* Botão Limpar fixo à direita */} {temFiltroAtivo && ( )}
{/* Contador de Peças Encontradas */}
Exibindo {pecasFiltradas.length} de{' '} {pecasStatus.length} marcas Ordem Numérica Natural
{/* Lista de Peças Ordenadas com Timeline Completa */}
{loading ? (
Carregando peças e apontamentos...
) : pecasFiltradas.length === 0 ? (

Nenhuma peça encontrada

Tente ajustar a busca, a fase ou clique em limpar filtros

) : ( pecasFiltradas.map((peca) => (
{/* Linha Superior: Marca, Perfil, Fase e Quantidade */}
{peca.marca} {peca.tem_componentes === false && ( S/M )} {peca.perfil_principal && ( {peca.perfil_principal} )} Fase {peca.etapa_fase} {peca.material && ( {peca.material} )}
{peca.descricao || 'Peça Estrutural'}
Total {peca.quantidade}{' '} un
{/* Linha do Tempo Visual com Etapas Fabris Corretas: Det → Corte → Solda → Pint → Emb → Obra */}
{[ { nome: 'Det.', valor: peca.detalhamento, isPula: false }, { nome: 'Corte', valor: peca.corte, isPula: false }, { nome: 'Solda', valor: peca.solda, isPula: peca.tem_componentes === false }, { nome: 'Pint.', valor: peca.pintura, isPula: false }, { nome: 'Emb.', valor: peca.expedido, isPula: false }, { nome: 'Obra', valor: peca.montado_obra, isPula: false }, ].map((etapa, idx) => { if (etapa.isPula) { return (
{etapa.nome}
S/M
N/A
); } const badge = getBadgeEtapa(etapa.valor, peca.quantidade); return (
{etapa.nome}
{badge.icon}
{badge.label}
); })}
)) )}
)} {/* ───────────────────────────────────────────────────────────── ABA 2: EMISSÃO DE RELATÓRIOS EM PDF & COMPARTILHAMENTO ───────────────────────────────────────────────────────────── */} {abaAtiva === 'relatorios' && (

Selecione o Relatório Desejado

Gere documentos oficiais para download ou envio imediato

{/* CARD 1: RELATÓRIO DO MEU TURNO */}
Opção 1

Relatório do Meu Turno (Hoje)

Lista todas as peças apontadas por você no turno de hoje

{/* CARD 2: RELATÓRIO GERAL DA OBRA */}
Opção 2

Relatório de Produção Geral da Obra

Quadro completo de todas as marcas com avanço por etapa

)} {/* Botão de Voltar ao Hub */}
); };