From 5902b6f8ef8c0930427bf48ff72e4ee99dbafb69 Mon Sep 17 00:00:00 2001 From: Marcos Reifonas Date: Mon, 21 Sep 2026 22:58:07 +0000 Subject: [PATCH] fix: corrigir processos vazios no item 4, adicionar filtros por perfil/fase e ordenacao natural das marcas --- .../SmartConsultaRelatoriosFlow.tsx | 433 ++++++++++++++---- .../smart/components/SmartProducaoFlow.tsx | 2 +- 2 files changed, 337 insertions(+), 98 deletions(-) diff --git a/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx b/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx index 96abc3c..29416c9 100644 --- a/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx +++ b/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Search, FileText, - Share2, Download, Mail, X, @@ -10,8 +9,10 @@ import { Clock, Circle, AlertCircle, - Eye, - MessageCircle + MessageCircle, + Filter, + RotateCcw, + Layers } from 'lucide-react'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; @@ -34,8 +35,11 @@ interface PecaStatus { 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; @@ -56,35 +60,42 @@ export const SmartConsultaRelatoriosFlow: React.FC('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 e progresso + // Carregar dados de peças, processos e apontamentos da OF const carregarDadosCompletos = React.useCallback(async () => { try { setLoading(true); - const [resPecas, resApontamentos, resExpedidos, resMontados] = await Promise.all([ + 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') + .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal, material') .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, - quantidade_produzida, - processos_fabricacao ( - nome - ) - `) + .select('peca_id, processo_id, quantidade_produzida') .eq('of_number', obra.of_number), + + // 4. Expedição em Romaneios supabase .from('itens_romaneio_pecas') .select(` @@ -93,6 +104,8 @@ export const SmartConsultaRelatoriosFlow: React.FC(); + // 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 = (ap.processos_fabricacao?.nome || '').toLowerCase(); + const nomeProc = mapaNomeProcesso.get(ap.processo_id) || ''; const qtd = Number(ap.quantidade_produzida || 0); - const atual = mapaProc.get(pecaId) || { corte: 0, montagem: 0, solda: 0, pintura: 0 }; - 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('pintura')) atual.pintura += qtd; + const atual = mapaProcPeca.get(pecaId) || { + detalhamento: 0, + corte: 0, + montagem: 0, + solda: 0, + pintura: 0, + }; - mapaProc.set(pecaId, atual); + 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 @@ -133,16 +170,26 @@ export const SmartConsultaRelatoriosFlow: React.FC { - const procs = mapaProc.get(p.id) || { corte: 0, montagem: 0, solda: 0, pintura: 0 }; + 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, - quantidade: p.quantidade, - peso_unitario: p.peso_unitario, + 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, @@ -152,6 +199,11 @@ export const SmartConsultaRelatoriosFlow: React.FC + a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }) + ); + setPecasStatus(lista); } catch (e) { console.error('Erro ao carregar dados de status:', e); @@ -165,31 +217,92 @@ export const SmartConsultaRelatoriosFlow: React.FC { + 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) => { - // Filtro texto + // 1. Filtro por Fase + if (filtroFase !== 'todas') { + if ((p.etapa_fase || '').trim() !== filtroFase) return false; + } + + // 2. Filtro rápido de Perfil / Material (W, L, Chapa, Redondo, Tubo) + 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') { + bateuPerfil = perfilNorm.startsWith('w') || perfilNorm.includes('w150') || perfilNorm.includes('w200') || perfilNorm.includes('w250') || perfilNorm.includes('w310') || perfilNorm.includes('perfil w'); + } else if (buscaP === 'l') { + bateuPerfil = perfilNorm.startsWith('l') || perfilNorm.includes('cant') || descNorm.includes('l2x'); + } else if (buscaP === 'chapa') { + bateuPerfil = perfilNorm.includes('chapa') || perfilNorm.includes('ch.') || descNorm.includes('chapa') || descNorm.includes('arruela'); + } else if (buscaP === 'redondo') { + bateuPerfil = perfilNorm.includes('red') || perfilNorm.includes('ferro') || perfilNorm.includes('barra') || perfilNorm.includes('ø') || descNorm.includes('chumb'); + } else if (buscaP === 'tubo') { + bateuPerfil = perfilNorm.includes('tubo') || perfilNorm.includes('tubular') || descNorm.includes('tubo'); + } 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(); - const match = p.marca.toLowerCase().includes(t) || p.descricao?.toLowerCase().includes(t); + 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; } - // Filtro status + // 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.pintura < p.quantidade; + if (filtroStatus === 'fabricando') return (p.corte > 0 || p.detalhamento > 0) && p.pintura < p.quantidade; return true; }); - }, [pecasStatus, busca, filtroStatus]); + }, [pecasStatus, busca, filtroFase, filtroPerfilRapido, filtroStatus]); - // Status visual de cada etapa + // 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-400', label: 'OK' }; + return { icon: , cor: 'text-emerald-400', label: `${concluido}` }; } if (concluido > 0) { return { icon: , cor: 'text-amber-400', label: `${concluido}/${total}` }; @@ -198,16 +311,15 @@ export const SmartConsultaRelatoriosFlow: React.FC { try { setGerandoPDF(true); const hoje = new Date().toISOString().split('T')[0]; - // Buscar apontamentos feitos hoje pelo operador autenticado const { data: meusApontamentos, error } = await supabase .from('apontamentos_producao') .select(` @@ -235,10 +347,9 @@ export const SmartConsultaRelatoriosFlow: React.FC [ p.marca, - p.etapa_fase || 'Geral', + p.perfil_principal || p.descricao, + p.etapa_fase || '1', p.quantidade.toString(), + p.detalhamento.toString(), p.corte.toString(), p.montagem.toString(), p.solda.toString(), @@ -363,7 +473,7 @@ export const SmartConsultaRelatoriosFlow: React.FC +
{/* Navegação entre Abas: Consulta Rápida vs Relatórios */} -
+
)}
- {/* Pílulas de Filtro Rápido */} -
+ {/* FILTRO 1: Pílulas Rápidas de Perfis / Materiais Comuns (W, L, Chapa, Redondo, Tubo) */} +
+ + PERFIL: + {[ - { id: 'todos', label: 'Todos' }, - { id: 'fabricando', label: 'Fabricando' }, - { id: 'pronto', label: 'Pronto Pátio' }, - { id: 'embarcado', label: 'Embarcado' }, - { id: 'montado', label: 'Montado' }, - ].map((f) => ( + { id: 'w', label: 'W / I' }, + { id: 'l', label: 'L (Cantoneira)' }, + { id: 'chapa', label: 'Chapa' }, + { id: 'redondo', label: 'Barra / Redondo' }, + { id: 'tubo', label: 'Tubo' }, + ].map((p) => { + const ativo = filtroPerfilRapido === p.id; + return ( + + ); + })} +
+ + {/* FILTRO 2: Fases da Obra */} +
+ + FASE: + + + {fasesUnicas.map((fase) => ( ))}
- {/* Lista de Peças com Linha do Tempo Visual */} -
+ {/* FILTRO 3: Status Fabril e Botão Limpar */} +
+
+ {[ + { id: 'todos', label: 'Todos' }, + { id: 'fabricando', label: 'Em Fabricação' }, + { id: 'pronto', label: 'Pronto Pátio' }, + { id: 'embarcado', label: 'Embarcado' }, + { id: 'montado', label: 'Montado' }, + ].map((f) => ( + + ))} +
+ + {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 timeline das peças... + Carregando peças e apontamentos...
) : pecasFiltradas.length === 0 ? (

Nenhuma peça encontrada

-

Verifique o termo de busca ou filtro

+

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

+
) : ( pecasFiltradas.map((peca) => (
-
-
- - {peca.marca} - - - {peca.etapa_fase || 'Estrutural'} + {/* Linha Superior: Marca, Perfil, Fase e Quantidade */} +
+
+
+ + {peca.marca} + + + {peca.perfil_principal && ( + + {peca.perfil_principal} + + )} + + + Fase {peca.etapa_fase} + + + {peca.material && ( + + {peca.material} + + )} +
+ +
+ {peca.descricao || 'Peça Estrutural'} +
+
+ +
+ Total + + {peca.quantidade}{' '} + un
- - Total: {peca.quantidade} un -
-
- {peca.descricao || 'Peça Estrutural'} -
- - {/* Linha do Tempo Visual da Peça */} -
+ {/* Linha do Tempo Visual com TODAS as Etapas Fabris */} +
{[ + { nome: 'Det.', valor: peca.detalhamento }, { nome: 'Corte', valor: peca.corte }, { nome: 'Mont.', valor: peca.montagem }, { nome: 'Solda', valor: peca.solda }, @@ -523,10 +762,10 @@ export const SmartConsultaRelatoriosFlow: React.FC -
+
{etapa.nome}
-
+
{badge.icon}
@@ -649,7 +888,7 @@ export const SmartConsultaRelatoriosFlow: React.FC ⬅️ Voltar ao Menu Principal diff --git a/src/pages/smart/components/SmartProducaoFlow.tsx b/src/pages/smart/components/SmartProducaoFlow.tsx index ca4090d..bd8fb2d 100644 --- a/src/pages/smart/components/SmartProducaoFlow.tsx +++ b/src/pages/smart/components/SmartProducaoFlow.tsx @@ -149,7 +149,7 @@ export const SmartProducaoFlow: React.FC = ({ // Colocar primeiro as que têm saldo pendente if (a.saldo > 0 && b.saldo === 0) return -1; if (a.saldo === 0 && b.saldo > 0) return 1; - return a.marca.localeCompare(b.marca); + return a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }); }); }, [pecas, faseSelecionada, processoSelecionado, mapaProducao]);