diff --git a/CONTEXT.md b/CONTEXT.md index 712cd7a..4809491 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -38,7 +38,7 @@ One stage of preparing an Order Item, requiring a Machine carrying a specific Ma _Avoid_: Stage (use Step consistently) **Delay**: -Extra time a Machine reports on top of a claimed Preparation Step's estimated duration. Delays add to the Order Item's time to completion and are shown to the customer as the item running late. +Extra time a Machine reports on top of a claimed Preparation Step's estimated duration. Delays add to the Order Item's time to completion; the customer only sees the updated time, never a delay notice. **Machine**: A physical cooking hardware unit at a Kitchen (e.g. an oven, a grill), carrying one or more Machine Tags. A Kitchen may have multiple Machines carrying the same Tag, working in parallel. diff --git a/src/components/orders/EtaMessage.module.scss b/src/components/orders/EtaMessage.module.scss new file mode 100644 index 0000000..47e5531 --- /dev/null +++ b/src/components/orders/EtaMessage.module.scss @@ -0,0 +1,19 @@ +@use '../../styles/variables' as *; + +.eta { + margin: 0 0 $space-2; + font-size: 0.9rem; + color: $color-success; +} + +.value { + position: relative; + display: inline-flex; + overflow: hidden; + vertical-align: bottom; +} + +.valueText { + display: inline-block; + white-space: nowrap; +} diff --git a/src/components/orders/EtaMessage.tsx b/src/components/orders/EtaMessage.tsx new file mode 100644 index 0000000..bb6a435 --- /dev/null +++ b/src/components/orders/EtaMessage.tsx @@ -0,0 +1,30 @@ +import { AnimatePresence, motion, useReducedMotion } from 'framer-motion' + +import { formatEtaParts } from '#/lib/format.ts' + +import styles from './EtaMessage.module.scss' + +export const EtaMessage = ({ etaSeconds }: { etaSeconds: number }) => { + const reduceMotion = useReducedMotion() + const { prefix, value } = formatEtaParts(etaSeconds) + + return ( +
+ {prefix}{' '}
+
+
{error}
}- {formatEtaMessage(item.etaSeconds)} -
+- {cancelHint} -
- > - )} {item.cancellable && ( )} diff --git a/src/domain/ordering.ts b/src/domain/ordering.ts index 317430a..3df6191 100644 --- a/src/domain/ordering.ts +++ b/src/domain/ordering.ts @@ -789,8 +789,6 @@ export type OrderItemStatusView = { menuItemName: string status: schema.OrderItemStatus cancelledBy: schema.OrderItemCancelledBy | null - // Total delay reported across the item's Steps so far. - delaySeconds: number cancellable: boolean steps: Array<{ sequence: number @@ -1016,7 +1014,6 @@ export const getOrderStatus = async ( menuItemName: menuItem?.name ?? '', status: item.status, cancelledBy: item.cancelledBy, - delaySeconds: instances.reduce((sum, row) => sum + row.delaySeconds, 0), cancellable: item.status !== 'cancelled' && item.status !== 'ready' && diff --git a/src/lib/format.ts b/src/lib/format.ts index 9e37b96..97f63dc 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -58,9 +58,9 @@ export const formatEtaClock = (etaSeconds: number, now = new Date()) => { const ABSOLUTE_TIME_THRESHOLD_SECONDS = 3 * 60 * 60 -export const formatEtaMessage = (etaSeconds: number, now = new Date()) => +export const formatEtaParts = (etaSeconds: number, now = new Date()) => etaSeconds > ABSOLUTE_TIME_THRESHOLD_SECONDS - ? `Ready ${formatEtaClock(etaSeconds, now)}` - : `Ready in about ${formatDuration(etaSeconds)}` + ? { prefix: 'Ready', value: formatEtaClock(etaSeconds, now) } + : { prefix: 'Ready in about', value: formatDuration(etaSeconds) } export const formatStatus = (status: string) => status.replaceAll('_', ' ') diff --git a/tests/format.test.ts b/tests/format.test.ts index 90eac65..e8a2f38 100644 --- a/tests/format.test.ts +++ b/tests/format.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { formatDuration, formatEtaClock, formatEtaMessage } from '#/lib/format.ts' +import { formatDuration, formatEtaClock, formatEtaParts } from '#/lib/format.ts' describe('formatDuration', () => { it('rounds up to at least 1 minute', () => { @@ -39,16 +39,28 @@ describe('formatEtaClock', () => { }) }) -describe('formatEtaMessage', () => { +describe('formatEtaParts', () => { const noon = new Date(2026, 0, 15, 12, 0, 0) it('shows the relative time at or under the 3 hour threshold', () => { - expect(formatEtaMessage(34 * 60, noon)).toBe('Ready in about 34 min') - expect(formatEtaMessage(3 * 60 * 60, noon)).toBe('Ready in about 3 hr') + expect(formatEtaParts(34 * 60, noon)).toEqual({ + prefix: 'Ready in about', + value: '34 min', + }) + expect(formatEtaParts(3 * 60 * 60, noon)).toEqual({ + prefix: 'Ready in about', + value: '3 hr', + }) }) it('shows the absolute time once the eta passes 3 hours', () => { - expect(formatEtaMessage(3 * 60 * 60 + 60, noon)).toBe('Ready today, 03:01 PM') - expect(formatEtaMessage(230 * 60, noon)).toBe('Ready today, 03:50 PM') + expect(formatEtaParts(3 * 60 * 60 + 60, noon)).toEqual({ + prefix: 'Ready', + value: 'today, 03:01 PM', + }) + expect(formatEtaParts(230 * 60, noon)).toEqual({ + prefix: 'Ready', + value: 'today, 03:50 PM', + }) }) }) diff --git a/tests/ordering.test.ts b/tests/ordering.test.ts index c366395..bc4a05e 100644 --- a/tests/ordering.test.ts +++ b/tests/ordering.test.ts @@ -667,7 +667,6 @@ describe('kitchen simulation', () => { const after = (await getStatus(order)).items[0] expect(after.steps[0].status).toBe('in_progress') - expect(after.delaySeconds).toBe(180) expect(after.etaSeconds).toBeGreaterThanOrEqual(before.etaSeconds! + 170) })