From a24c1b821c0b3a477d2d643da1c8f7cc2f7f960d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 22:12:23 +0000 Subject: [PATCH] =?UTF-8?q?Add=20the=20application=20shell,=20Aper=C3=A7u?= =?UTF-8?q?=20and=20the=20week=20grid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the shell and Aperçu screen from the Claude Design export, and builds the week grid from the design system's grid specification. The theme is applied by a nonced inline script before first paint. Without it the page renders light and then swaps, which is a white flash for anyone working in dark. The nonce our CSP already emits is what makes an inline script possible at all. Demo data lives in src/lib/demo behind a README stating it is fictional and replaceable. Screens import from there and nowhere else, so the directory can be deleted wholesale once real queries land. Weekend columns are shaded because in retail those are the days that carry counterparts — mayor-authorised Sundays and their compensatory rest — and scheduling one by accident is the failure worth preventing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv --- src/app/(app)/layout.tsx | 7 + src/app/(app)/page.tsx | 127 +++++++++++ src/app/(app)/planning/semaine/page.tsx | 78 +++++++ src/app/layout.tsx | 22 +- src/components/planning/WeekGrid.tsx | 158 ++++++++++++++ src/components/shell/AppShell.tsx | 139 ++++++++++++ src/components/shell/PageHeader.tsx | 28 +++ src/components/shell/ThemeToggle.tsx | 69 ++++++ src/components/shell/navigation.ts | 98 +++++++++ src/lib/demo/README.md | 16 ++ src/lib/demo/apercu.ts | 103 +++++++++ src/lib/demo/equipe.ts | 130 ++++++++++++ src/lib/demo/semaine.ts | 268 ++++++++++++++++++++++++ src/lib/demo/types.ts | 63 ++++++ 14 files changed, 1301 insertions(+), 5 deletions(-) create mode 100644 src/app/(app)/layout.tsx create mode 100644 src/app/(app)/page.tsx create mode 100644 src/app/(app)/planning/semaine/page.tsx create mode 100644 src/components/planning/WeekGrid.tsx create mode 100644 src/components/shell/AppShell.tsx create mode 100644 src/components/shell/PageHeader.tsx create mode 100644 src/components/shell/ThemeToggle.tsx create mode 100644 src/components/shell/navigation.ts create mode 100644 src/lib/demo/README.md create mode 100644 src/lib/demo/apercu.ts create mode 100644 src/lib/demo/equipe.ts create mode 100644 src/lib/demo/semaine.ts create mode 100644 src/lib/demo/types.ts diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx new file mode 100644 index 0000000..91d9b4a --- /dev/null +++ b/src/app/(app)/layout.tsx @@ -0,0 +1,7 @@ +import type { ReactNode } from 'react'; + +import { AppShell } from '@/components/shell/AppShell'; + +export default function AppLayout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/src/app/(app)/page.tsx b/src/app/(app)/page.tsx new file mode 100644 index 0000000..eb44b98 --- /dev/null +++ b/src/app/(app)/page.tsx @@ -0,0 +1,127 @@ +import { PageBody, PageHeader } from '@/components/shell/PageHeader'; +import { Badge } from '@/components/ui/Badge'; +import { Button } from '@/components/ui/Button'; +import { Card, CardHeader } from '@/components/ui/Card'; +import { ENTRIES, TILES, TODOS } from '@/lib/demo/apercu'; + +export const metadata = { title: 'Aperçu RH · PlanFlow' }; + +const TILE_SWATCH: Record = { + warn: 'bg-warn-soft border-warn', + info: 'bg-info-soft border-info', + accent: 'bg-accent-soft border-accent', + danger: 'bg-danger-soft border-danger', +}; + +export default function ApercuPage() { + return ( + + + + + + } + /> + +
+ {TILES.map((tile) => ( + + ))} +
+ +
+ + 4} + action={ + + } + /> +
    + {ENTRIES.map((entry) => ( +
  • + + {entry.initials} + + + + {entry.name} + + + {entry.job} + + + + {entry.date} + + {entry.state} +
  • + ))} +
+
+ + + +
    + {TODOS.map((todo) => ( +
  • + + + + {todo.title} + + + {todo.detail} + + + + {todo.when} + +
  • + ))} +
+
+
+
+ ); +} diff --git a/src/app/(app)/planning/semaine/page.tsx b/src/app/(app)/planning/semaine/page.tsx new file mode 100644 index 0000000..e3d1cc9 --- /dev/null +++ b/src/app/(app)/planning/semaine/page.tsx @@ -0,0 +1,78 @@ +import { WeekGrid } from '@/components/planning/WeekGrid'; +import { PageBody, PageHeader } from '@/components/shell/PageHeader'; +import { Badge } from '@/components/ui/Badge'; +import { Button } from '@/components/ui/Button'; +import { POSTE_CODES, POSTE_LABELS, posteTokens } from '@/lib/design/postes'; +import { UNASSIGNED_ROW, WEEK_DAYS, WEEK_LABEL, WEEK_ROWS } from '@/lib/demo/semaine'; + +export const metadata = { title: 'Planning · semaine 33 · PlanFlow' }; + +export default function SemainePage() { + return ( + + + + + + + } + /> + +
+ 2 alertes de convention + Samedi non publié + 5 besoins non couverts +
+ + + + +
+ ); +} + +/** + * Légende des postes. + * + * Elle n'est pas décorative : la couleur seule ne dit pas quel poste est + * lequel, et le code à trois lettres imprimé dans chaque bloc n'a de sens que + * si sa correspondance est visible sur la même page. + */ +function PosteLegend() { + return ( +
+

+ Postes +

+
    + {POSTE_CODES.map((code) => { + const tokens = posteTokens(code); + return ( +
  • + + {code.toUpperCase()} + + {POSTE_LABELS[code]} +
  • + ); + })} +
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 26478ce..1b19f29 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,7 @@ import type { Metadata } from 'next'; +import { headers } from 'next/headers'; + +import { THEME_INIT_SCRIPT } from '@/components/shell/ThemeToggle'; import './globals.css'; @@ -7,14 +10,23 @@ export const metadata: Metadata = { description: 'Gestion du personnel, des plannings et des temps.', }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { + // Le nonce est posé par requête dans src/proxy.ts. C'est la seule façon + // d'exécuter un script inline sous notre CSP, qui n'autorise pas + // 'unsafe-inline'. + const nonce = (await headers()).get('x-nonce') ?? undefined; + return ( - - - {children} - + + +