WaterLogger in src/features/nutrition/components/WaterLogger.tsx. It is self-contained: it owns its query, its two mutations and its modal. It does not use the nutrition session store, and water is not part of the day snapshot.
The write functions and types live in the tracking feature: src/features/tracking/data/trackingApi.ts and src/features/tracking/data/types.ts.
Props and placement
PlanScreen renders it in three places and always passes key={dateParam ?? 'today'}, so the component remounts when the date changes and its local state resets.
dateParam is undefined when the tab is on today and the local ISO date otherwise.
Reading the day
Goal
The app does not compute the goal. It showsgoalMl from the response.
progress = goal > 0 ? total / goal : 0.- With a goal, the label is the
waterProgressstring withcurrentandgoal. - With
goalMlat 0 or below, the label is the total in millilitres and the sheet adds thewaterNoGoalcaption.
water value inside each day’s targets (NutritionTargetsDto.water). The mobile code does not read it for this component. How the server derives goalMl is not visible from the app.
Logging an amount
DailyMetricTotals: waterMl and steps.
Constants:
Ways to add:
- Plus button.
save(100). - Presets. 200, 330, 500, 1000 and 1500 ml, labelled as cups and bottles from the strings file.
- Custom. Opens a small dialog with a number field (
maxLength5).applyCustomaccepts a whole number above 0 and at most 5000.
save(value) builds the payload:
dateis the date the server returned for this day, not a value the device computed again.requestIdis a fresh client-generated id per tap. The mutation hasretry: 1, so a retried request carries the same id.saverejects a non-integer, zero or an absolute value aboveMAX_MLwith thewaterInvalidmessage before any request.
Subtracting
The minus button callssave(-Math.min(STEP_ML, total)). The amount is negative and never larger than the current total, and the button is disabled when the total is 0. A subtraction is a new entry with a negative amountMl. It is listed in the history with its sign.
Guarding against double taps
Apending ref blocks a second save or remove until the first settles. All controls are also disabled while either mutation is pending, while the query has no data, and when the query errored.
History and deletion
The sheet has a history toggle. When open it listsdata.entries newest first ([...entries].reverse()), each row showing the signed amount, the local time of createdAt, and a trash button. An empty list shows the waterEmpty message.
remove(entryId) sends date: data.date with the entry id. The response is DailyMetricTotals. This mutation also has retry: 1.
Opening the history scrolls the sheet so the toggle sits at the top. The list gets a minHeight of the scroll viewport minus the toggle height, so there is always room to scroll to it. Closing it scrolls back to the top.
WaterDay.entries has an optional deductEntryId field. This component does not read it.
Cache invalidation
Both mutations call the samerefresh on success:
Errors
Sheet structure
The sheet is a transparent slideModal over a KeyboardAvoidingView (padding on iOS, height on Android). The custom amount dialog is a second Modal nested inside the first, with a fade animation. It focuses its field in onShow.