import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { License, User, AppSettings } from './types'; import Timeline from './components/Timeline'; import LicenseList from './components/LicenseList'; import LicenseForm from './components/LicenseForm'; import Header from './components/Header'; import LoginForm from './components/LoginForm'; import UserManagement from './components/UserManagement'; import AdminSettings from './components/AdminSettings'; import AuditLogs from './components/AuditLogs'; import DashboardAlerts from './components/DashboardAlerts'; import { PlusIcon, TrashIcon } from './components/Icons'; import { fetchLicenses, saveLicense, deleteLicense, exportDatabase, importDatabase, checkSession, logout, fetchSettings, DEFAULT_SETTINGS, runNotifications } from './graphService'; const App: React.FC = () => { const [licenses, setLicenses] = useState([]); const [currentUser, setCurrentUser] = useState(null); const [appSettings, setAppSettings] = useState(DEFAULT_SETTINGS); const [currentView, setCurrentView] = useState('dashboard'); const [isModalOpen, setIsModalOpen] = useState(false); const [showLoginModal, setShowLoginModal] = useState(false); const [editingLicense, setEditingLicense] = useState(null); const [timelineDate, setTimelineDate] = useState(new Date()); const [searchQuery, setSearchQuery] = useState(''); const [selectedTags, setSelectedTags] = useState([]); const [sortBy, setSortBy] = useState<'name' | 'price_desc' | 'price_asc' | 'end_soon'>('name'); const [showArchived, setShowArchived] = useState(false); const [isLoading, setIsLoading] = useState(true); const initApp = useCallback(async () => { setIsLoading(true); try { // Trigger background notification checks runNotifications().catch(e => console.debug("Notifications silent run:", e)); const [settings, session, data] = await Promise.all([ fetchSettings().catch(() => DEFAULT_SETTINGS), checkSession().catch(() => ({ authenticated: false })), fetchLicenses().catch(() => []) ]); if (settings) { setAppSettings({ ...DEFAULT_SETTINGS, ...settings }); } if (session?.authenticated) setCurrentUser(session.user); setLicenses(Array.isArray(data) ? data : []); } catch (e) { console.error("Init Error", e); setLicenses([]); } finally { setIsLoading(false); } }, []); useEffect(() => { initApp(); }, [initApp]); const handleFormSubmit = async (licenseData: any) => { try { await saveLicense(licenseData); const freshData = await fetchLicenses(); setLicenses(Array.isArray(freshData) ? freshData : []); setIsModalOpen(false); setEditingLicense(null); } catch (err: any) { alert("Save Failed: " + err.message); } }; const handleImport = async (e: React.ChangeEvent) => { if(e.target.files?.[0]) { try { const text = await e.target.files[0].text(); const res = await importDatabase(text); // CRITICAL: Clear all UI filters before refreshing data setSearchQuery(''); setSelectedTags([]); setShowArchived(false); await initApp(); const count = res.count || 'all'; alert(`Restore Complete. ${count} licenses were successfully recovered to the repository.`); } catch (err: any) { alert("Restore failed: " + err.message); } finally { e.target.value = ''; } } }; const allTags = useMemo(() => { const tags = new Set(); if (Array.isArray(licenses)) { licenses.forEach(l => l.tags?.forEach(t => tags.add(t))); } return Array.from(tags).sort(); }, [licenses]); const toggleTag = (tag: string) => { setSelectedTags(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] ); }; const filteredAndSorted = useMemo(() => { const safeLicenses = Array.isArray(licenses) ? licenses : []; let filtered = safeLicenses.filter(l => { if (!l) return false; const statusMatch = showArchived ? !l.isActive : l.isActive; if (!statusMatch) return false; if (selectedTags.length > 0) { const hasAllTags = selectedTags.every(tag => l.tags?.includes(tag)); if (!hasAllTags) return false; } const search = searchQuery.toLowerCase(); return !search || (l.licenseName && l.licenseName.toLowerCase().includes(search)) || (l.companyName && l.companyName.toLowerCase().includes(search)); }); return filtered.sort((a, b) => { if (sortBy === 'price_desc') return b.purchasePrice - a.purchasePrice; if (sortBy === 'price_asc') return a.purchasePrice - b.purchasePrice; if (sortBy === 'end_soon') { const da = a.endDate ? new Date(a.endDate).getTime() : Infinity; const db = b.endDate ? new Date(b.endDate).getTime() : Infinity; return da - db; } return (a.licenseName || '').localeCompare(b.licenseName || ''); }); }, [licenses, searchQuery, showArchived, selectedTags, sortBy]); return (
{ await logout(); setCurrentUser(null); initApp(); }} onLoginClick={() => setShowLoginModal(true)} onExport={async () => { try { const j = await exportDatabase(); const jsonString = JSON.stringify(j, null, 2); const b = new Blob([jsonString], {type:'application/json'}); const u = URL.createObjectURL(b); const a = document.createElement('a'); a.href = u; a.download = `licenses_backup_${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(u); } catch (err: any) { alert("Export failed: " + err.message); } }} onImport={handleImport} currentView={currentView} onChangeView={setCurrentView} /> {currentView === 'users' ? : currentView === 'logs' ? : currentView === 'settings' ? : (
setTimelineDate(d => { const n = new Date(d); n.setFullYear(d.getFullYear() + (dir === 'next' ? 1 : -1)); return n; })} fiscalStartMonth={appSettings.fiscal_start_month} />

{showArchived ? 'Archive' : 'Active Inventory'}

{currentUser && ( )}
setSearchQuery(e.target.value)} className="w-full bg-slate-900 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:border-cyan-500 outline-none transition-all shadow-inner" />
{selectedTags.length > 0 && ( )}
{allTags.length === 0 ? ( No tags available in current inventory. ) : ( allTags.map(tag => { const isActive = selectedTags.includes(tag); return ( ); }) )}
{isLoading ? (
LOADING REPOSITORY...
) : ( { setEditingLicense(l); setIsModalOpen(true); }} onDelete={async (id) => { if(confirm('Delete?')) { await deleteLicense(id); initApp(); } }} isReadOnly={!currentUser} /> )}
)}
setIsModalOpen(false)} onSubmit={handleFormSubmit} initialData={editingLicense} /> {showLoginModal && { setCurrentUser(u); setShowLoginModal(false); initApp(); }} onCancel={() => setShowLoginModal(false)} />}
); }; export default App;