site-panzai/components/CustomizeModal.vue

149 lines
6.9 KiB
Vue
Raw Permalink Normal View History

2026-11-13 13:57:04 +01:00
<script setup lang="ts">
import type { Size } from '~/server/utils/store'
const { dish, close } = useCustomize()
const { extras } = useShop()
const cart = useCart()
const toast = useToast()
const size = ref<Size>('XL')
const meat = ref('')
const sauce = ref('')
const picked = ref<string[]>([])
const note = ref('')
const qty = ref(1)
watch(dish, (d) => {
if (!d) return
const sizes = sizesOf(d).filter(s => d.prices[s] != null)
size.value = sizes.includes('XL') ? 'XL' : sizes[0]
meat.value = d.meats.includes('Poulet') ? 'Poulet' : (d.meats[0] || '')
sauce.value = d.sauces?.[0] || ''
picked.value = []
note.value = ''
qty.value = 1
})
const unit = computed(() => {
if (!dish.value) return 0
const base = dish.value.prices[size.value] ?? 0
return base + picked.value.reduce((a, id) => a + (extras.value.find(e => e.id === id)?.price || 0), 0)
})
function toggle(id: string) {
picked.value = picked.value.includes(id) ? picked.value.filter(x => x !== id) : [...picked.value, id]
}
function add() {
const d = dish.value
if (!d) return
cart.add({
dishId: d.id, name: d.name, img: d.img, size: size.value,
meat: meat.value || 'Légumes', sauce: d.sauces ? sauce.value : undefined,
extras: [...picked.value], extrasLabel: picked.value.map(id => extras.value.find(e => e.id === id)?.label || id),
note: note.value.trim() || undefined, qty: qty.value, unit: unit.value
})
toast.show(`${qty.value}× ${d.name} ajouté au panier`)
close()
}
function onKey(e: KeyboardEvent) { if (e.key === 'Escape' && dish.value) close() }
onMounted(() => window.addEventListener('keydown', onKey))
onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
</script>
<template>
<Teleport to="body">
<div v-if="dish" class="overlay" @click.self="close">
<section class="sheet cz" role="dialog" aria-modal="true" aria-labelledby="cz-title">
<div class="cz-photo wood">
<span class="grab" aria-hidden="true" />
<DishPhoto :img="dish.img" :alt="dish.name" />
<button class="btn btn-icon closeb" type="button" aria-label="Fermer" @click="close"><Icon name="close" :stroke="2.6" /></button>
</div>
<div class="cz-body">
<div>
<div class="row" style="gap: 10px; flex-wrap: wrap">
<h2 id="cz-title" class="display" style="font-size: 28px">{{ dish.name }}</h2>
<span v-if="dish.badge" class="badge" :class="`badge-${dish.badge.tone}`">{{ dish.badge.label }}</span>
</div>
<p class="muted" style="margin-top: 6px; font-size: 14px">{{ dish.desc }}</p>
</div>
<fieldset>
<legend class="label">Taille</legend>
<div class="opts">
<button v-for="s in sizesOf(dish)" :key="s" class="opt" type="button" :aria-pressed="size === s" :disabled="dish.prices[s] == null" @click="size = s">
<strong style="font-size: 17px">{{ s }}</strong><span class="price" style="font-size: 13px">{{ eur(dish.prices[s]) }}</span>
</button>
</div>
</fieldset>
<fieldset v-if="dish.meats.length">
<legend class="label">Viande</legend>
<div class="opts">
<button v-for="m in dish.meats" :key="m" class="opt" type="button" :aria-pressed="meat === m" @click="meat = m">{{ m }}</button>
</div>
</fieldset>
<fieldset v-if="dish.sauces">
<legend class="label">Sauce</legend>
<div class="opts">
<button v-for="s in dish.sauces" :key="s" class="opt" type="button" :aria-pressed="sauce === s" @click="sauce = s">{{ s }}</button>
</div>
</fieldset>
<fieldset v-if="dish.cat === 'wok' || dish.cat === 'baguette'">
<legend class="label">Suppléments</legend>
<div class="stack" style="gap: 8px">
<button v-for="e in extras" :key="e.id" class="extra" type="button" :aria-pressed="picked.includes(e.id)" @click="toggle(e.id)">
<span class="box"><Icon v-if="picked.includes(e.id)" name="check" :size="14" :stroke="3.4" /></span>
<span class="grow">{{ e.label }}</span>
<span class="price">+{{ eur(e.price) }}</span>
</button>
</div>
</fieldset>
<div class="field">
<label class="label" for="cz-note">Note pour la cuisine</label>
<textarea id="cz-note" v-model="note" class="input" rows="2" maxlength="200" placeholder="Ex. : sans oignons frits, sauce à part…" />
</div>
</div>
<div class="cz-foot">
<div class="qty">
<button type="button" aria-label="Retirer un" @click="qty = Math.max(1, qty - 1)">−</button>
<span aria-live="polite">{{ qty }}</span>
<button type="button" aria-label="Ajouter un" @click="qty = Math.min(20, qty + 1)">+</button>
</div>
<button class="btn btn-primary btn-lg grow" type="button" @click="add">Ajouter — {{ eur(unit * qty) }}</button>
</div>
</section>
</div>
</Teleport>
</template>
<style scoped>
.cz { display: flex; flex-direction: column; }
.cz-photo { position: relative; height: 190px; flex: none; }
.cz-photo :deep(.dish-photo) { position: absolute; inset: 18px 18%; }
.grab { position: absolute; left: 50%; top: 10px; width: 40px; height: 5px; margin-left: -20px; border-radius: 3px; background: rgba(243,227,195,.5); }
.closeb { position: absolute; right: 14px; top: 18px; background: rgba(20,16,13,.88); color: var(--cream); border-radius: 50%; }
.cz-body { padding: 20px 20px 8px; display: flex; flex-direction: column; gap: 20px; }
fieldset { margin: 0; padding: 0; border: 0; }
legend { margin-bottom: 10px; }
.opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 8px; }
.extra { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 14px; border-radius: 14px; border: 1px solid var(--line); background: var(--bg); color: var(--cream); cursor: pointer; font-weight: 600; text-align: left; }
.box { width: 22px; height: 22px; border-radius: 7px; border: 2px solid rgba(243,227,195,.35); display: flex; align-items: center; justify-content: center; color: var(--bg); flex: none; }
.extra[aria-pressed="true"] .box { background: var(--grad); border: 0; }
.cz-foot { position: sticky; bottom: 0; display: flex; align-items: center; gap: 12px; padding: 14px 20px calc(18px + env(safe-area-inset-bottom)); background: var(--surface); border-top: 1px solid var(--line); }
@media (min-width: 768px) {
.cz { display: grid; grid-template-columns: 300px 1fr; grid-template-rows: 1fr auto; }
.cz-photo { height: auto; grid-row: 1 / 3; border-radius: 28px 0 0 28px; }
.cz-photo :deep(.dish-photo) { inset: 25% 8%; }
.grab { display: none; }
.closeb { left: 14px; right: auto; }
.cz-body { padding: 26px 26px 8px; }
.cz-foot { padding: 16px 26px 22px; border-radius: 0 0 28px 0; }
}
</style>