// App shell: sidebar (desktop) + bottom nav (mobile) + vues // ── Sidebar (desktop uniquement) ───────────────────── function Sidebar({ view, setView, items, openForm, user, onLogout, syncError }) { const userInfo = USERS[user] || { name: user, color: 'var(--terracotta)', avatar: (user||'?')[0].toUpperCase() }; const today = TODAY; const todayCount = itemsOnDate(items, today).filter(x => x.kind === 'task' && ['todo','progress'].includes(statusOn(x, today))).length; const overdue = items.filter(x => x.kind === 'task' && (x.recurrence?.kind || 'none') === 'none' && isBefore(fromYmd(x.date), today) && !['done','cancelled'].includes(x.status)).length; const eventsCount = items.filter(x => x.kind !== 'task').length; const NavItem = ({ id, icon, label, badge, badgeColor }) => { const active = view === id; return ( ); }; return ( ); } // ── Bottom navigation (mobile uniquement) ───────────── function BottomNav({ view, setView, items }) { const today = TODAY; const todayCount = itemsOnDate(items, today).filter(x => x.kind === 'task' && ['todo','progress'].includes(statusOn(x, today))).length; const overdueCount = items.filter(x => x.kind === 'task' && (x.recurrence?.kind || 'none') === 'none' && isBefore(fromYmd(x.date), today) && !['done','cancelled'].includes(x.status)).length; const eventsCount = items.filter(x => x.kind !== 'task').length; const tabs = [ { id: 'dashboard', icon: 'sparkle', label: 'Accueil', badge: todayCount, badgeColor: 'var(--terracotta)' }, { id: 'calendar', icon: 'calendar', label: 'Calendrier' }, { id: 'tasks', icon: 'list', label: 'Tâches' }, { id: 'overdue', icon: 'bell', label: 'En retard', badge: overdueCount, badgeColor: 'var(--prio-high)' }, { id: 'events', icon: 'cake', label: 'Événements', badge: eventsCount, badgeColor: 'var(--event)' }, ]; return ( ); } // ── App principal ───────────────────────────────────── function App() { const { user, login, logout, loading } = useAuthSB(); const { items, upsert, remove, setStatusOn, syncError, reload } = useStoreSB(); const [view, setView] = React.useState('dashboard'); const [formOpen, setFormOpen] = React.useState(false); const [editing, setEditing] = React.useState(null); const openForm = (initial) => { setEditing(initial || null); setFormOpen(true); }; const onEdit = (item) => { setEditing(item); setFormOpen(true); }; // Recharge les items dès que l'utilisateur est connecté React.useEffect(() => { if (user) reload(); }, [user]); const handleLogin = async (key) => { await login(key); }; if (loading) return (
Chargement…
); if (!user) return ; const overdueItems = items.filter(x => x.kind === 'task' && (x.recurrence?.kind || 'none') === 'none' && isBefore(fromYmd(x.date), TODAY) && !['done','cancelled'].includes(x.status) ); return (
{view === 'dashboard' && } {view === 'calendar' && } {view === 'tasks' && } {view === 'overdue' && } {view === 'events' && }
{syncError && (
Erreur de synchro : {syncError}
)} setFormOpen(false)}> setFormOpen(false)} />
); } // ── Vues secondaires ────────────────────────────────── function OverdueView({ items, onStatusChange, onEdit, openForm }) { const sorted = [...items].sort((a, b) => a.date.localeCompare(b.date)); return (
{sorted.length === 0 ? ( ) : (
{sorted.map(it => )}
)}
); } function EventsView({ items, onStatusChange, onEdit, openForm }) { const events = items.filter(x => x.kind !== 'task'); const today = TODAY; const occ = events.map(it => ({ item: it, date: fromYmd(it.date) })).sort((a, b) => a.date - b.date); const upcoming = occ.filter(({ date }) => !isBefore(date, today)); const past = occ.filter(({ date }) => isBefore(date, today)); const Section = ({ title, list }) => (
{list.length === 0 ?
Aucun événement
:
{list.map(({ item, date }) => )}
}
); return (
openForm({ kind: 'appointment' })}>Nouvel événement} />
); } ReactDOM.createRoot(document.getElementById('root')).render();