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 [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 loadLocal = async () => { const localData = await dbService.initData(); setData(prev => ({ ...prev, ...localData })); }; loadLocal(); }, []); // 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) { setSyncStatus('saved'); } else if (result.reason === 'newer_version') { setSyncStatus('conflict'); } 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]); useEffect(() => { if (isCloudEnabled) { console.log("📡 Iniciando escuta em tempo real para school_data..."); // Cria um canal de escuta para a tabela school_data const channel = supabase .channel('school_data_changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'school_data', filter: 'id=eq.1' }, (payload) => { // Quando houver um UPDATE (ex: Portal enviou justificativa) const newData = payload.new.data as SchoolData; // Só atualiza se for uma mudança externa (evita loops) if (newData.lastUpdated !== dataRef.current.lastUpdated) { console.log("🔔 Nova mudança externa detectada em tempo real!"); setData(newData); dbService.saveData(newData); // Sincroniza cache local } } ) .subscribe((status) => { console.log("🔌 Status da conexão Realtime:", status); }); return () => { console.log("⚰️ Encerrando canal de Realtime"); supabase.removeChannel(channel); }; } }, [isCloudEnabled]); 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 (!isAuthenticated) { return { setCurrentUser(user); setIsAuthenticated(true); }} onUpdateUsers={handleUpdateUsers} />; } 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( );