site-panzai/layouts/admin.vue

75 lines
3.8 KiB
Vue
Raw Normal View History

2026-11-13 13:57:04 +01:00
<script setup lang="ts">
const links = [
{ to: '/admin', label: 'Commandes', icon: 'kanban' },
{ to: '/admin/menu', label: 'Menu', icon: 'list' },
{ to: '/admin/stats', label: 'Statistiques', icon: 'chart' },
{ to: '/admin/horaires', label: 'Horaires & réglages', icon: 'clock' }
]
useHead({ title: 'PANZAÏ — Espace gérant', meta: [{ name: 'robots', content: 'noindex' }] })
const { data: orders, refresh } = useOrders()
const nNew = computed(() => (orders.value || []).filter(o => o.status === 'new').length)
// Rafraîchit les commandes toutes les 2 s et sonne à chaque nouvelle commande
const sound = useState('adminSound', () => false)
const seen = new Set<string>()
let primed = false
watch(orders, (list) => {
const fresh = (list || []).filter(o => o.status === 'new' && !seen.has(o.id))
;(list || []).forEach(o => seen.add(o.id))
if (primed && fresh.length && sound.value) ding()
primed = true
}, { immediate: true })
onMounted(() => {
const t = setInterval(() => refresh(), 2000)
onBeforeUnmount(() => clearInterval(t))
})
function enableSound() { sound.value = unlockSound(); if (sound.value) ding() }
</script>
<template>
<div class="adm">
<aside class="side">
<div class="brand"><Logo to="/admin" sub="ESPACE GÉRANT" /></div>
<nav aria-label="Dashboard" class="nav">
<NuxtLink v-for="l in links" :key="l.to" :to="l.to" :class="{ on: $route.path === l.to }">
<Icon :name="l.icon" :size="22" />
<span>{{ l.label }}</span>
<span v-if="l.to === '/admin' && nNew" class="cnt">{{ nNew }}</span>
</NuxtLink>
</nav>
<button class="btn soundb" :class="sound ? 'on' : ''" type="button" @click="enableSound">
<Icon name="sound" :size="18" /> {{ sound ? 'Son activé' : 'Activer le son' }}
</button>
<div class="foot">
<a href="/" target="_blank" rel="noopener" class="small"><Icon name="ext" :size="14" /> Voir le site client</a>
<p class="small muted">Propulsé par KNRWebsite</p>
</div>
</aside>
<div class="main"><slot /></div>
</div>
</template>
<style scoped>
.adm { min-height: 100vh; display: flex; flex-wrap: wrap; }
.side { flex: 1 1 220px; max-width: 240px; min-width: 200px; padding: 20px 14px; background: #100D0A; border-right: 1px solid rgba(243,227,195,.08); display: flex; flex-direction: column; gap: 22px; }
.brand { padding: 0 6px; }
.nav { display: flex; flex-direction: column; gap: 4px; }
.nav a { display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 0 14px; border-radius: 14px; color: var(--cream); text-decoration: none; font-weight: 700; font-size: 15px; }
.nav a:hover { background: var(--surface); }
.nav a.on { background: rgba(255,180,58,.14); color: var(--orange-1); }
.cnt { margin-left: auto; min-width: 26px; height: 26px; padding: 0 7px; border-radius: 13px; background: var(--orange-2); color: var(--bg); font-size: 13px; font-weight: 800; display: flex; align-items: center; justify-content: center; }
.soundb { background: var(--surface); color: var(--cream); border: 1px dashed rgba(243,227,195,.3); font-size: 14px; }
.soundb.on { border: 1px solid rgba(140,198,63,.5); color: var(--leaf); }
.foot { margin-top: auto; display: flex; flex-direction: column; gap: 6px; padding: 0 8px; }
.foot a { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; text-decoration: none; }
.main { flex: 999 1 560px; min-width: 0; padding: 22px clamp(14px, 2vw, 28px) 32px; }
@media (max-width: 860px) {
.side { max-width: none; flex-basis: 100%; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 10px; border-right: 0; border-bottom: 1px solid rgba(243,227,195,.08); padding: 12px; }
.nav { flex-direction: row; overflow-x: auto; flex: 1 1 100%; order: 3; }
.nav a { flex: none; min-height: 46px; }
.soundb { margin-left: auto; }
.foot { display: none; }
}
</style>