import React, { useEffect, useState, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { smartAudio } from '@/utils/smartAudio'; import { Bell, X, CheckCircle2, User, Clock, Layers, RefreshCw, Flame, Wrench, Paintbrush, Truck, Scissors, Box } from 'lucide-react'; export interface ApontamentoNotificacao { id: string; of_number: string; quantidade: number; data_apontamento: string; created_at: string; marca: string; fase: string; perfil?: string; processo_nome: string; processo_cor: string; usuario_nome: string; } interface SmartNotificationsModalProps { isOpen: boolean; onClose: () => void; ofFiltrar?: string; } export const SmartNotificationsModal: React.FC = ({ isOpen, onClose, ofFiltrar, }) => { const [apontamentos, setApontamentos] = useState([]); const [carregando, setCarregando] = useState(false); const formatarTempo = (dataIso?: string) => { if (!dataIso) return 'Recente'; try { const data = new Date(dataIso); const agora = new Date(); const diffMs = agora.getTime() - data.getTime(); const diffMin = Math.floor(diffMs / (1000 * 60)); const diffHoras = Math.floor(diffMin / 60); if (diffMin < 1) return 'Agora mesmo'; if (diffMin < 60) return `há ${diffMin} min`; if (diffHoras < 24) return `há ${diffHoras}h`; return data.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch { return 'Recente'; } }; const getProcessIcon = (processoNome: string) => { const norm = (processoNome || '').toLowerCase(); if (norm.includes('corte')) return ; if (norm.includes('solda')) return ; if (norm.includes('pintu') || norm.includes('galv')) return ; if (norm.includes('exped')) return ; if (norm.includes('montag')) return ; return ; }; const carregarNotificacoes = useCallback(async () => { setCarregando(true); try { // 1. Buscar os últimos 30 apontamentos let query = supabase .from('apontamentos_producao' as any) .select(` id, of_number, quantidade_produzida, data_apontamento, created_at, created_by, peca:pecas!apontamentos_producao_peca_id_fkey(marca, etapa_fase, perfil_principal, descricao), processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, cor, ordem) `) .order('created_at', { ascending: false }) .limit(30); if (ofFiltrar) { query = query.eq('of_number', ofFiltrar); } const { data: apontamentosData, error } = await query; if (error) throw error; // 2. Buscar nomes dos usuários dos apontamentos const userIds = Array.from(new Set((apontamentosData || []).map((ap: any) => ap.created_by).filter(Boolean))); const profilesMap = new Map(); if (userIds.length > 0) { try { const { data: profs } = await supabase .from('profiles' as any) .select('id, full_name, email') .in('id', userIds); if (profs) { profs.forEach((pr: any) => { const nome = pr.full_name || pr.email?.split('@')[0] || 'Operador'; profilesMap.set(pr.id, nome); }); } } catch (e) { console.debug('Erro ao buscar perfis:', e); } } // 3. Montar lista tratada const lista: ApontamentoNotificacao[] = (apontamentosData || []).map((ap: any) => { const peca = ap.peca || {}; const processo = ap.processo || {}; const userName = (ap.created_by && profilesMap.get(ap.created_by)) || 'Chão de Fábrica'; return { id: String(ap.id), of_number: ap.of_number || 'OF', quantidade: Number(ap.quantidade_produzida || 1), data_apontamento: ap.data_apontamento, created_at: ap.created_at || ap.data_apontamento, marca: peca.marca || 'Sem Marca', fase: peca.etapa_fase || '1', perfil: peca.perfil_principal || peca.descricao || '', processo_nome: processo.nome || 'Produção', processo_cor: processo.cor || '#10b981', usuario_nome: userName, }; }); setApontamentos(lista); } catch (err) { console.error('Erro ao carregar notificações de produção:', err); } finally { setCarregando(false); } }, [ofFiltrar]); useEffect(() => { if (isOpen) { carregarNotificacoes(); } }, [isOpen, carregarNotificacoes]); if (!isOpen) return null; return (
e.stopPropagation()} > {/* Cabeçalho do Modal */}

Apontamentos Recentes {ofFiltrar && ( OF {ofFiltrar} )}

Histórico em tempo real das peças produzidas

{/* Lista de Notificações de Apontamentos */}
{carregando && apontamentos.length === 0 ? (
Buscando novos apontamentos...
) : apontamentos.length === 0 ? (

Nenhum apontamento registrado recentemente{ofFiltrar ? ` para a OF ${ofFiltrar}` : ''}.

) : ( apontamentos.map((item) => (
{/* Lado Esquerdo: Marca, Detalhes e Usuário */}
{getProcessIcon(item.processo_nome)}
{item.marca} OF {item.of_number} • F{item.fase} {item.processo_nome}
{item.perfil && (

{item.perfil}

)}
{item.usuario_nome} {formatarTempo(item.created_at)}
{/* Lado Direito: Quantidade Apontada */}
+{item.quantidade} un
)) )}
{/* Rodapé */}
{apontamentos.length} apontamentos listados
); }; // Hook reativo para alertar e contar novos apontamentos no sininho export const useSmartNotificationsAlert = (ofFiltrar?: string) => { const [unreadCount, setUnreadCount] = useState(0); const [hasNewAlert, setHasNewAlert] = useState(false); useEffect(() => { // 1. Busca contagem de apontamentos de hoje / recentes const fetchRecentCount = async () => { try { const hoje = new Date(); hoje.setHours(0, 0, 0, 0); let query = supabase .from('apontamentos_producao' as any) .select('id', { count: 'exact', head: true }) .gte('created_at', hoje.toISOString()); if (ofFiltrar) { query = query.eq('of_number', ofFiltrar); } const { count, error } = await query; if (!error && count !== null) { setUnreadCount(count); if (count > 0) setHasNewAlert(true); } } catch (err) { console.debug('Erro ao contar apontamentos:', err); } }; fetchRecentCount(); // 2. Realtime listener para alertar quando novos apontamentos forem inseridos const channelName = `realtime-smart-notifs-${ofFiltrar || 'all'}-${Math.random().toString(36).substring(2, 7)}`; const channel = supabase .channel(channelName) .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'apontamentos_producao', }, (payload) => { const newRow = payload.new as any; if (!ofFiltrar || newRow?.of_number === ofFiltrar) { setUnreadCount((c) => c + 1); setHasNewAlert(true); try { smartAudio.playSuccess(); } catch { // ignore } } } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [ofFiltrar]); const clearAlert = useCallback(() => { setHasNewAlert(false); }, []); return { unreadCount, hasNewAlert, clearAlert }; };