Show live order progress, delays, and kitchen cancellations
- Order cards derive their label from item progress (Not Started, Preparing, Partly Ready, Ready, Picked Up, Cancelled) instead of the order's own status; final states are coloured - Order page: animated status banner per item (ready/picked up check, cancelled cross, running-late clock), steps collapse once an item is settled with a Show/Hide steps toggle, done steps are faded instead of struck through, and the per-item status badge is gone - New Delay concept: machines can report extra time on a claimed step (preparation_step_instances.delay_seconds), which counts toward the ETA - The kitchen can cancel an item at any point before it's ready, with a refund (order_items.cancelled_by records who cancelled) - Dev kitchen simulation now runs server-side across every order, so a step nobody is watching no longer blocks the queue; each settled step may randomly be delayed or cancelled - Humanize snake_case statuses in the UI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { motion, useReducedMotion } from 'framer-motion'
|
||||
|
||||
import styles from './ItemStatusBanner.module.scss'
|
||||
|
||||
export type ItemStatusBannerVariant =
|
||||
'ready' | 'pickedUp' | 'cancelled' | 'delayed'
|
||||
|
||||
const iconPaths = {
|
||||
ready: ['M7.5 12.5l3 3 6-6.5'],
|
||||
pickedUp: ['M7.5 12.5l3 3 6-6.5'],
|
||||
cancelled: ['M8.5 8.5l7 7', 'M15.5 8.5l-7 7'],
|
||||
delayed: ['M12 7v5', 'M12 12l3.5 2'],
|
||||
} satisfies Record<ItemStatusBannerVariant, Array<string>>
|
||||
|
||||
type ItemStatusBannerProps = {
|
||||
variant: ItemStatusBannerVariant
|
||||
label: string
|
||||
detail?: string
|
||||
animateIn: boolean
|
||||
}
|
||||
|
||||
export const ItemStatusBanner = ({
|
||||
variant,
|
||||
label,
|
||||
detail,
|
||||
animateIn,
|
||||
}: ItemStatusBannerProps) => {
|
||||
const reduceMotion = useReducedMotion()
|
||||
const skipAnimation = !animateIn || reduceMotion
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.banner}
|
||||
data-variant={variant}
|
||||
role="status"
|
||||
initial={skipAnimation ? false : { opacity: 0, y: 4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<svg className={styles.icon} viewBox="0 0 24 24" aria-hidden="true">
|
||||
<motion.circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
initial={skipAnimation ? false : { pathLength: 0, rotate: -90 }}
|
||||
animate={{ pathLength: 1, rotate: -90 }}
|
||||
transition={{ duration: 0.5, ease: 'easeOut' }}
|
||||
/>
|
||||
{iconPaths[variant].map((d, index) => (
|
||||
<motion.path
|
||||
key={d}
|
||||
d={d}
|
||||
initial={skipAnimation ? false : { pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: 'easeOut',
|
||||
delay: 0.45 + index * 0.2,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
<span className={styles.text}>
|
||||
<span className={styles.label}>{label}</span>
|
||||
{detail && <span className={styles.detail}>{detail}</span>}
|
||||
</span>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user