Panel admin, planning, tags, suivi du temps, thèmes clair/sombre

- Admin: création/suppression de comptes + rôles via Edge Function sécurisée
- Config Supabase intégrée au build (.env), écran Setup supprimé
- Page Planning (vue mois/semaine)
- Tags "jobs" sur les tâches + filtres (membre / tag)
- Suivi du temps par tâche, total par jalon
- Toasts + confirmations in-app (fini alert/confirm natifs)
- Thème Notion clair + mode sombre (accent monochrome, bascule persistée)
- .env suivi par git (public uniquement) pour synchro multi-machines

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 09:51:40 +02:00
parent 0806be135d
commit b054e36df0
30 changed files with 1991 additions and 411 deletions
+45 -17
View File
@@ -10,21 +10,35 @@ import {
ChevronDown,
LogOut,
Settings,
Gamepad2
Gamepad2,
ShieldCheck,
CalendarRange,
Sun,
Moon
} from 'lucide-react'
import { useApp } from '../lib/AppContext'
import { supa } from '../lib/supabase'
import { ROLE_LABELS } from '../lib/types'
import { Theme, getTheme, setTheme } from '../lib/theme'
import { Avatar, Modal } from './ui'
export type Page = 'dashboard' | 'kanban' | 'milestones' | 'bugs' | 'members'
export type Page =
| 'dashboard'
| 'kanban'
| 'planning'
| 'milestones'
| 'bugs'
| 'members'
| 'admin'
const NAV: { id: Page; label: string; icon: typeof LayoutDashboard }[] = [
const NAV: { id: Page; label: string; icon: typeof LayoutDashboard; adminOnly?: boolean }[] = [
{ id: 'dashboard', label: 'Tableau de bord', icon: LayoutDashboard },
{ id: 'kanban', label: 'Tâches (Kanban)', icon: KanbanSquare },
{ id: 'milestones', label: 'Jalons & planning', icon: Target },
{ id: 'planning', label: 'Planning', icon: CalendarRange },
{ id: 'milestones', label: 'Jalons', icon: Target },
{ id: 'bugs', label: 'Bugs', icon: Bug },
{ id: 'members', label: 'Équipe', icon: Users }
{ id: 'members', label: 'Équipe', icon: Users },
{ id: 'admin', label: 'Administration', icon: ShieldCheck, adminOnly: true }
]
export default function Layout({
@@ -39,14 +53,21 @@ export default function Layout({
const { profile, projects, currentProject, setCurrentProject, refreshProjects, signOut } = useApp()
const [picker, setPicker] = useState(false)
const [showNew, setShowNew] = useState(false)
const [theme, setThemeState] = useState<Theme>(getTheme())
const toggleTheme = (): void => {
const next: Theme = theme === 'dark' ? 'light' : 'dark'
setTheme(next)
setThemeState(next)
}
return (
<div className="flex h-full">
{/* Barre latérale */}
<aside className="flex w-64 shrink-0 flex-col border-r border-border bg-panel">
<aside className="flex w-64 shrink-0 flex-col border-r border-border bg-sidebar">
<div className="flex items-center gap-2 px-4 py-4 text-accent">
<Gamepad2 size={22} />
<span className="font-semibold text-white">GameDev Tracker</span>
<span className="font-semibold text-ink">GameDev Tracker</span>
</div>
{/* Sélecteur de projet */}
@@ -56,10 +77,10 @@ export default function Layout({
onClick={() => setPicker((v) => !v)}
>
<span className="truncate">
<span className="block text-[10px] uppercase text-gray-500">Projet</span>
<span className="font-medium text-white">{currentProject?.name ?? 'Aucun projet'}</span>
<span className="block text-[10px] uppercase text-subtle">Projet</span>
<span className="font-medium text-ink">{currentProject?.name ?? 'Aucun projet'}</span>
</span>
<ChevronDown size={16} className="text-gray-400" />
<ChevronDown size={16} className="text-muted" />
</button>
{picker && (
<div className="absolute left-3 right-3 z-20 mt-1 rounded-lg border border-border bg-panel2 py-1 shadow-xl">
@@ -89,12 +110,12 @@ export default function Layout({
</div>
<nav className="mt-4 flex-1 space-y-1 px-3">
{NAV.map((n) => (
{NAV.filter((n) => !n.adminOnly || profile?.is_admin).map((n) => (
<button
key={n.id}
onClick={() => setPage(n.id)}
className={`relative flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${
page === n.id ? 'text-white' : 'text-gray-400 hover:bg-panel2 hover:text-white'
page === n.id ? 'text-ink' : 'text-muted hover:bg-panel2 hover:text-ink'
}`}
>
{page === n.id && (
@@ -115,12 +136,19 @@ export default function Layout({
<div className="flex items-center gap-2">
<Avatar profile={profile} />
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium text-white">{profile?.full_name}</div>
<div className="truncate text-xs text-gray-500">
<div className="truncate text-sm font-medium text-ink">{profile?.full_name}</div>
<div className="truncate text-xs text-subtle">
{profile ? ROLE_LABELS[profile.role] : ''}
</div>
</div>
<button onClick={signOut} title="Se déconnecter" className="text-gray-400 hover:text-rose-400">
<button
onClick={toggleTheme}
title={theme === 'dark' ? 'Passer en clair' : 'Passer en sombre'}
className="text-muted hover:text-ink"
>
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
</button>
<button onClick={signOut} title="Se déconnecter" className="text-muted hover:text-rose-600">
<LogOut size={18} />
</button>
</div>
@@ -129,10 +157,10 @@ export default function Layout({
{/* Contenu */}
<main className="flex-1 overflow-hidden">
{currentProject ? (
{currentProject || page === 'admin' ? (
children
) : (
<div className="flex h-full flex-col items-center justify-center gap-4 text-gray-400">
<div className="flex h-full flex-col items-center justify-center gap-4 text-muted">
<Settings size={40} className="text-gray-600" />
<p>Aucun projet pour le moment.</p>
<button className="btn-primary" onClick={() => setShowNew(true)}>