feat(meal-plan) Essensplan feature #10
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "meal-plan"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
/meal-planroute, accessible from the settings menu for all authenticated usersWhat's included
docs/adr/2026-05-31-meal-plan.mdMealPlanPermissionMealPlanDto,MealPlanDayDto,MealPlanDayDishDto,MealPlanShareDto,MealPlanSummaryDto,MealPlanListResponseDto,MealPlanLoadRequestDtoMealPlanModel,MealPlanDayModel,MealPlanDayDishModel,MealPlanShareModel,MealPlanSummaryModelMealPlanDayDishMapper,MealPlanShareMapper,MealPlanSummaryMapper,MealPlanDayMapper,MealPlanMapperMealPlanRestResource(8 endpoints)useMealPlanDayAutoSaveMealPlanDishCard,MealPlanDayNoteEditor,MealPlanCreateDialog,MealPlanRenameDialog,MealPlanAddDishPopup,MealPlanShareDialog,MealPlanDayColumn,MealPlanCalendar,MealPlanHeader,MealPlanPagegetMealPlanUrl,getMealPlanUrlDefinition,MEAL_PLAN_PLAN_PARAM,MEAL_PLAN_WEEK_PARAMinroutes.tsSettingsMenu, route wired inApp.tsxgetMondayOfWeek,formatIsoDate,parseIsoDate,getWeekDatesindateUtils.tsTest plan
/meal-plan?plan=<uuid>?week=param and reloads the day window🤖 Generated with Claude Code
@ -0,0 +20,4 @@recipeTitle?: string;/** Number of portions needed for this dish on this day. Must be a positive integer. Required. */servings: number;If we wish to retain a consistent order here or allow the user to reorder the dishes in some way, the backend will have to add a sortOrder field.
@ -0,0 +6,4 @@* For week navigation, set `dateFrom` to the Monday and `dateTo` to the Sunday of the* displayed week. The backend returns only days whose `date` falls within the inclusive range.*/export interface MealPlanLoadRequestDto {Should be MealPlanFilterRequest - Backend renamed. Request and Response should never end on Dto
@ -0,0 +5,4 @@* a named field so the response shape can be extended with pagination metadata in the future* without breaking existing consumers.*/export interface MealPlanListResponseDto {Naming should be consistent with backend! Request and Response should never end with Dto as they are no Dtos...
@ -0,0 +9,4 @@* existing share and create a new one. `sharedWithUserDisplayName` is a read-only convenience* field; do NOT send it on write requests.*/export interface MealPlanShareDto extends AbstractDto {This is out dated. Backend updated DTO. It's now
export class MealPlanShareDto extends AbstractDto {
/**
* UUID of the user this plan is shared with.
* Required on create; identifies the recipient of the access grant.
*/
sharedWithUserId!: string;
}
@ -0,0 +89,4 @@const goToPrevWeek = () => {const prev = new Date(weekStart);prev.setDate(prev.getDate() - 7);I'd prefer to have a named constant for 7 instead of a magic number. E.g., const daysOfWeek = 7
@ -0,0 +108,4 @@onClick={goToPrevWeek}title="Vorherige Woche"variant={IconButtonVariant.ghost}size={20}Why the repeated magic 20? We should at least have a constant for this page instead of repeating the magic value. Probably even some global constants to keep sizes consistent on different pages / in different components.
@ -0,0 +120,4 @@onClick={goToNextWeek}title="Nächste Woche"variant={IconButtonVariant.ghost}size={20}See comment on magic numbers above.
@ -0,0 +47,4 @@};// Allow submitting via the Enter key in the text input for convenience.const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {It's nice to have this feature, but why introduce it here? I'd like to have it, but then we need to add it to all our dialogs. Otherwise, we'll be inconsistent. Ideally, it's even ensured globally by a shared basic dialog component.
@ -0,0 +128,4 @@onClick={enterEditMode}title="Notiz bearbeiten"variant={IconButtonVariant.ghost}size={14}Here we have another magic size. Is this type of component used somewhere else as well? is this something that we should define in a central place? Or maybe add size-options to the IconButton?
@ -0,0 +46,4 @@};// Enter submits the form; Escape is handled globally by BasicDialog.const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {check whether it's possible to have a general solution for confirm as well.
@ -0,0 +68,4 @@function pickDefaultPlan(plans: MealPlanSummaryModel[]): MealPlanSummaryModel | null {if (plans.length === 0) return null;// First plan where the user is the owner is the natural default.const ownedPlan = plans.find(p => p.effectivePermission === "owner");Must owner be referenced by string.... Is there no less magic way to reference an enum value? What about a util method? I guess we'll need this check in several places.
@ -14,0 +20,4 @@export function getMondayOfWeek(date: Date): Date {const d = new Date(date);// getDay() returns 0=Sunday … 6=Saturday. Shift so Monday=0, Sunday=6.const dayOfWeek = (d.getDay() + 6) % 7;magic 7s again. Use constant for days of week in dateUtil. Export that constant here and use in calendar as well
@ -109,0 +117,4 @@if (planId) params.set(MEAL_PLAN_PLAN_PARAM, planId)if (weekStart) params.set(MEAL_PLAN_WEEK_PARAM, weekStart)const qs = params.toString()return qs ? `/meal-plan?${qs}` : "/meal-plan"Why not use getMealPlanUrlDefinition here?
@ -0,0 +116,4 @@// Map each dish to its DTO shape, omitting id/version for brand-new dishes.// Pass the array index as sortOrder so the backend persists the display order.const dishDtos: MealPlanDayDishDto[] = (current?.dishes ?? []).map((dish, index): MealPlanDayDishDto => {shouldn't all this mapping be part of the mapper?
@ -0,0 +226,4 @@setSaveState("error");} else {// Any other network or server error — show generic error state.setSaveState("error");Can't we reference all these state by enum values instead of magic strings.
@ -0,0 +165,4 @@// Compute the ISO bounds for the week window sent to the backend.const weekDates = getWeekDates(week);const dateFrom = formatIsoDate(weekDates[0]); // Mondayconst dateTo = formatIsoDate(weekDates[6]); // SundayUse days of week constant?
@ -0,0 +104,4 @@setIsLoadingUsers(true);fetchAllUsers().then(users => {setAllUsers(users);A user can currently share with himself...
CircularIconButton gains a size prop ("md" default, "sm" for tight layouts). NumberStepControl gains a compact prop that uses size="sm" circular buttons, gap-1, and a w-10 input. MealPlanDishCard uses compact, removes the "Portionen" label, and centres the control horizontally in the card. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>- DishCard: restore relative/absolute trash button (top-1.5 right-1.5, size 14), pr-6 on title, hyphens-auto for German syllable breaks - index.html: lang="en" → lang="de" (enables browser German hyphenation) - NumberStepControl: py-0.5 on compact input to reduce height - Button: add iconSize prop so callers can override the default 20px icon - MealPlanDayColumn: "Rezept hinzufügen" → "Rezept" with iconSize={14} to match the text-xs font size Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>@ -0,0 +86,4 @@* specific view via the address bar and the browser's back/forward navigation* moves between weeks naturally.*/export default function MealPlanPage() {We need some way to delete a meal plan.