b69661c997
- Dashboard: progress bar vs target, entries grouped by meal slot, edit/delete inline, date navigation (UTC-safe shiftDate helper) - Add Food manual creation form, search-and-log flow with live preview - Minimal target form; loading/error/empty states throughout - Stores (current date, log, summary) with summary refresh on mutation - All HTTP via lib/api.js; formatting via lib/format.js; runes only - Full suites green (backend 104 passed, frontend vitest + build)
91 lines
3.0 KiB
JavaScript
91 lines
3.0 KiB
JavaScript
/**
|
|
* Shared state lives in stores, not prop-drilling (spec §8.2 rule 3).
|
|
* Mutation flow: component → api.js → update store from the response.
|
|
* Svelte 5 runes style only (spec §8.2 rule 8).
|
|
*/
|
|
|
|
import { api } from './api.js'
|
|
import { shiftDate } from './format.js'
|
|
|
|
/** Return today as a YYYY-MM-DD string — the server never decides "today" (spec §8.1 rule 8). */
|
|
function todayString() {
|
|
return new Date().toISOString().slice(0, 10)
|
|
}
|
|
|
|
// ── Current date ────────────────────────────────────────────────────────────
|
|
|
|
export const currentDate = $state({ value: todayString() })
|
|
|
|
export function setDate(dateStr) {
|
|
currentDate.value = dateStr
|
|
}
|
|
|
|
export function goPrevDay() {
|
|
currentDate.value = shiftDate(currentDate.value, -1)
|
|
}
|
|
|
|
export function goNextDay() {
|
|
currentDate.value = shiftDate(currentDate.value, 1)
|
|
}
|
|
|
|
// ── App view ─────────────────────────────────────────────────────────────────
|
|
|
|
export const appView = $state({ current: 'dashboard' })
|
|
|
|
/** Simple navigation requests from child components (e.g., "Set a target"). */
|
|
export const navigateTo = (view) => { appView.current = view }
|
|
|
|
// ── Day data (log + summary) ─────────────────────────────────────────────────
|
|
|
|
export const dayData = $state({
|
|
log: [],
|
|
summary: null, // { date, totals: { calories, protein_g, ... }, target: {...} | null }
|
|
loading: false,
|
|
error: null,
|
|
})
|
|
|
|
export async function refreshDayData() {
|
|
const date = currentDate.value
|
|
dayData.loading = true
|
|
dayData.error = null
|
|
try {
|
|
const [log, summary] = await Promise.all([
|
|
api.getLog(date),
|
|
api.getSummary(date),
|
|
])
|
|
dayData.log = log
|
|
dayData.summary = summary
|
|
dayData.loading = false
|
|
} catch (e) {
|
|
dayData.error = e.message
|
|
dayData.loading = false
|
|
}
|
|
}
|
|
|
|
/** Re-fetch the summary for the current date without touching the log array. */
|
|
async function refreshSummary() {
|
|
try {
|
|
dayData.summary = await api.getSummary(currentDate.value)
|
|
} catch {
|
|
// Keep the previous summary on failure; the user can retry.
|
|
}
|
|
}
|
|
|
|
/** Add a newly-created log entry to the store then refresh the summary. */
|
|
export async function addLogEntryToStore(entry) {
|
|
dayData.log = [...dayData.log, entry]
|
|
await refreshSummary()
|
|
}
|
|
|
|
/** Update a log entry in the store after a successful API PUT, then refresh the summary. */
|
|
export async function updateLogEntryInStore(updated) {
|
|
dayData.log = dayData.log.map(e => (e.id === updated.id ? updated : e))
|
|
await refreshSummary()
|
|
}
|
|
|
|
/** Remove a log entry from the store after a successful API DELETE, then refresh the summary. */
|
|
export async function removeLogEntryFromStore(id) {
|
|
dayData.log = dayData.log.filter(e => e.id !== id)
|
|
await refreshSummary()
|
|
}
|