site-panzai/components/CustomizeModal.vue
2026-11-13 13:57:04 +01:00

148 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>