site-panzai/pages/admin/horaires.vue

127 lines
7.6 KiB
Vue
Raw Permalink Normal View History

2026-11-13 13:57:04 +01:00
<script setup lang="ts">
import type { Settings } from '~/server/utils/store'
definePageMeta({ layout: 'admin' })
const { data } = useAdminSettings()
const toast = useToast()
const { msg } = toast
// Copie locale éditable
const s = ref<Settings | null>(null)
watch(data, (v) => { if (v && !s.value) s.value = JSON.parse(JSON.stringify(v)) }, { immediate: true })
const euros = (c: number) => (c / 100).toFixed(2).replace('.', ',')
const cents = (v: string) => Math.round(Number(v.replace(',', '.').replace(/[^\d.]/g, '')) * 100) || 0
const newClosure = reactive({ date: '', label: '' })
async function save() {
if (!s.value) return
await saveSettings({
hours: s.value.hours, closures: s.value.closures, deliveryFee: s.value.deliveryFee,
freeFrom: s.value.freeFrom, minOrder: s.value.minOrder, radiusKm: s.value.radiusKm
})
toast.show('Réglages enregistrés — le site est à jour')
}
function addClosure() {
if (!s.value || !newClosure.date.trim()) return
s.value.closures.push({ date: newClosure.date.trim(), label: newClosure.label.trim() || 'Fermeture exceptionnelle' })
newClosure.date = ''; newClosure.label = ''
}
async function resetDemo() {
if (!confirm('Remettre la démo à zéro ? (commandes, menu et réglages)')) return
await $fetch('/api/reset', { method: 'POST' })
await Promise.all([refreshNuxtData('settings'), refreshNuxtData('orders'), refreshNuxtData('menu')])
s.value = JSON.parse(JSON.stringify(data.value))
toast.show('Démo remise à zéro')
}
</script>
<template>
<div v-if="s" class="stack" style="gap: 20px">
<div class="row wrap" style="gap: 14px">
<div class="grow">
<h1 style="font-size: 28px; font-weight: 800; color: #fff">Horaires &amp; réglages</h1>
<p class="small muted">Les horaires et les frais s'affichent automatiquement sur le site.</p>
</div>
<button class="btn btn-primary btn-lg" type="button" @click="save">Enregistrer</button>
</div>
<div class="row wrap" style="gap: 20px; align-items: flex-start">
<section class="card sec" style="flex: 3 1 520px" aria-labelledby="h-h">
<h2 id="h-h" class="st">Horaires d'ouverture</h2>
<div class="stack" style="gap: 8px; margin-top: 14px">
<div v-for="h in s.hours" :key="h.day" class="day">
<strong class="dn">{{ h.day }}</strong>
<button class="switch" type="button" role="switch" :aria-checked="h.open" :aria-label="`Ouvert le ${h.day}`" @click="h.open = !h.open" />
<template v-if="h.open">
<label class="hl"><span>MIDI</span><input v-model="h.midi" class="input"></label>
<label class="hl"><span>SOIR</span><input v-model="h.soir" class="input"></label>
</template>
<span v-else style="color: var(--danger-text); font-weight: 700">Fermé toute la journée</span>
</div>
</div>
</section>
<section class="card sec" style="flex: 2 1 320px" aria-labelledby="f-h">
<h2 id="f-h" class="st">Fermetures exceptionnelles</h2>
<div class="stack" style="gap: 10px; margin-top: 14px">
<div v-for="(c, i) in s.closures" :key="i" class="clo">
<span class="cd">{{ c.date }}</span>
<span class="grow strong">{{ c.label }}</span>
<button class="btn btn-icon" style="background: var(--surface-2); color: var(--cream)" type="button" :aria-label="`Supprimer ${c.label}`" @click="s.closures.splice(i, 1)"><Icon name="close" :size="16" /></button>
</div>
<div class="row" style="gap: 8px">
<input v-model="newClosure.date" class="input" style="max-width: 130px" placeholder="Date" aria-label="Date de fermeture">
<input v-model="newClosure.label" class="input" placeholder="Motif" aria-label="Motif">
</div>
<button class="btn btn-ghost" type="button" @click="addClosure">+ Ajouter une fermeture</button>
</div>
</section>
</div>
<section class="card sec" aria-labelledby="p-h">
<h2 id="p-h" class="st">Livraison &amp; commande</h2>
<div class="row wrap" style="gap: 20px; margin-top: 16px; align-items: flex-start">
<div class="stack" style="flex: 2 1 360px; gap: 12px">
<div class="zone" role="img" :aria-label="`Zone de livraison de ${s.radiusKm} km autour du restaurant`">
<span class="lake">LAC LÉMAN</span>
<span class="circle" :style="{ width: `${60 + s.radiusKm * 18}px`, height: `${60 + s.radiusKm * 18}px` }" />
<span class="pin" />
</div>
<div class="row wrap" style="gap: 8px">
<span class="strong small">Rayon</span>
<button v-for="k in [3, 5, 8, 10]" :key="k" class="opt" style="min-width: 76px" type="button" :aria-pressed="s.radiusKm === k" @click="s.radiusKm = k">{{ k }} km</button>
</div>
</div>
<div class="stack" style="flex: 1 1 260px; gap: 14px">
<label class="field"><span class="label">Frais de livraison (€)</span><input class="input money" :value="euros(s.deliveryFee)" inputmode="decimal" @change="s.deliveryFee = cents(($event.target as HTMLInputElement).value)"></label>
<label class="field"><span class="label">Livraison offerte dès (€)</span><input class="input money" :value="euros(s.freeFrom)" inputmode="decimal" @change="s.freeFrom = cents(($event.target as HTMLInputElement).value)"></label>
<label class="field"><span class="label">Minimum de commande en livraison (€)</span><input class="input money" :value="euros(s.minOrder)" inputmode="decimal" @change="s.minOrder = cents(($event.target as HTMLInputElement).value)"></label>
</div>
</div>
</section>
<div class="row" style="justify-content: flex-end">
<button class="btn btn-danger" type="button" @click="resetDemo">Remettre la démo à zéro</button>
</div>
<div v-if="msg" class="toast" role="status">{{ msg }}</div>
</div>
</template>
<style scoped>
.sec { padding: 22px; }
.st { font-size: 18px; font-weight: 800; color: #fff; }
.day { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 16px; background: var(--bg); }
.dn { width: 96px; color: #fff; }
.hl { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; letter-spacing: .8px; opacity: .95; }
.hl .input { width: 150px; min-height: 46px; background: var(--surface); }
.clo { display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 16px; background: var(--bg); }
.cd { padding: 8px 10px; border-radius: 10px; background: var(--wood-dark); color: var(--orange-1); font-size: 13px; font-weight: 800; white-space: nowrap; }
.zone { position: relative; height: 240px; border-radius: 18px; overflow: hidden; background: repeating-linear-gradient(0deg, #2A221B 0 1px, transparent 1px 26px), repeating-linear-gradient(90deg, #2A221B 0 1px, transparent 1px 26px), #1A1511; }
.zone::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 64px; background: linear-gradient(180deg, #1C2B33, #1A1511); }
.lake { position: absolute; left: 14px; top: 12px; font-size: 11px; font-weight: 800; letter-spacing: 1px; opacity: .6; }
.circle { position: absolute; left: 50%; top: 55%; transform: translate(-50%, -50%); border-radius: 50%; background: rgba(255,180,58,.14); border: 2px solid var(--orange-1); transition: all .3s ease; }
.pin { position: absolute; left: calc(50% - 12px); top: calc(55% - 26px); width: 24px; height: 24px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); background: var(--orange-2); }
.money { background: var(--bg); color: var(--orange-1); font-size: 20px; font-weight: 800; }
</style>