feat(restaurant): customer-friendly order status labels
Order status came through to the customer as raw operational
strings — 'paid', 'accepted', 'ready'. These are fine for the
operator's KDS but unfriendly for the customer waiting on their
food.
types/restaurant.ts:
+ FRIENDLY_ORDER_STATUS map (status → label)
pending → 'Awaiting payment'
paid → 'Order received'
accepted → 'Cooking'
ready → 'Ready for pickup'
completed → 'Served'
canceled → 'Canceled'
refunded → 'Refunded'
+ friendlyOrderStatus(status) helper. Unknown statuses (future
kitchen-workflow values from aiolabs/restaurant#4 — e.g.
'preparing', 'plating', 'in_service') fall through to a
titlecased version of the raw key so the build stays green
and the surface stays readable.
views/OrderStatusPage.vue:
- Status Badge uses friendlyOrderStatus().
- Alert sections now have one per status with appropriate copy:
paid → 'Order received / Payment confirmed — the
kitchen will start preparing it shortly.'
accepted → 'Cooking / Your food is being made.'
ready → 'Ready for pickup / Pick up at the counter.'
completed → 'Served / Enjoy! Thanks for ordering.'
views/CheckoutPage.vue: Phase 2 status badge uses
friendlyOrderStatus() so the checkout's live per-restaurant
status pill matches the language on the order page.
Deeper kitchen workflow (prep stations, courses, ETA, per-station
status) stays on aiolabs/restaurant#4 — this commit is the cheap
win that ships with the existing data model unchanged.
This commit is contained in:
parent
1d815652c4
commit
15545c9b5e
3 changed files with 57 additions and 6 deletions
|
|
@ -233,6 +233,34 @@ export const KNOWN_ORDER_STATUSES = [
|
||||||
export type KnownOrderStatus = (typeof KNOWN_ORDER_STATUSES)[number]
|
export type KnownOrderStatus = (typeof KNOWN_ORDER_STATUSES)[number]
|
||||||
export type OrderStatus = string
|
export type OrderStatus = string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer-facing labels for order statuses. The extension's raw
|
||||||
|
* status names are operational ('paid' / 'accepted' / 'ready') but
|
||||||
|
* customers prefer human-friendly framing ('Order received' /
|
||||||
|
* 'Cooking' / 'Ready for pickup').
|
||||||
|
*
|
||||||
|
* Future statuses from aiolabs/restaurant#4 (kitchen workflow) —
|
||||||
|
* 'preparing', 'plating', 'at_pass', 'in_service', etc — can land
|
||||||
|
* here as they arrive. Unknown values fall through to the raw
|
||||||
|
* status string titlecased.
|
||||||
|
*/
|
||||||
|
export const FRIENDLY_ORDER_STATUS: Record<string, string> = {
|
||||||
|
pending: 'Awaiting payment',
|
||||||
|
paid: 'Order received',
|
||||||
|
accepted: 'Cooking',
|
||||||
|
ready: 'Ready for pickup',
|
||||||
|
completed: 'Served',
|
||||||
|
canceled: 'Canceled',
|
||||||
|
refunded: 'Refunded',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function friendlyOrderStatus(status: OrderStatus): string {
|
||||||
|
if (status in FRIENDLY_ORDER_STATUS) return FRIENDLY_ORDER_STATUS[status]
|
||||||
|
// Unknown status — titlecase the raw key as a graceful fallback.
|
||||||
|
if (!status) return ''
|
||||||
|
return status.charAt(0).toUpperCase() + status.slice(1).replace(/_/g, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
export interface Order {
|
export interface Order {
|
||||||
id: string
|
id: string
|
||||||
restaurant_id: string
|
restaurant_id: string
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import { Separator } from '@/components/ui/separator'
|
||||||
import OrderInvoiceCard from '../components/OrderInvoiceCard.vue'
|
import OrderInvoiceCard from '../components/OrderInvoiceCard.vue'
|
||||||
import { useCartStore } from '../stores/cart'
|
import { useCartStore } from '../stores/cart'
|
||||||
import { useCheckout, type PlacedOrder } from '../composables/useCheckout'
|
import { useCheckout, type PlacedOrder } from '../composables/useCheckout'
|
||||||
|
import { friendlyOrderStatus } from '../types/restaurant'
|
||||||
import {
|
import {
|
||||||
injectService,
|
injectService,
|
||||||
tryInjectService,
|
tryInjectService,
|
||||||
|
|
@ -383,7 +384,7 @@ function buildOrderInvoice(p: PlacedOrder) {
|
||||||
:variant="isPaid(placed.order.id) ? 'default' : 'outline'"
|
:variant="isPaid(placed.order.id) ? 'default' : 'outline'"
|
||||||
class="text-xs"
|
class="text-xs"
|
||||||
>
|
>
|
||||||
{{ statusOf(placed.order.id) }}
|
{{ friendlyOrderStatus(statusOf(placed.order.id)) }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<OrderInvoiceCard
|
<OrderInvoiceCard
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import OrderInvoiceCard from '../components/OrderInvoiceCard.vue'
|
||||||
import { useOrder } from '../composables/useOrder'
|
import { useOrder } from '../composables/useOrder'
|
||||||
import {
|
import {
|
||||||
KNOWN_ORDER_STATUSES,
|
KNOWN_ORDER_STATUSES,
|
||||||
|
friendlyOrderStatus,
|
||||||
type OrderInvoice,
|
type OrderInvoice,
|
||||||
} from '../types/restaurant'
|
} from '../types/restaurant'
|
||||||
|
|
||||||
|
|
@ -150,7 +151,7 @@ const timeline = computed(() => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge :variant="statusStyle" class="text-sm">
|
<Badge :variant="statusStyle" class="text-sm">
|
||||||
{{ order.status }}
|
{{ friendlyOrderStatus(order.status) }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -161,13 +162,25 @@ const timeline = computed(() => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
v-else-if="['paid', 'accepted'].includes(order.status)"
|
v-else-if="order.status === 'paid'"
|
||||||
class="mb-4 border-emerald-500/40"
|
class="mb-4 border-emerald-500/40"
|
||||||
>
|
>
|
||||||
<CheckCircle2 class="h-4 w-4 text-emerald-500" />
|
<CheckCircle2 class="h-4 w-4 text-emerald-500" />
|
||||||
<AlertTitle>Payment received</AlertTitle>
|
<AlertTitle>Order received</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
The kitchen is on it.
|
Payment confirmed — the kitchen will start preparing it
|
||||||
|
shortly.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
v-else-if="order.status === 'accepted'"
|
||||||
|
class="mb-4 border-emerald-500/40"
|
||||||
|
>
|
||||||
|
<CheckCircle2 class="h-4 w-4 text-emerald-500" />
|
||||||
|
<AlertTitle>Cooking</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Your food is being made.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|
@ -176,12 +189,21 @@ const timeline = computed(() => {
|
||||||
class="mb-4 border-amber-500/40"
|
class="mb-4 border-amber-500/40"
|
||||||
>
|
>
|
||||||
<Clock class="h-4 w-4 text-amber-500" />
|
<Clock class="h-4 w-4 text-amber-500" />
|
||||||
<AlertTitle>Ready</AlertTitle>
|
<AlertTitle>Ready for pickup</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Pick up at the counter.
|
Pick up at the counter.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
v-else-if="order.status === 'completed'"
|
||||||
|
class="mb-4 border-emerald-500/40"
|
||||||
|
>
|
||||||
|
<CheckCircle2 class="h-4 w-4 text-emerald-500" />
|
||||||
|
<AlertTitle>Served</AlertTitle>
|
||||||
|
<AlertDescription>Enjoy! Thanks for ordering.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Card class="mb-4">
|
<Card class="mb-4">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-base">Items</CardTitle>
|
<CardTitle class="text-base">Items</CardTitle>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue