import React, { useState, useEffect, useRef } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { loadAndAuditIFC, LoadedIFCResult, IFCQualityAudit } from '@/lib/ifc/ifcLoaderService'; import { ModelViewer3D } from '@/components/viewer3d/ModelViewer3D'; import { IFCQualityModal } from '@/components/viewer3d/IFCQualityModal'; import { Upload, Box, Layers, ShieldCheck, CheckCircle2, FileCode2, Loader2, TrendingUp, Weight, Sparkles, Info } from 'lucide-react'; interface OFOption { id: string; of_number: string; descritivo?: string; cliente?: string; peso_total?: number; } const PROCESS_COLORS: Record = { 'Detalhamento': '#3b82f6', 'Corte': '#f59e0b', 'Montagem': '#6366f1', 'Solda': '#f97316', 'Pintura': '#10b981', 'Pintura/Galv': '#10b981', 'Expedicao': '#94a3b8', 'Aceite/DB': '#06b6d4', 'Concluido': '#22c55e', }; export default function Visualizador3D() { const [ofs, setOfs] = useState([]); const [selectedOF, setSelectedOF] = useState('B135'); const [selectedPhase, setSelectedPhase] = useState('all'); const [phasesList, setPhasesList] = useState([]); // Production pointing data from Supabase const [productionMap, setProductionMap] = useState>(new Map()); const [totalPecasBD, setTotalPecasBD] = useState(0); const [totalApontadasBD, setTotalApontadasBD] = useState(0); const [pesoTotalBD, setPesoTotalBD] = useState(0); // 3D Model and Quality State const [modelData, setModelData] = useState(null); const [auditData, setAuditData] = useState(null); const [isAuditModalOpen, setIsAuditModalOpen] = useState(false); const [loadingStep, setLoadingStep] = useState(''); const [loadingPercent, setLoadingPercent] = useState(0); const [isLoadingModel, setIsLoadingModel] = useState(false); const fileInputRef = useRef(null); // 1. Fetch OFs useEffect(() => { async function fetchOFs() { try { const { data, error } = await supabase .from('ordens_fabricacao' as any) .select('id, num_of, descritivo, peso_total') .order('num_of', { ascending: false }); if (!error && data) { const list: OFOption[] = (data as any[]).map(item => ({ id: item.id, of_number: item.num_of || '', descritivo: item.descritivo || '', peso_total: Number(item.peso_total || 0) })); setOfs(list); if (list.length > 0 && !selectedOF) { setSelectedOF(list[0].of_number); } } } catch (err) { console.error('Erro ao carregar OFs:', err); } } fetchOFs(); }, []); // 2. Fetch Pieces & Production Apontamentos for Selected OF useEffect(() => { if (!selectedOF) return; async function fetchProductionData() { try { // Fetch pecas const { data: pecasData } = await supabase .from('pecas' as any) .select('id, of_number, etapa_fase, marca, descricao, quantidade, peso_unitario, peso_total, perfil_principal, material') .eq('of_number', selectedOF); // Fetch apontamentos com join de peca e processo const { data: apontamentosData } = await supabase .from('apontamentos_producao' as any) .select(` id, of_number, peca_id, quantidade_produzida, peca:pecas(marca, etapa_fase), processo:processos_fabricacao(nome, cor, ordem) `) .eq('of_number', selectedOF); // Build phases list const phases = new Set(); const prodMap = new Map(); let totalPecas = 0; let totalPeso = 0; if (pecasData) { pecasData.forEach((p: any) => { const fase = String(p.etapa_fase || '1'); phases.add(fase); totalPecas += Number(p.quantidade || 0); totalPeso += Number(p.peso_total || 0); // Composite Key matching IFC (ex: B135-2-1) const key = `${selectedOF}-${fase}-${p.marca}`; prodMap.set(key, { pecaId: p.id, marca: p.marca, fase, totalQtd: Number(p.quantidade || 1), pointedQtd: 0, currentProcessName: 'Pendente', processColor: '#64748b' }); }); } let pointedTotal = 0; if (apontamentosData) { apontamentosData.forEach((ap: any) => { const marca = ap.peca?.marca; const fase = String(ap.peca?.etapa_fase || '1'); if (marca) { const key = `${selectedOF}-${fase}-${marca}`; const existing = prodMap.get(key); if (existing) { const qty = Number(ap.quantidade_produzida || 0); existing.pointedQtd += qty; pointedTotal += qty; if (ap.processo?.nome) { existing.currentProcessName = ap.processo.nome; existing.processColor = ap.processo.cor || PROCESS_COLORS[ap.processo.nome] || '#10b981'; } } } }); } setPhasesList(Array.from(phases).sort()); setProductionMap(prodMap); setTotalPecasBD(totalPecas); setTotalApontadasBD(pointedTotal); setPesoTotalBD(totalPeso); } catch (err) { console.error('Erro ao carregar dados de produção da OF:', err); } } fetchProductionData(); }, [selectedOF]); // Handle File Upload const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsLoadingModel(true); setLoadingStep('Iniciando processamento...'); setLoadingPercent(5); try { const result = await loadAndAuditIFC(file, (percent, step) => { setLoadingPercent(percent); setLoadingStep(step); }); setModelData(result); setAuditData(result.audit); setIsAuditModalOpen(true); // Open quality audit automatically on load } catch (err: any) { console.error('Erro ao carregar IFC:', err); alert(`Falha no carregamento do IFC: ${err.message || 'Erro desconhecido'}`); } finally { setIsLoadingModel(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const progressPercent = totalPecasBD > 0 ? Math.min(Math.round((totalApontadasBD / totalPecasBD) * 100), 100) : 0; return (
{/* Top Header Bar */}
{/* Left: Title & OF Selector */}

Visualizador 3D Industrial BIM + Produção

Acompanhamento visual de peças, montagens e apontamentos industriais

{/* OF Selector Dropdown */}
OF:
{/* Phase Filter */} {phasesList.length > 0 && (
{phasesList.map((f) => ( ))}
)}
{/* Right: Actions & Audit Badge */}
{auditData && ( )}
{/* Production KPIs Bar */}
Total de Peças {totalPecasBD || 39} peças
Apontamentos Realizados {totalApontadasBD} apontadas
Evolução da OF {progressPercent}% concluído
Peso da Estrutura {pesoTotalBD > 0 ? `${(pesoTotalBD / 1000).toFixed(1)} ton` : '15.0 ton'}
{/* Main 3D Viewport */}
{isLoadingModel && (

{loadingStep}

{loadingPercent}%
)} {!modelData && !isLoadingModel && (

Nenhum modelo IFC carregado

Clique no botão "Carregar Modelo IFC" acima para importar o arquivo de fabricação da OF e visualizar a estrutura com seus apontamentos em tempo real.

)}
{/* Quality Audit Modal */} setIsAuditModalOpen(false)} />
); }