site-panzai/pages/commande/[id].vue

120 lines
6.6 KiB
Vue
Raw Normal View History

2026-11-13 13:57:04 +01:00
<script setup lang="ts">
import type { Order } from '~/server/utils/store'
const route = useRoute()
const { data: order, refresh, error } = await useFetch<Order>(`/api/orders/${route.params.id}`, { key: `order-${route.params.id}` })
useHead({ title: () => order.value ? `Commande #${order.value.num} — PANZAÏ` : 'Suivi de commande — PANZAÏ' })
// Le suivi se met à jour tout seul quand le resto change le statut
onMounted(() => {
const t = setInterval(() => refresh(), 2500)
onBeforeUnmount(() => clearInterval(t))
})
const liv = computed(() => order.value?.mode === 'livraison')
const idx = computed(() => ({ new: 0, prep: 1, ready: 2, done: 3, refused: -1 } as const)[order.value?.status || 'new'])
const steps = computed(() => [
{ label: 'Commande reçue', sub: order.value ? hhmm(order.value.createdAt) : '' },
{ label: 'En préparation au wok 🔥', sub: order.value?.readyBy ? `prête vers ${hhmm(order.value.readyBy)}` : 'en attente de confirmation du resto' },
{ label: liv.value ? 'En livraison' : 'Prête — viens la récupérer', sub: liv.value ? 'ton livreur arrive' : '2 avenue de Champagne' },
{ label: liv.value ? 'Livrée' : 'Récupérée', sub: 'bon appétit !' }
])
const headline = computed(() => {
if (!order.value) return ''
if (order.value.status === 'refused') return 'Commande annulée'
return ['C’est reçu !', 'Ça chauffe au wok', liv.value ? 'Ça arrive !' : 'C’est prêt !', 'Bon appétit !'][idx.value]
})
const eta = computed(() => {
const o = order.value
if (!o) return ''
if (o.status === 'done') return liv.value ? 'Livrée' : 'Récupérée'
if (o.status === 'refused') return '—'
if (o.status === 'ready' && !liv.value) return 'Maintenant'
if (o.readyBy) return hhmm(o.readyBy + (liv.value ? 12 * 60000 : 0))
return '~' + hhmm(o.createdAt + (liv.value ? 35 : 20) * 60000)
})
</script>
<template>
<main class="container wrap-c">
<div v-if="error" class="card" style="padding: 28px; text-align: center">
<p class="strong">Commande introuvable.</p>
<NuxtLink to="/" class="btn btn-primary" style="margin-top: 16px">Retour à l'accueil</NuxtLink>
</div>
<template v-else-if="order">
<LeafMonstera class="leaf" />
<div class="toucan-in" style="width: 90px"><Toucan :size="90" label="Toucan PANZAÏ" /></div>
<p class="small muted" style="margin-top: 12px">Commande <strong style="color: #fff">#{{ order.num }}</strong></p>
<h1 class="display" style="font-size: clamp(30px, 5vw, 46px); margin-top: 6px" aria-live="polite">{{ headline }}</h1>
<div v-if="order.status === 'refused'" class="card refused">
<p>Le restaurant n'a pas pu accepter ta commande. <template v-if="order.refunded">Tu as été intégralement remboursé.</template></p>
<a href="tel:+33485478262" class="btn btn-primary">Appeler le resto</a>
</div>
<template v-else>
<p class="muted" style="margin-top: 8px">On te prévient par SMS à chaque étape.</p>
<div class="card eta">
<div>
<p class="eyebrow">{{ liv ? 'Arrivée estimée' : 'Prête vers' }}</p>
<p class="eta-v">{{ eta }}</p>
</div>
<div style="text-align: right">
<p class="small muted">{{ liv ? 'Livraison' : 'Retrait' }}</p>
<p class="strong" style="font-size: 14px">{{ liv ? order.address : order.slot }}</p>
</div>
</div>
<ol class="steps" aria-label="Étapes de la commande">
<li v-for="(s, i) in steps" :key="i" :class="{ done: i < idx || (idx === 3 && i === 3), cur: i === idx && idx !== 3 }">
<span class="node">
<Icon v-if="i < idx || (idx === 3 && i === 3)" name="check" :stroke="3" />
<Icon v-else-if="i === idx" name="flame" :stroke="2.4" />
</span>
<div>
<p class="strong">{{ s.label }}</p>
<p class="small muted">{{ s.sub }}</p>
</div>
</li>
</ol>
</template>
<section class="card recap" aria-label="Détail de la commande">
<div v-for="(it, i) in order.items" :key="i" class="row" style="justify-content: space-between; gap: 12px">
<span>{{ it.qty }}× {{ it.name }} {{ it.size }} · {{ it.meat }}<template v-if="it.sauce"> · {{ it.sauce }}</template></span>
<strong>{{ eur(it.unit * it.qty) }}</strong>
</div>
<div v-if="order.fee" class="row muted" style="justify-content: space-between"><span>Livraison</span><span>{{ eur(order.fee) }}</span></div>
<div class="row tot" style="justify-content: space-between">
<span class="strong">{{ order.refunded ? 'Remboursé' : order.paid ? `Payé · ${payLabel[order.payment]}` : 'À régler au retrait' }}</span>
<span class="price" style="font-size: 18px">{{ eur(order.total) }}</span>
</div>
</section>
<div class="stack" style="gap: 10px; margin-top: 20px">
<a href="tel:+33485478262" class="btn btn-primary btn-lg"><Icon name="phone" /> Appeler le resto</a>
<NuxtLink to="/menu" class="btn btn-ghost">Recommander</NuxtLink>
</div>
</template>
</main>
</template>
<style scoped>
.wrap-c { position: relative; max-width: 620px; padding-top: 28px; padding-bottom: 40px; }
.leaf { position: absolute; right: -60px; top: 0; width: 170px; z-index: -1; }
.eta { margin-top: 20px; padding: 18px 20px; display: flex; align-items: center; gap: 16px; justify-content: space-between; }
.eta-v { font-size: 34px; font-weight: 800; color: #fff; letter-spacing: -.5px; }
.steps { list-style: none; margin: 26px 0 0; padding: 0; }
.steps li { position: relative; display: flex; gap: 16px; padding-bottom: 28px; }
.steps li:not(:last-child)::before { content: ''; position: absolute; left: 19px; top: 44px; bottom: 4px; width: 3px; border-radius: 2px; background: rgba(243,227,195,.14); }
.steps li.done:not(:last-child)::before { background: var(--leaf); }
.node { width: 40px; height: 40px; border-radius: 50%; border: 2px solid rgba(243,227,195,.25); flex: none; display: flex; align-items: center; justify-content: center; color: var(--bg); }
.done .node { background: var(--leaf); border: 0; }
.cur .node { background: var(--grad); border: 0; animation: ring 1.4s ease-out infinite; }
.steps li > div { padding-top: 8px; }
.refused { margin-top: 18px; padding: 20px; display: flex; flex-direction: column; gap: 14px; align-items: flex-start; border: 1px solid rgba(232,98,42,.45); }
.recap { margin-top: 8px; padding: 18px; display: flex; flex-direction: column; gap: 8px; font-size: 14px; }
.recap .tot { margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--line); }
</style>