import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; import { View, SchoolData, User } from './types'; import { dbService } from './services/dbService'; import Sidebar from './components/Sidebar'; import Dashboard from './components/Dashboard'; import Students from './components/Students'; import Classes from './components/Classes'; import Courses from './components/Courses'; import Finance from './components/Finance'; import Settings from './components/Settings'; import Contracts from './components/Contracts'; import Certificates from './components/Certificates'; import AttendanceCapture from './components/AttendanceCapture'; import AttendanceQuery from './components/AttendanceQuery'; import ReportCard from './components/ReportCard'; import Auth from './components/Auth'; import UserManagement from './components/UserManagement'; import Handouts from './components/Handouts'; import Employees from './components/Employees'; import Messages from './components/Messages'; import AdminNotifications from './components/AdminNotifications'; import Exams from './components/Exams'; import { Cloud, CloudOff, RefreshCw, AlertCircle } from 'lucide-react'; import { supabase, isSupabaseConfigured } from './services/supabase'; import { DialogProvider } from './DialogContext'; const App = () => { const [isAuthenticated, setIsAuthenticated] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [isCheckingAuth, setIsCheckingAuth] = useState(true); const [currentView, setCurrentView] = useState(View.Dashboard); const [deepLinkStudentId, setDeepLinkStudentId] = useState(null); const [deepLinkClassId, setDeepLinkClassId] = useState(null); // Initial load from LocalStorage for speed (fallback), then IDB const [data, setData] = useState(dbService.getData()); // Sync Status const [syncStatus, setSyncStatus] = useState<'idle' | 'syncing' | 'saved' | 'error' | 'conflict'>('idle'); const [isCloudEnabled, setIsCloudEnabled] = useState(false); const saveTimeoutRef = useRef | null>(null); // 0. Load from IndexedDB on mount useEffect(() => { const loadSessionAndData = async () => { try { const savedSession = localStorage.getItem('edumanager_session'); if (savedSession) { const user = JSON.parse(savedSession); setCurrentUser(user); setIsAuthenticated(true); } const localData = await dbService.initData(); setData(prev => ({ ...prev, ...localData })); } catch (e) { console.error("Erro ao carregar sessão:", e); } finally { setIsCheckingAuth(false); } }; loadSessionAndData(); }, []); // 1. Initial Cloud Fetch (Sync on Load) useEffect(() => { const initCloud = async () => { if (isSupabaseConfigured()) { setSyncStatus('syncing'); const cloudData = await dbService.fetchFromCloud(); if (cloudData) { // If cloud data exists, it takes precedence. setData(cloudData); dbService.saveData(cloudData); // Update local cache setSyncStatus('saved'); } else { // If no cloud data, we might be starting fresh setSyncStatus('idle'); } // Only enable cloud saving AFTER the initial fetch is attempted setIsCloudEnabled(true); } }; initCloud(); }, []); // 2. Save Data Effect (Local + Debounced Cloud) useEffect(() => { // Immediate Local Save dbService.saveData(data); // Debounced Cloud Save if (isCloudEnabled) { setSyncStatus('syncing'); if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current); saveTimeoutRef.current = setTimeout(async () => { try { const result = await dbService.saveToCloud(data); if (result.success && result.lastUpdated) { // Sincroniza o timestamp local com o do servidor para evitar conflitos no polling setData(prev => ({ ...prev, lastUpdated: result.lastUpdated })); setSyncStatus('saved'); } else if (result.reason === 'newer_version') { setSyncStatus('conflict'); console.warn("⚠️ Conflito de versão detectado. Sincronizando com os dados mais recentes do servidor..."); forceSyncFromCloud(); } else { setSyncStatus('error'); } } catch (e) { setSyncStatus('error'); } }, 2000); // Save to cloud 2 seconds after last change } return () => { if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current); }; }, [data, isCloudEnabled]); // 3. Dynamic Favicon useEffect(() => { const logoUrl = data.logo; if (logoUrl) { let link = document.querySelector("link[rel~='icon']") as HTMLLinkElement; if (!link) { link = document.createElement('link'); link.type = 'image/x-icon'; link.rel = 'icon'; document.getElementsByTagName('head')[0].appendChild(link); } link.href = logoUrl; } }, [data.logo]); // 4. Efeito para Realtime (Escuta mudanças do Portal em tempo real) const dataRef = useRef(data); useEffect(() => { dataRef.current = data; }, [data]); // 4. Polling Inteligente (Substitui o Realtime do Supabase no ambiente Self-Hosted) // Verifica mudanças no servidor a cada 30 segundos useEffect(() => { const pollInterval = setInterval(async () => { // Pequena validação para não interromper ações do usuário if (syncStatus === 'syncing') return; try { const cloudData = await dbService.fetchFromCloud(); if (cloudData && cloudData.lastUpdated && dataRef.current.lastUpdated) { const cloudTime = new Date(cloudData.lastUpdated).getTime(); const localTime = new Date(dataRef.current.lastUpdated).getTime(); // Regra crucial: Só substitui o estado local se o servidor tiver dados ESTRITAMENTE mais novos. // Isso impede que verificações durante o "debounce" (espera de 2 segs) sobrescrevam o estado // local com dados velhos do servidor, fazendo itens recém-criados "sumirem". if (cloudTime > localTime) { console.log("🔔 Polling: Novos dados detectados no servidor!"); setData(cloudData); dbService.saveData(cloudData); setSyncStatus('saved'); } } } catch (e) { // Silencioso em caso de erro de rede temporário } }, 30000); // 30 segundos return () => clearInterval(pollInterval); }, [syncStatus]); const updateData = (newData: Partial) => { setData(prev => ({ ...prev, ...newData, lastUpdated: new Date().toISOString() })); }; const handleUpdateUsers = (newUsers: User[]) => { updateData({ users: newUsers }); }; const forceSyncFromCloud = async () => { setSyncStatus('syncing'); const cloudData = await dbService.fetchFromCloud(); if (cloudData) { setData(cloudData); dbService.saveData(cloudData); setSyncStatus('saved'); } else { setSyncStatus('error'); } }; const handleNavigateToStudent = (studentId: string) => { setDeepLinkStudentId(studentId); setCurrentView(View.AttendanceQuery); }; const handleNavigateToClass = (classId: string, studentId?: string) => { setDeepLinkClassId(classId); setDeepLinkStudentId(studentId || null); setCurrentView(View.Students); }; const renderView = () => { switch (currentView) { case View.Dashboard: return ; case View.Courses: return ; case View.Students: return { setDeepLinkStudentId(null); setDeepLinkClassId(null); }} />; case View.Classes: return ; case View.Finance: return ; case View.Contracts: return ; case View.Certificates: return ; case View.Attendance: return ; case View.AttendanceQuery: return setDeepLinkStudentId(null)} />; case View.ReportCard: return ; case View.Handouts: return ; case View.Exams: return ; case View.Employees: return ; case View.Users: return ; case View.Messages: return ; case View.Settings: return ; default: return ; } }; if (isCheckingAuth) { return (
); } if (!isAuthenticated) { return { localStorage.setItem('edumanager_session', JSON.stringify(user)); setCurrentUser(user); setIsAuthenticated(true); }} onUpdateUsers={handleUpdateUsers} />; } const handleLogout = () => { localStorage.removeItem('edumanager_session'); setIsAuthenticated(false); setCurrentUser(null); setCurrentView(View.Dashboard); }; return (
{/* Sync Indicator - Green Strip on the Right */} {syncStatus === 'syncing' && (
Sincronizando
)} {/* Conflict Alert - Only show when there is a version mismatch */} {syncStatus === 'conflict' && (
)}
{renderView()}
); }; const root = createRoot(document.getElementById('root')!); root.render( );