import { DateTime, formatDateOnly, getWeekStart, parseDateOnly } from "@batch-cooking/date-tools"; import type { CookingBackgroundTaskView, CookingPhaseView, CookingTaskView, OptimizedCookingPlanView, } from "@batch-cooking/shared"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSearchParams } from "react-router-dom"; import { apiClient } from "../../api/client"; import { WeekNavigator } from "../../features/planning/WeekNavigator"; import { taskHeadline, taskRecipeNames } from "./cooking-session"; import "./cooking-session-page.scss"; /** Load state for the `GET /cooking-session` call — same discriminated-union shape as `ShoppingListPage`'s own state. */ type CookingSessionState = | { status: "loading" } | { status: "loaded"; plan: OptimizedCookingPlanView } | { status: "error" }; /** * "Cuisiner cette semaine" — routed at `/cuisiner`, reached from the * planning page's "Commencer à cuisiner" button. Shows the household's week * of planned recipes reorganized by the backend optimizer * (`GET /cooking-session`, see the API's `cooking-optimizer.ts`) into * ordered phases: a mise-en-place that pools shared prep, then cooking * phases that interleave the recipes with passive cooks shown as running * in the background. * * The week comes from a `?date=` query param (set by the planning button so * the two pages stay on the same week); absent/invalid falls back to the * current week. Read-only and recomputed on every visit — no progress * state to keep in sync, same design stance as `ShoppingListPage`. */ export function CookingSessionPage() { const { t } = useTranslation(); const [searchParams] = useSearchParams(); const [weekStart, setWeekStart] = useState(() => { const fromQuery = parseDateOnly(searchParams.get("date") ?? ""); return getWeekStart(fromQuery ?? DateTime.utc()); }); const [state, setState] = useState({ status: "loading" }); useEffect(() => { let cancelled = false; setState({ status: "loading" }); apiClient .getCookingPlanForWeek(formatDateOnly(weekStart)) .then((plan) => { if (!cancelled) setState({ status: "loaded", plan }); }) .catch(() => { if (!cancelled) setState({ status: "error" }); }); return () => { cancelled = true; }; }, [weekStart]); return (

{t("cookingSession.title")}

{t("cookingSession.subtitle")}

{state.status === "loading" && (

{t("cookingSession.loading")}

)} {state.status === "error" && (

{t("common.loadError")}

)} {state.status === "loaded" && }
); } /** The plan body — the recipe legend then every phase, or the empty-week message. */ function CookingPlan({ plan }: { plan: OptimizedCookingPlanView }) { const { t } = useTranslation(); if (plan.phases.length === 0) { return

{t("cookingSession.empty")}

; } return (
{plan.recipes.map((recipe) => ( {recipe.name} · ×{recipe.portions} ))}
{plan.phases.map((phase) => ( ))}
); } /** One phase: its background band (if any) then its task cards. */ function PhaseSection({ phase }: { phase: CookingPhaseView }) { const { t } = useTranslation(); return (

{t("cookingSession.phase.label", { index: phase.index + 1 })} {t(`cookingSession.phase.${phase.kind}`)}

{phase.background.length > 0 && (
{t("cookingSession.background.title")}
    {phase.background.map((task) => (
  • ))}
)}
    {phase.tasks.map((task) => (
  • ))}
); } /** A single actionable task — a merged-prep pool or a plain recipe step. */ function TaskCard({ task }: { task: CookingTaskView }) { const { t } = useTranslation(); return (

{taskHeadline(task, t)} {task.kind === "merged-prep" && ( {t("cookingSession.task.sharedBadge")} )}

{t("cookingSession.task.forRecipes", { recipes: taskRecipeNames(task) })}

{task.utensils.length > 0 && (

{t("cookingSession.task.utensils")} :{" "} {task.utensils.map((utensil) => t(`catalog.utensils.${utensil.key}`)).join(", ")}

)}
); } /** One "meanwhile, X is cooking" line inside a phase's background band. */ function BackgroundLine({ task }: { task: CookingBackgroundTaskView }) { const { t } = useTranslation(); const technique = task.technique ? `${t(`catalog.techSteps.${task.technique.key}`)} — ` : ""; return ( {technique} {task.description} ({task.recipeName}) ); }