refactor(web): regroupe pages/ par section au lieu d'un dossier à plat
pages/ mélangeait 8 fichiers directement à sa racine (LoginPage, SignupPage, PlanningPage+scss, RecipesPage, RecipeFormPage, ImportRecipePage, ShoppingListPage, ComingSoonPage+scss) à côté de deux sous-dossiers déjà groupés (onboarding/, settings/) — incohérent, et difficile à parcourir une fois le nombre de pages monté. Un sous-dossier par section routée, même règle que onboarding/settings existants : - pages/auth/ — LoginPage, SignupPage - pages/planning/ — PlanningPage + planning-page.scss - pages/recipes/ — RecipesPage, RecipeFormPage, ImportRecipePage - pages/shopping-list/ — ShoppingListPage ComingSoonPage (+ .scss) déménage vers components/ui/ — ce n'est pas une page routée elle-même (ShoppingListPage l'enveloppe), c'est un composant UI générique réutilisable, sa place est aux côtés de Dialog/Tooltip/etc., pas dans pages/. Chemins relatifs internes de chaque fichier déplacé mis à jour (un niveau de profondeur en plus), imports dans App.tsx repointés, tri Biome réappliqué. specs/frontend-architecture.md mis à jour (arborescence + références de chemin). Vérifié : pnpm build clean (apps/web, 1952 modules), pnpm lint clean sur tout le repo, testé en live dans le navigateur (login/signup, planning, recettes, nouvelle recette, liste de courses, paramètres) — aucune route cassée. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
eab28aa017
commit
23d4fd4966
13 changed files with 85 additions and 71 deletions
|
|
@ -2,22 +2,22 @@ import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated";
|
import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated";
|
||||||
import { RequireAuth } from "./features/auth/RequireAuth";
|
import { RequireAuth } from "./features/auth/RequireAuth";
|
||||||
import { AppLayout } from "./layouts/AppLayout";
|
import { AppLayout } from "./layouts/AppLayout";
|
||||||
import { ImportRecipePage } from "./pages/ImportRecipePage";
|
import { LoginPage } from "./pages/auth/LoginPage";
|
||||||
import { LoginPage } from "./pages/LoginPage";
|
import { SignupPage } from "./pages/auth/SignupPage";
|
||||||
import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage";
|
import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage";
|
||||||
import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage";
|
import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage";
|
||||||
import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage";
|
import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage";
|
||||||
import { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage";
|
import { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage";
|
||||||
import { PlanningPage } from "./pages/PlanningPage";
|
import { PlanningPage } from "./pages/planning/PlanningPage";
|
||||||
import { RecipeFormPage } from "./pages/RecipeFormPage";
|
import { ImportRecipePage } from "./pages/recipes/ImportRecipePage";
|
||||||
import { RecipesPage } from "./pages/RecipesPage";
|
import { RecipeFormPage } from "./pages/recipes/RecipeFormPage";
|
||||||
import { ShoppingListPage } from "./pages/ShoppingListPage";
|
import { RecipesPage } from "./pages/recipes/RecipesPage";
|
||||||
import { SignupPage } from "./pages/SignupPage";
|
|
||||||
import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage";
|
import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage";
|
||||||
import { CreditsPage } from "./pages/settings/CreditsPage";
|
import { CreditsPage } from "./pages/settings/CreditsPage";
|
||||||
import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage";
|
import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage";
|
||||||
import { PreferencesPage } from "./pages/settings/PreferencesPage";
|
import { PreferencesPage } from "./pages/settings/PreferencesPage";
|
||||||
import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage";
|
import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage";
|
||||||
|
import { ShoppingListPage } from "./pages/shopping-list/ShoppingListPage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level route table. Every authenticated section is nested under one
|
* Top-level route table. Every authenticated section is nested under one
|
||||||
|
|
|
||||||
26
apps/web/src/components/ui/ComingSoonPage.tsx
Normal file
26
apps/web/src/components/ui/ComingSoonPage.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import "./ComingSoonPage.scss";
|
||||||
|
|
||||||
|
interface ComingSoonPageProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder rendered by a section that has a route/sidebar entry but no
|
||||||
|
* real feature behind it yet — today only `pages/shopping-list/ShoppingListPage.tsx`
|
||||||
|
* (`Recettes`/`Foyer & profil` both grew real backends since this was
|
||||||
|
* written, see `pages/recipes/`/`pages/settings/`). Kept as a shared,
|
||||||
|
* reusable component (`components/ui/`, not itself a routed page) rather
|
||||||
|
* than inlined into that one page, so a future stub section doesn't need to
|
||||||
|
* hand-roll the same markup — the page that needs it still gets its own
|
||||||
|
* file (and its own copy, via i18n), just wrapping this instead of
|
||||||
|
* rewriting it.
|
||||||
|
*/
|
||||||
|
export function ComingSoonPage({ title, description }: ComingSoonPageProps) {
|
||||||
|
return (
|
||||||
|
<div className="coming-soon-page">
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import "./ComingSoonPage.scss";
|
|
||||||
|
|
||||||
interface ComingSoonPageProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder rendered by every section that has a route/sidebar entry but
|
|
||||||
* no real feature behind it yet (Recettes, Liste de courses, Foyer &
|
|
||||||
* profil — see `RecipesPage.tsx` etc.). One shared component instead of
|
|
||||||
* three near-identical markup blocks; each page still gets its own file
|
|
||||||
* (and its own copy, via i18n) so building out a real feature later means
|
|
||||||
* rewriting one dedicated file, not splitting a generic route.
|
|
||||||
*/
|
|
||||||
export function ComingSoonPage({ title, description }: ComingSoonPageProps) {
|
|
||||||
return (
|
|
||||||
<div className="coming-soon-page">
|
|
||||||
<h1>{title}</h1>
|
|
||||||
<p>{description}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -2,13 +2,13 @@ import { ErrorCode, loginSchema } from "@batch-cooking/shared";
|
||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { ApiError } from "../api/client";
|
import { ApiError } from "../../api/client";
|
||||||
import { useAuth } from "../features/auth/AuthContext";
|
import { useAuth } from "../../features/auth/AuthContext";
|
||||||
// Shared with SignupPage — see the file for why it's colocated in
|
// Shared with SignupPage — see the file for why it's colocated in
|
||||||
// features/auth/ rather than duplicated per page.
|
// features/auth/ rather than duplicated per page.
|
||||||
import "../features/auth/auth-form.scss";
|
import "../../features/auth/auth-form.scss";
|
||||||
import { fieldErrorsFrom } from "../lib/zod-errors";
|
import { fieldErrorsFrom } from "../../lib/zod-errors";
|
||||||
import { errorMessageService } from "../services/error-message.service";
|
import { errorMessageService } from "../../services/error-message.service";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login form. Validates client-side first (via the shared `loginSchema`,
|
* Login form. Validates client-side first (via the shared `loginSchema`,
|
||||||
|
|
@ -2,13 +2,13 @@ import { ErrorCode, signupSchema } from "@batch-cooking/shared";
|
||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { ApiError } from "../api/client";
|
import { ApiError } from "../../api/client";
|
||||||
import { useAuth } from "../features/auth/AuthContext";
|
import { useAuth } from "../../features/auth/AuthContext";
|
||||||
// Shared with LoginPage — see the file for why it's colocated in
|
// Shared with LoginPage — see the file for why it's colocated in
|
||||||
// features/auth/ rather than duplicated per page.
|
// features/auth/ rather than duplicated per page.
|
||||||
import "../features/auth/auth-form.scss";
|
import "../../features/auth/auth-form.scss";
|
||||||
import { fieldErrorsFrom } from "../lib/zod-errors";
|
import { fieldErrorsFrom } from "../../lib/zod-errors";
|
||||||
import { errorMessageService } from "../services/error-message.service";
|
import { errorMessageService } from "../../services/error-message.service";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signup form (profile creation). Validates client-side first (via the
|
* Signup form (profile creation). Validates client-side first (via the
|
||||||
|
|
@ -15,8 +15,8 @@ import {
|
||||||
} from "@batch-cooking/shared";
|
} from "@batch-cooking/shared";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { apiClient } from "../api/client";
|
import { apiClient } from "../../api/client";
|
||||||
import { type PlanningSlot, RecipePickerDialog } from "../features/planning/RecipePickerDialog";
|
import { type PlanningSlot, RecipePickerDialog } from "../../features/planning/RecipePickerDialog";
|
||||||
import "./planning-page.scss";
|
import "./planning-page.scss";
|
||||||
|
|
||||||
/** Load state for the `GET /planning` call — a discriminated union so a stale/impossible combination (e.g. "loading" with data) can't be represented. */
|
/** Load state for the `GET /planning` call — a discriminated union so a stale/impossible combination (e.g. "loading" with data) can't be represented. */
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { MEALS, type Meal, WEEK_DAYS, type WeekDay } from "@batch-cooking/shared";
|
import { MEALS, type Meal, WEEK_DAYS, type WeekDay } from "@batch-cooking/shared";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { RecipeImportForm } from "../features/recipes/RecipeImportForm";
|
import { RecipeImportForm } from "../../features/recipes/RecipeImportForm";
|
||||||
import "../features/recipes/recipes.scss";
|
import "../../features/recipes/recipes.scss";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and validates `?planningDate=&planningWeekDay=&planningMeal=` off
|
* Reads and validates `?planningDate=&planningWeekDay=&planningMeal=` off
|
||||||
|
|
@ -10,14 +10,14 @@ import {
|
||||||
import { type FormEvent, useEffect, useState } from "react";
|
import { type FormEvent, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { ApiError, apiClient } from "../api/client";
|
import { ApiError, apiClient } from "../../api/client";
|
||||||
import { DietTagSelect } from "../features/recipes/DietTagSelect";
|
import { DietTagSelect } from "../../features/recipes/DietTagSelect";
|
||||||
import { IngredientPicker } from "../features/recipes/IngredientPicker";
|
import { IngredientPicker } from "../../features/recipes/IngredientPicker";
|
||||||
import { IngredientRow } from "../features/recipes/IngredientRow";
|
import { IngredientRow } from "../../features/recipes/IngredientRow";
|
||||||
import { type StepDraft, StepListEditor } from "../features/recipes/StepListEditor";
|
import { type StepDraft, StepListEditor } from "../../features/recipes/StepListEditor";
|
||||||
import "../features/recipes/recipes.scss";
|
import "../../features/recipes/recipes.scss";
|
||||||
import { makeClientKey } from "../lib/client-key";
|
import { makeClientKey } from "../../lib/client-key";
|
||||||
import { errorMessageService } from "../services/error-message.service";
|
import { errorMessageService } from "../../services/error-message.service";
|
||||||
|
|
||||||
/** In display order — mirrors `RecipeVisibility` (schema.prisma/shared types). */
|
/** In display order — mirrors `RecipeVisibility` (schema.prisma/shared types). */
|
||||||
const VISIBILITY_OPTIONS: RecipeVisibility[] = ["PERSONAL", "HOUSE", "PUBLIC"];
|
const VISIBILITY_OPTIONS: RecipeVisibility[] = ["PERSONAL", "HOUSE", "PUBLIC"];
|
||||||
|
|
@ -2,22 +2,25 @@ import { ErrorCode, type RecipeSummaryView } from "@batch-cooking/shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { ApiError, apiClient } from "../api/client";
|
import { ApiError, apiClient } from "../../api/client";
|
||||||
import { RecipeDetailPanel, type RecipeDetailState } from "../features/recipes/RecipeDetailPanel";
|
import {
|
||||||
|
RecipeDetailPanel,
|
||||||
|
type RecipeDetailState,
|
||||||
|
} from "../../features/recipes/RecipeDetailPanel";
|
||||||
import {
|
import {
|
||||||
RecipeSourcesPanel,
|
RecipeSourcesPanel,
|
||||||
type SourceItemSelection,
|
type SourceItemSelection,
|
||||||
} from "../features/recipes/RecipeSourcesPanel";
|
} from "../../features/recipes/RecipeSourcesPanel";
|
||||||
import { RecipeTable } from "../features/recipes/RecipeTable";
|
import { RecipeTable } from "../../features/recipes/RecipeTable";
|
||||||
import {
|
import {
|
||||||
isSourceTab,
|
isSourceTab,
|
||||||
parseSourceTabValue,
|
parseSourceTabValue,
|
||||||
type RecipesPageTab,
|
type RecipesPageTab,
|
||||||
RecipeTabs,
|
RecipeTabs,
|
||||||
sourceTabValue,
|
sourceTabValue,
|
||||||
} from "../features/recipes/RecipeTabs";
|
} from "../../features/recipes/RecipeTabs";
|
||||||
import { useEnabledSources } from "../features/recipes/useEnabledSources";
|
import { useEnabledSources } from "../../features/recipes/useEnabledSources";
|
||||||
import "../features/recipes/recipes.scss";
|
import "../../features/recipes/recipes.scss";
|
||||||
|
|
||||||
/** Debounce for the search field — avoids firing a request on every keystroke, same idea as the household name's autosave (`HouseholdSettingsPage`). */
|
/** Debounce for the search field — avoids firing a request on every keystroke, same idea as the household name's autosave (`HouseholdSettingsPage`). */
|
||||||
const SEARCH_DEBOUNCE_MS = 300;
|
const SEARCH_DEBOUNCE_MS = 300;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ComingSoonPage } from "./ComingSoonPage";
|
import { ComingSoonPage } from "../../components/ui/ComingSoonPage";
|
||||||
|
|
||||||
/** Shopping list section — routed at `/liste-de-courses`. No backend yet, stub for now. */
|
/** Shopping list section — routed at `/liste-de-courses`. No backend yet, stub for now. */
|
||||||
export function ShoppingListPage() {
|
export function ShoppingListPage() {
|
||||||
|
|
@ -21,7 +21,8 @@ apps/web/src/
|
||||||
│ └── ui/ # primitives réutilisables partout, voir plus bas
|
│ └── ui/ # primitives réutilisables partout, voir plus bas
|
||||||
│ ├── Dialog.tsx + dialog.scss # modale (élément <dialog> natif)
|
│ ├── Dialog.tsx + dialog.scss # modale (élément <dialog> natif)
|
||||||
│ ├── Checkbox.tsx / Radio.tsx # "carte sélectionnable" (CheckboxOption/RadioOption)
|
│ ├── Checkbox.tsx / Radio.tsx # "carte sélectionnable" (CheckboxOption/RadioOption)
|
||||||
│ └── Tooltip.tsx + tooltip.scss # infobulle CSS-only
|
│ ├── Tooltip.tsx + tooltip.scss # infobulle CSS-only
|
||||||
|
│ └── ComingSoonPage.tsx + .scss # placeholder générique, section sans backend (ex. Liste de courses) — pas une page routée elle-même, un composant que la page routée (pages/shopping-list/ShoppingListPage.tsx) enveloppe
|
||||||
├── features/
|
├── features/
|
||||||
│ ├── auth/ # authentification
|
│ ├── auth/ # authentification
|
||||||
│ │ ├── AuthContext.tsx # état global (profil connecté, login/signup/logout, refreshUser, deleteAccount)
|
│ │ ├── AuthContext.tsx # état global (profil connecté, login/signup/logout, refreshUser, deleteAccount)
|
||||||
|
|
@ -49,13 +50,17 @@ apps/web/src/
|
||||||
├── layouts/
|
├── layouts/
|
||||||
│ ├── AppLayout.tsx + .scss # sidebar (nav, sous-menu Paramètres, menu compte) commune à tout l'espace connecté, voir plus bas
|
│ ├── AppLayout.tsx + .scss # sidebar (nav, sous-menu Paramètres, menu compte) commune à tout l'espace connecté, voir plus bas
|
||||||
│ └── nav-icons.tsx # ré-export nommé des icônes lucide-react utilisées par la sidebar
|
│ └── nav-icons.tsx # ré-export nommé des icônes lucide-react utilisées par la sidebar
|
||||||
├── pages/
|
├── pages/ # un sous-dossier par section routée — jamais tous les fichiers à plat, un seul composant par sous-dossier n'est pas un problème (cohérence de rangement avant tout)
|
||||||
│ ├── LoginPage.tsx / SignupPage.tsx (via auth-form.scss, partagé)
|
│ ├── auth/
|
||||||
│ ├── PlanningPage.tsx + planning-page.scss # grille de la semaine (routée sur "/"), voir plus bas
|
│ │ └── LoginPage.tsx / SignupPage.tsx (via features/auth/auth-form.scss, partagé)
|
||||||
│ ├── RecipesPage.tsx # vue maître-détail du catalogue (routée sur /recettes, /recettes/:id, /recettes/sources/:sourceKey/:externalId)
|
│ ├── planning/
|
||||||
│ ├── RecipeFormPage.tsx # création/édition manuelle (/recettes/nouvelle, /recettes/:id/modifier)
|
│ │ └── PlanningPage.tsx + planning-page.scss # grille de la semaine (routée sur "/"), voir plus bas
|
||||||
│ ├── ImportRecipePage.tsx # route de secours autonome pour un import (/recettes/importer/:sourceKey/:externalId)
|
│ ├── recipes/
|
||||||
│ ├── ComingSoonPage.tsx + .scss / ShoppingListPage.tsx # placeholder, section sans backend
|
│ │ ├── RecipesPage.tsx # vue maître-détail du catalogue (routée sur /recettes, /recettes/:id, /recettes/sources/:sourceKey/:externalId)
|
||||||
|
│ │ ├── RecipeFormPage.tsx # création/édition manuelle (/recettes/nouvelle, /recettes/:id/modifier)
|
||||||
|
│ │ └── ImportRecipePage.tsx # route de secours autonome pour un import (/recettes/importer/:sourceKey/:externalId)
|
||||||
|
│ ├── shopping-list/
|
||||||
|
│ │ └── ShoppingListPage.tsx # enveloppe components/ui/ComingSoonPage.tsx — section sans backend
|
||||||
│ ├── settings/ # ancienne HouseholdPage éclatée en 5 pages, voir plus bas
|
│ ├── settings/ # ancienne HouseholdPage éclatée en 5 pages, voir plus bas
|
||||||
│ │ ├── AccountSettingsPage.tsx / HouseholdSettingsPage.tsx / PreferencesPage.tsx
|
│ │ ├── AccountSettingsPage.tsx / HouseholdSettingsPage.tsx / PreferencesPage.tsx
|
||||||
│ │ ├── UserPreferencesPage.tsx / CreditsPage.tsx
|
│ │ ├── UserPreferencesPage.tsx / CreditsPage.tsx
|
||||||
|
|
@ -347,7 +352,7 @@ L'ancienne `HouseholdPage` combinée est éclatée en 5 pages dédiées
|
||||||
|
|
||||||
## Planning (`/`)
|
## Planning (`/`)
|
||||||
|
|
||||||
`pages/PlanningPage.tsx` (+ `planning-page.scss`) — remplace l'ancienne
|
`pages/planning/PlanningPage.tsx` (+ `planning-page.scss`) — remplace l'ancienne
|
||||||
`HomePage`. Grille complète de la semaine, pas un simple tableau du jour :
|
`HomePage`. Grille complète de la semaine, pas un simple tableau du jour :
|
||||||
7 colonnes (jours) × 5 lignes (`petit-dejeuner`, `collation`, `dejeuner`,
|
7 colonnes (jours) × 5 lignes (`petit-dejeuner`, `collation`, `dejeuner`,
|
||||||
`gouter`, `diner` — `WEEK_DAYS`/`MEALS` de `packages/shared`), avec un
|
`gouter`, `diner` — `WEEK_DAYS`/`MEALS` de `packages/shared`), avec un
|
||||||
|
|
@ -427,7 +432,7 @@ quelqu'un l'ajoute à son planning" :
|
||||||
|
|
||||||
## Recettes — catalogue, favoris, import depuis une source externe
|
## Recettes — catalogue, favoris, import depuis une source externe
|
||||||
|
|
||||||
`pages/RecipesPage.tsx` — routée sur `/recettes`, `/recettes/:id` **et**
|
`pages/recipes/RecipesPage.tsx` — routée sur `/recettes`, `/recettes/:id` **et**
|
||||||
`/recettes/sources/:sourceKey/:externalId` (le **même composant** pour les
|
`/recettes/sources/:sourceKey/:externalId` (le **même composant** pour les
|
||||||
trois) : une vue **maître-détail**, pas une navigation vers une page séparée —
|
trois) : une vue **maître-détail**, pas une navigation vers une page séparée —
|
||||||
la barre d'onglets + le tableau restent montés, seul le panneau de détail
|
la barre d'onglets + le tableau restent montés, seul le panneau de détail
|
||||||
|
|
@ -485,7 +490,7 @@ Clic sur une ligne :
|
||||||
/planning/items` avec les portions du formulaire.
|
/planning/items` avec les portions du formulaire.
|
||||||
- **`RecipeImportForm` a été extrait d'`ImportRecipePage`** pour que
|
- **`RecipeImportForm` a été extrait d'`ImportRecipePage`** pour que
|
||||||
`RecipePickerDialog` puisse l'embarquer directement comme une de ses étapes
|
`RecipePickerDialog` puisse l'embarquer directement comme une de ses étapes
|
||||||
— `pages/ImportRecipePage.tsx` (`/recettes/importer/:sourceKey/:externalId`)
|
— `pages/recipes/ImportRecipePage.tsx` (`/recettes/importer/:sourceKey/:externalId`)
|
||||||
n'en est plus que le wrapper d'une **route de secours autonome et
|
n'en est plus que le wrapper d'une **route de secours autonome et
|
||||||
partageable** (favori enregistré, page rechargée en plein milieu du flux),
|
partageable** (favori enregistré, page rechargée en plein milieu du flux),
|
||||||
plus le chemin principal. Elle décode toujours défensivement
|
plus le chemin principal. Elle décode toujours défensivement
|
||||||
|
|
@ -608,6 +613,9 @@ Clic sur une ligne :
|
||||||
`:hover`/`:focus-within` (aucun état JS). `children` doit être un seul
|
`:hover`/`:focus-within` (aucun état JS). `children` doit être un seul
|
||||||
élément focusable ; cloné pour y attacher `aria-describedby` (lecteurs
|
élément focusable ; cloné pour y attacher `aria-describedby` (lecteurs
|
||||||
d'écran). Utilisé par `StepDescription.tsx` pour l'infobulle des techniques.
|
d'écran). Utilisé par `StepDescription.tsx` pour l'infobulle des techniques.
|
||||||
|
- **`ComingSoonPage.tsx`** (+ `.scss`) — placeholder générique (`title`/
|
||||||
|
`description`) pour une section routée sans backend, voir
|
||||||
|
[Sections sans backend](#sections-sans-backend--comingsoonpage) plus haut.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue