// ============================================================================== // Author: Simon-Pierre Boucher // File: pages/Resto.tsx // Desc: Fiche restaurant — panneau synthèse collant (comme la fiche Lou·Ka) // + menu complet par sections avec prix, options et badge de CONTEXTE // de prix toujours visible (CLAUDE.md §6.2 : un prix sans contexte // est inutilisable ; delivery affiché clairement étiqueté). // ============================================================================== import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { CONTEXT_LABELS, CONTEXT_SHORT, Menu, Restaurant, cuisineLabel, dietLabel, fetchRestaurant, fmtDate, fmtPrice, pushRecentUid, sourceName, typeLabel, } from "../api"; import { IcoCompass } from "../components/Icons"; import { FavButton } from "../favorites"; function MenuBlock({ menu }: { menu: Menu }) { const [openSections, setOpenSections] = useState>( new Set(menu.sections.map((_, i) => i).slice(0, 4))); const toggle = (i: number) => { const next = new Set(openSections); if (next.has(i)) next.delete(i); else next.add(i); setOpenSections(next); }; return (
{CONTEXT_LABELS[menu.price_context] ?? menu.price_context} source {sourceName(menu.price_source)} · capté le {fmtDate(menu.captured_at)} {" · "}{menu.item_count} plats
{menu.sections.map((sec, i) => (
{openSections.has(i) && (
    {sec.items.map((it, j) => (
  • {it.name} {(it.tags ?? []).map((t) => ( {dietLabel(t)} ))}
    {it.description &&

    {it.description}

    }
    {it.image && ( {it.name} )}
    {(it.options ?? []).length > 0 && (
    {(it.options ?? []).map((op, k) => (
    {op.group} {op.required ? "Choix requis" : "Optionnel"}
    {op.choices.map((c, m) => ( {c.name} {c.price_delta !== 0 && ( 0 ? "plus" : "moins"}> {c.price_delta > 0 ? "+" : "−"} {Math.abs(c.price_delta).toFixed(2).replace(".", ",")} $ )} ))}
    ))}
    )}
  • ))}
)}
))}
); } export default function RestoPage() { const { uid } = useParams<{ uid: string }>(); const [resto, setResto] = useState(null); const [notFound, setNotFound] = useState(false); const [ctx, setCtx] = useState(null); useEffect(() => { if (!uid) return; setResto(null); setNotFound(false); fetchRestaurant(uid) .then((r) => { setResto(r); setCtx(r.menus?.[0]?.price_context ?? null); pushRecentUid(r.uid); }) .catch(() => setNotFound(true)); window.scrollTo({ top: 0 }); }, [uid]); if (notFound) { return (

Restaurant introuvable

Retour à la recherche

); } if (!resto) { return (
); } const menus = resto.menus ?? []; const menu = menus.find((m) => m.price_context === ctx) ?? menus[0] ?? null; return (
{/* Panneau resto (nom, prix, adresse) EN PREMIER dans le DOM : c'est le héro de la fiche — l'ordre du DOM est l'ordre visuel sur mobile (standard Groupe Ka « Ordre des sections — pages détail »). Sur desktop, le CSS le place explicitement en colonne 2. */}
{menus.length > 1 && (
{menus.map((m) => ( ))}
)} {menu ? : (

Menu à venir

Ce resto est répertorié ; son menu sera capté à la prochaine synchronisation.

)}
); }