feat: aggiungi pulsante svuota piano settimanale

- Sposta .btn-clear da ShoppingList.vue (scoped) a style.css (globale)
- Aggiunge hasMeals, clearAll e btn-clear in MealPlanner.vue
- Aggiunge 4 test di integrazione per il nuovo pulsante

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-12 16:28:07 +02:00
parent cc866ae78c
commit f1bfa9a88c
4 changed files with 65 additions and 17 deletions
+11 -1
View File
@@ -22,6 +22,8 @@
Genera lista della spesa Genera lista della spesa
</button> </button>
<button v-if="hasMeals" class="btn-clear" @click="clearAll">Svuota piano</button>
<div class="btn-share-row"> <div class="btn-share-row">
<button class="btn-share" @click="openShare"> <button class="btn-share" @click="openShare">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -99,7 +101,7 @@
</template> </template>
<script setup> <script setup>
import { reactive, watch, ref, onUnmounted, nextTick } from 'vue' import { reactive, watch, ref, computed, onUnmounted, nextTick } from 'vue'
import MealCard from '../components/MealCard.vue' import MealCard from '../components/MealCard.vue'
import { save, load } from '../utils/storage.js' import { save, load } from '../utils/storage.js'
import QRCode from 'qrcode' import QRCode from 'qrcode'
@@ -137,6 +139,14 @@ function removeItem(day, slot, idx) {
meals[day][slot].splice(idx, 1) meals[day][slot].splice(idx, 1)
} }
const hasMeals = computed(() =>
days.some(d => ['colazione', 'pranzo', 'cena'].some(s => meals[d.id][s].length > 0))
)
function clearAll() {
if (confirm('Svuotare tutto il piano settimanale?')) Object.assign(meals, defaultMeals())
}
function generateShopping() { function generateShopping() {
const allItems = days.flatMap(d => const allItems = days.flatMap(d =>
['colazione', 'pranzo', 'cena'].flatMap(slot => meals[d.id][slot]) ['colazione', 'pranzo', 'cena'].flatMap(slot => meals[d.id][slot])
-15
View File
@@ -144,21 +144,6 @@ function clearAll() {
background: var(--color-border); background: var(--color-border);
} }
/* bottone distruttivo in ghost style — meno aggressivo del solid rosso */
.btn-clear {
width: 100%;
background: transparent;
color: var(--color-danger);
border: 1.5px solid var(--color-danger);
margin-top: 16px;
font-weight: 600;
}
.btn-clear:active {
background: var(--color-danger-muted);
opacity: 1;
}
.empty-state { .empty-state {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
+15
View File
@@ -78,6 +78,21 @@ button {
button:active { opacity: 0.75; } button:active { opacity: 0.75; }
/* bottone distruttivo in ghost style — meno aggressivo del solid rosso */
.btn-clear {
width: 100%;
background: transparent;
color: var(--color-danger);
border: 1.5px solid var(--color-danger);
margin-top: 16px;
font-weight: 600;
}
.btn-clear:active {
background: var(--color-danger-muted);
opacity: 1;
}
input[type="text"], input[type="text"],
input[type="number"] { input[type="number"] {
font-size: 1rem; font-size: 1rem;
+39 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { nextTick } from 'vue' import { nextTick } from 'vue'
import MealPlanner from '../../src/pages/MealPlanner.vue' import MealPlanner from '../../src/pages/MealPlanner.vue'
@@ -150,3 +150,41 @@ describe('MealPlanner — genera lista della spesa', () => {
expect(load('shopping', [])).toHaveLength(0) expect(load('shopping', [])).toHaveLength(0)
}) })
}) })
describe('MealPlanner — svuota piano', () => {
it('non mostra il pulsante se il piano è vuoto', () => {
const w = mountPlanner()
expect(w.find('.btn-clear').exists()).toBe(false)
})
it('mostra il pulsante se c\'è almeno un pasto', () => {
seedMeals({ lunedi: { colazione: ['caffè'], pranzo: [], cena: [] } })
const w = mountPlanner()
expect(w.find('.btn-clear').exists()).toBe(true)
})
it('svuota il piano dopo conferma', async () => {
vi.spyOn(window, 'confirm').mockReturnValue(true)
seedMeals({ lunedi: { colazione: ['caffè'], pranzo: ['pasta'], cena: [] } })
const w = mountPlanner()
await w.find('.btn-clear').trigger('click')
await nextTick()
const saved = load('meals', {})
expect(saved.lunedi.colazione).toHaveLength(0)
expect(saved.lunedi.pranzo).toHaveLength(0)
expect(w.find('.btn-clear').exists()).toBe(false)
})
it('non svuota se l\'utente annulla', async () => {
vi.spyOn(window, 'confirm').mockReturnValue(false)
seedMeals({ lunedi: { colazione: ['caffè'], pranzo: [], cena: [] } })
const w = mountPlanner()
await w.find('.btn-clear').trigger('click')
await nextTick()
expect(load('meals', {}).lunedi.colazione).toHaveLength(1)
})
})