diff --git a/TASKS.md b/TASKS.md index e1b6e97..dc379f1 100644 --- a/TASKS.md +++ b/TASKS.md @@ -66,12 +66,18 @@ ## Fase 3 — UI: Vista settimanale -- [ ] **T3.1** Layout vista settimanale: griglia 7 giorni × N pasti. Pranzo e cena sempre visibili. Toggle per mostrare colazione/merende (stato persistito in localStorage). -- [ ] **T3.2** Navigazione settimane: pulsanti `<` e `>`, label "Settimana del lun gg/mm". Pulsante "oggi" per tornare alla settimana corrente. -- [ ] **T3.3** Slot vuoto: pulsante "+". Slot occupato: nome piatto + chip degli Elementi. -- [ ] **T3.4** Form aggiunta piatto: nome libero + multi-select Elementi con autocomplete. Salvataggio in IndexedDB + refresh vista. -- [ ] **T3.5** Edit/Delete piatto da uno slot. -- [ ] **T3.6** Chip Elemento dentro lo slot: mostra `nome (n/max)` o solo `nome` se unlimited. Colore rosso se sforato. +- [x] **T3.1** Layout vista settimanale: griglia 7 giorni × N pasti. Pranzo e cena sempre visibili. Toggle per mostrare colazione/merende (stato persistito in localStorage). + - `settimanaStore.ts` (Pinia): week corrente, toggle `showOptionalMeals` in localStorage. `WeekView.vue` riscritto con nuova architettura: griglia CSS Grid 7×N, pasti opzionali collassabili. Build e 81 test passano. +- [x] **T3.2** Navigazione settimane: pulsanti `<` e `>`, label "Settimana del lun gg/mm". Pulsante "oggi" per tornare alla settimana corrente. + - `formatWeekLabel()` usato in `WeekView.vue`. Pulsante "Oggi" visibile solo quando non si è sulla settimana corrente. +- [x] **T3.3** Slot vuoto: pulsante "+". Slot occupato: nome piatto + chip degli Elementi. + - Slot vuoto centrato con "+" cliccabile. Slot occupato mostra dish card con nome + chip. +- [x] **T3.4** Form aggiunta piatto: nome libero + multi-select Elementi con autocomplete. Salvataggio in IndexedDB + refresh vista. + - Nuovo componente `FormAggiuntaPiatto.vue`: input nome, lista elementi con filtro testo, checkbox multipla. `addDishToSlot` + `settimanaStore.refresh()` al salvataggio. +- [x] **T3.5** Edit/Delete piatto da uno slot. + - Pulsante ✏️ riapre `FormAggiuntaPiatto` in edit mode (rimuove vecchio + inserisce aggiornato). Pulsante 🗑️ chiama `removeDishFromSlot` + refresh. +- [x] **T3.6** Chip Elemento dentro lo slot: mostra `nome (n/max)` o solo `nome` se unlimited. Colore rosso se sforato. + - `computeWeeklyFrequencies` calcolato come `computed` reattivo. `getChipData()` ritorna label e flag `exceeded`. CSS `.chip--exceeded` → rosso. ## Fase 4 — Reminder frequenze diff --git a/src/components/FormAggiuntaPiatto.vue b/src/components/FormAggiuntaPiatto.vue new file mode 100644 index 0000000..050c242 --- /dev/null +++ b/src/components/FormAggiuntaPiatto.vue @@ -0,0 +1,336 @@ + + + + + diff --git a/src/stores/settimanaStore.ts b/src/stores/settimanaStore.ts new file mode 100644 index 0000000..684006c --- /dev/null +++ b/src/stores/settimanaStore.ts @@ -0,0 +1,79 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; +import type { Week, MealType, DayOfWeek, Dish } from '../domain/types'; +import { getCurrentWeekId, nextWeek, prevWeek, weekIdToMonday } from '../domain/week'; +import { getOrCreateWeek } from '../storage/weeks'; + +const LS_KEY = 'menuPlanner.showOptionalMeals'; + +export const useSettimanaStore = defineStore('settimana', () => { + const currentWeekId = ref(getCurrentWeekId()); + const week = ref(null); + const loading = ref(false); + + // Persisted in localStorage; default true (show optional meals by default) + const showOptionalMeals = ref(localStorage.getItem(LS_KEY) !== 'false'); + + function setShowOptionalMeals(value: boolean): void { + showOptionalMeals.value = value; + localStorage.setItem(LS_KEY, String(value)); + } + + const todayWeekId = computed(() => getCurrentWeekId()); + + async function loadCurrentWeek(): Promise { + loading.value = true; + try { + week.value = await getOrCreateWeek(currentWeekId.value); + } finally { + loading.value = false; + } + } + + async function navigateTo(weekId: string): Promise { + currentWeekId.value = weekId; + await loadCurrentWeek(); + } + + async function goToNextWeek(): Promise { + await navigateTo(nextWeek(currentWeekId.value)); + } + + async function goToPrevWeek(): Promise { + await navigateTo(prevWeek(currentWeekId.value)); + } + + async function goToToday(): Promise { + await navigateTo(getCurrentWeekId()); + } + + function getDishesForSlot(day: DayOfWeek, meal: MealType): Dish[] { + if (!week.value) return []; + const slot = week.value.slots.find((s) => s.day === day && s.meal === meal); + return slot?.dishes ?? []; + } + + /** Aggiorna il ref `week` con i dati freschi da DB (usato dopo add/remove piatto). */ + async function refresh(): Promise { + week.value = await getOrCreateWeek(currentWeekId.value); + } + + // Convenience: Monday Date for current week + const currentMonday = computed(() => weekIdToMonday(currentWeekId.value)); + + return { + currentWeekId, + week, + loading, + showOptionalMeals, + todayWeekId, + currentMonday, + setShowOptionalMeals, + loadCurrentWeek, + goToNextWeek, + goToPrevWeek, + goToToday, + getDishesForSlot, + refresh, + }; +}); diff --git a/src/views/WeekView.vue b/src/views/WeekView.vue index a9449fc..a83b860 100644 --- a/src/views/WeekView.vue +++ b/src/views/WeekView.vue @@ -1,371 +1,525 @@