'use client'; import { computeAdherence } from '@ketopath/shared'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; import { useState, useTransition } from 'react'; import { Button } from '@/components/ui/button'; import { regenerateSlot, swapSlotRecipe, toggleConsumed, toggleFreeMeal, type CurrentPlan, type PlanSlot, } from './actions'; const MEAL_ORDER: PlanSlot['meal'][] = ['COLAZIONE', 'PRANZO', 'SPUNTINO', 'CENA']; const DAY_KEYS = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', ] as const; const DAY_NUMERAL = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']; function aggregateMacros(slots: PlanSlot[]): { kcal: number; proteinG: number; fatG: number; netCarbG: number; } { return slots.reduce( (acc, s) => { // Per i free meal usiamo una stima fissa kcal e azzeriamo i macros // (non li conosciamo davvero) — il PRD parla di "compensazione" non di // tracking puntuale del pasto libero. if (s.isFreeMeal) { return { kcal: acc.kcal + FREE_MEAL_DEFAULT_KCAL, proteinG: acc.proteinG, fatG: acc.fatG, netCarbG: acc.netCarbG, }; } if (!s.selected) return acc; return { kcal: acc.kcal + s.selected.kcal, proteinG: acc.proteinG + s.selected.proteinG, fatG: acc.fatG + s.selected.fatG, netCarbG: acc.netCarbG + s.selected.netCarbG, }; }, { kcal: 0, proteinG: 0, fatG: 0, netCarbG: 0 }, ); } const FREE_MEAL_DEFAULT_KCAL = 750; export function PlanWeek({ plan }: { plan: CurrentPlan }) { const t = useTranslations('Plan'); const byDay = new Map(); for (const slot of plan.slots) { if (!byDay.has(slot.dayOfWeek)) byDay.set(slot.dayOfWeek, []); byDay.get(slot.dayOfWeek)!.push(slot); } const isPhase3 = plan.currentPhase === 'MAINTENANCE'; const weekTotals = aggregateMacros(plan.slots); const activeDays = Array.from(byDay.values()).filter((s) => s.length > 0).length; const weekAvg = activeDays > 0 ? { kcal: Math.round(weekTotals.kcal / activeDays), proteinG: Math.round(weekTotals.proteinG / activeDays), fatG: Math.round(weekTotals.fatG / activeDays), netCarbG: Math.round(weekTotals.netCarbG / activeDays), } : { kcal: 0, proteinG: 0, fatG: 0, netCarbG: 0 }; const adherence = computeAdherence( plan.slots.map((s) => ({ dayOfWeek: s.dayOfWeek, consumed: s.consumed, isFreeMeal: s.isFreeMeal, })), new Date(plan.weekStart + 'T00:00:00'), ); return (
{t('adherence')} {adherence.pastSlots === 0 ? '—' : `${Math.round(adherence.rate * 100)}%`} {adherence.pastSlots > 0 ? ( {adherence.consumedSlots}/{adherence.pastSlots} ) : null}
{DAY_KEYS.map((dayKey, idx) => { const slots = (byDay.get(idx) ?? []).sort( (a, b) => MEAL_ORDER.indexOf(a.meal) - MEAL_ORDER.indexOf(b.meal), ); const dayMacros = aggregateMacros(slots); // Nessuno slot → giorno di digiuno completo (ESE_24). const isFastingDay = slots.length === 0; // Solo CENA con dailyKcal ridotto → giorno "leggero" del 5:2. const isLightDay = !isFastingDay && slots.length === 1 && slots[0].meal === 'CENA'; return (
{DAY_NUMERAL[idx]}

{t(`days.${dayKey}`)} {isFastingDay ? ( · {t('fastingDay')} ) : isLightDay ? ( · {t('lightDay')} ) : null}

{isFastingDay ? '— kcal' : `${Math.round(dayMacros.kcal)} kcal · P ${Math.round(dayMacros.proteinG)} · G ${Math.round(dayMacros.fatG)} · C ${Math.round(dayMacros.netCarbG)}`}
{isFastingDay ? (

{t('fastingDayHint')}

) : (
{slots.map((slot, slotIdx) => ( ))}
)}
); })}
); } const MEAL_ROMAN = ['I', 'II', 'III', 'IV']; function SlotCard({ slot, index, canFreeMeal, }: { slot: PlanSlot; index: number; canFreeMeal: boolean; }) { const t = useTranslations('Plan'); const [open, setOpen] = useState(false); const [pending, startTransition] = useTransition(); function swap(recipeId: string) { startTransition(async () => { await swapSlotRecipe(slot.id, recipeId); setOpen(false); }); } function regenerate() { startTransition(async () => { await regenerateSlot(slot.id); setOpen(false); }); } function freeMeal() { startTransition(async () => { await toggleFreeMeal(slot.id); setOpen(false); }); } function consume() { startTransition(async () => { await toggleConsumed(slot.id); }); } return (

{MEAL_ROMAN[index]} {t(`meals.${slot.meal}`)}

{canFreeMeal ? ( ) : null} {!slot.isFreeMeal ? ( ) : null}
{slot.isFreeMeal ? ( <>

{t('freeMealLabel')}

~{FREE_MEAL_DEFAULT_KCAL}{' '} kcal stimate

) : slot.selected ? ( {slot.selected.name} ) : (

{t('noRecipe')}

)} {!slot.isFreeMeal && slot.selected ? (

{Math.round(slot.selected.kcal)}{' '} kcal

) : null} {slot.alternatives.length > 0 ? (
{open ? (
    {slot.alternatives.map((alt) => (
  • {alt.name}{' '} {Math.round(alt.kcal)} kcal
  • ))}
) : null}
) : null}
); } function MacroSummary({ label, value, unit, big = false, }: { label: string; value: number; unit: string; big?: boolean; }) { return (
{label} {value} {unit}
); }