127 lines
7.6 KiB
Vue
127 lines
7.6 KiB
Vue
|
|
<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 & 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 & 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>
|