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:
+45
-17
@@ -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)}>
|
||||
|
||||
Reference in New Issue
Block a user