Animate the ETA, hide delays, and tidy the order card actions
- Slide the "Ready in about" value when it changes (new EtaMessage) - Drop the "Running late" banner; delays only move the ETA now - Remove the cancel-info tooltip and its icon button - Shorten the steps toggle to "Steps" / "Hide" - Add icons and more padding to the order card action buttons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -38,7 +38,7 @@ One stage of preparing an Order Item, requiring a Machine carrying a specific Ma
|
|||||||
_Avoid_: Stage (use Step consistently)
|
_Avoid_: Stage (use Step consistently)
|
||||||
|
|
||||||
**Delay**:
|
**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**:
|
**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.
|
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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<p className={styles.eta}>
|
||||||
|
{prefix}{' '}
|
||||||
|
<span className={styles.value}>
|
||||||
|
<AnimatePresence initial={false} mode="popLayout">
|
||||||
|
<motion.span
|
||||||
|
key={value}
|
||||||
|
className={styles.valueText}
|
||||||
|
initial={reduceMotion ? false : { y: '100%', opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={reduceMotion ? { opacity: 0 } : { y: '-100%', opacity: 0 }}
|
||||||
|
transition={{ duration: reduceMotion ? 0 : 0.35, ease: 'easeOut' }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</motion.span>
|
||||||
|
</AnimatePresence>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,10 +9,6 @@
|
|||||||
&[data-variant='cancelled'] {
|
&[data-variant='cancelled'] {
|
||||||
color: $color-danger;
|
color: $color-danger;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-variant='delayed'] {
|
|
||||||
color: $color-warning;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import { motion, useReducedMotion } from 'framer-motion'
|
|||||||
|
|
||||||
import styles from './ItemStatusBanner.module.scss'
|
import styles from './ItemStatusBanner.module.scss'
|
||||||
|
|
||||||
export type ItemStatusBannerVariant =
|
export type ItemStatusBannerVariant = 'ready' | 'pickedUp' | 'cancelled'
|
||||||
'ready' | 'pickedUp' | 'cancelled' | 'delayed'
|
|
||||||
|
|
||||||
const iconPaths = {
|
const iconPaths = {
|
||||||
ready: ['M7.5 12.5l3 3 6-6.5'],
|
ready: ['M7.5 12.5l3 3 6-6.5'],
|
||||||
pickedUp: ['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'],
|
cancelled: ['M8.5 8.5l7 7', 'M15.5 8.5l-7 7'],
|
||||||
delayed: ['M12 7v5', 'M12 12l3.5 2'],
|
|
||||||
} satisfies Record<ItemStatusBannerVariant, Array<string>>
|
} satisfies Record<ItemStatusBannerVariant, Array<string>>
|
||||||
|
|
||||||
type ItemStatusBannerProps = {
|
type ItemStatusBannerProps = {
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
@use '../../styles/variables' as *;
|
@use '../../styles/variables' as *;
|
||||||
|
|
||||||
@position-try --cancel-hint-flip {
|
|
||||||
position-area: bottom;
|
|
||||||
--hint-arrow-top: auto;
|
|
||||||
--hint-arrow-bottom: 100%;
|
|
||||||
--hint-arrow-color-top: transparent;
|
|
||||||
--hint-arrow-color-bottom: #{$color-text};
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -277,12 +269,6 @@
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eta {
|
|
||||||
margin: 0 0 $space-2;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: $color-success;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -291,7 +277,10 @@
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
a {
|
a {
|
||||||
padding: $space-1 $space-3;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $space-1;
|
||||||
|
padding: $space-2 $space-4;
|
||||||
border: 1px solid $color-border;
|
border: 1px solid $color-border;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
background: $color-background;
|
background: $color-background;
|
||||||
@@ -310,66 +299,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoButton {
|
.actionIcon {
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
gap: $space-1;
|
|
||||||
padding: $space-1 $space-2;
|
|
||||||
color: $color-text-muted;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
color: $color-primary;
|
|
||||||
border-color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoIcon {
|
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancelHint {
|
|
||||||
position: fixed;
|
|
||||||
position-area: top;
|
|
||||||
position-try-fallbacks: --cancel-hint-flip;
|
|
||||||
inset: auto;
|
|
||||||
overflow: visible;
|
|
||||||
height: auto;
|
|
||||||
margin: $space-2;
|
|
||||||
padding: $space-2 $space-3;
|
|
||||||
width: max-content;
|
|
||||||
max-width: 260px;
|
|
||||||
border: none;
|
|
||||||
border-radius: $radius;
|
|
||||||
background: $color-text;
|
|
||||||
color: $color-background;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
text-align: left;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
|
|
||||||
--hint-arrow-top: 100%;
|
|
||||||
--hint-arrow-bottom: auto;
|
|
||||||
--hint-arrow-color-top: #{$color-text};
|
|
||||||
--hint-arrow-color-bottom: transparent;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: var(--hint-arrow-top);
|
|
||||||
bottom: var(--hint-arrow-bottom);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border: 6px solid transparent;
|
|
||||||
border-top-color: var(--hint-arrow-color-top);
|
|
||||||
border-bottom-color: var(--hint-arrow-color-bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancelButton {
|
.cancelButton {
|
||||||
padding: $space-1 $space-2;
|
padding: $space-1 $space-2;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import type { OrderStatus } from '#/db/schema.ts'
|
import type { OrderStatus } from '#/db/schema.ts'
|
||||||
import type { OrderItemStatusView } from '#/domain/ordering.ts'
|
import type { OrderItemStatusView } from '#/domain/ordering.ts'
|
||||||
import { formatDuration, formatEtaMessage } from '#/lib/format.ts'
|
|
||||||
import {
|
import {
|
||||||
advanceSimulatedKitchen,
|
advanceSimulatedKitchen,
|
||||||
cancelOrderItem,
|
cancelOrderItem,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
} from '#/server/orders.ts'
|
} from '#/server/orders.ts'
|
||||||
|
|
||||||
import styles from './OrderStatusView.module.scss'
|
import styles from './OrderStatusView.module.scss'
|
||||||
|
import { EtaMessage } from './EtaMessage.tsx'
|
||||||
import { ItemStatusBanner } from './ItemStatusBanner.tsx'
|
import { ItemStatusBanner } from './ItemStatusBanner.tsx'
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
@@ -55,14 +55,6 @@ const getItemBanner = (item: OrderItemStatusView) => {
|
|||||||
detail: "You've been refunded for this item.",
|
detail: "You've been refunded for this item.",
|
||||||
} as const)
|
} as const)
|
||||||
}
|
}
|
||||||
if (item.delaySeconds > 0) {
|
|
||||||
return {
|
|
||||||
animationKey: 'delayed',
|
|
||||||
variant: 'delayed',
|
|
||||||
label: 'Running late',
|
|
||||||
detail: `About ${formatDuration(item.delaySeconds)} behind schedule`,
|
|
||||||
} as const
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,20 +63,6 @@ const bannerKey = (item: OrderItemStatusView) => {
|
|||||||
return banner ? `${item.id}:${banner.animationKey}` : null
|
return banner ? `${item.id}:${banner.animationKey}` : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelBlockedReason = (item: OrderItemStatusView) => {
|
|
||||||
if (
|
|
||||||
item.cancellable ||
|
|
||||||
item.status === 'cancelled' ||
|
|
||||||
item.status === 'picked_up'
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (item.status === 'ready') {
|
|
||||||
return "This item is already done cooking, so it's too late to cancel. If something's wrong with it, use Get help below."
|
|
||||||
}
|
|
||||||
return "This item has already started cooking, so the kitchen can't stop or undo it. If something's wrong, use Get help below."
|
|
||||||
}
|
|
||||||
|
|
||||||
const BackArrowIcon = () => (
|
const BackArrowIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
className={styles.backArrow}
|
className={styles.backArrow}
|
||||||
@@ -101,9 +79,24 @@ const BackArrowIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
const InfoIcon = () => (
|
const actionIconPaths = {
|
||||||
|
buyAgain: ['M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8', 'M3 3v5h5'],
|
||||||
|
pickedUp: [
|
||||||
|
'M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z',
|
||||||
|
'M3 6h18',
|
||||||
|
'M16 10a4 4 0 0 1-8 0',
|
||||||
|
],
|
||||||
|
help: [
|
||||||
|
'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20Z',
|
||||||
|
'M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3',
|
||||||
|
'M12 17h.01',
|
||||||
|
],
|
||||||
|
cancel: ['M18 6 6 18', 'M6 6l12 12'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionIcon = ({ name }: { name: keyof typeof actionIconPaths }) => (
|
||||||
<svg
|
<svg
|
||||||
className={styles.infoIcon}
|
className={styles.actionIcon}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -112,9 +105,9 @@ const InfoIcon = () => (
|
|||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<circle cx="12" cy="12" r="9" />
|
{actionIconPaths[name].map((d) => (
|
||||||
<line x1="12" y1="11" x2="12" y2="16" />
|
<path key={d} d={d} />
|
||||||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
))}
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -262,7 +255,6 @@ export const OrderStatusView = ({
|
|||||||
{error && <p className={styles.error}>{error}</p>}
|
{error && <p className={styles.error}>{error}</p>}
|
||||||
<ul className={styles.items}>
|
<ul className={styles.items}>
|
||||||
{status.items.map((item) => {
|
{status.items.map((item) => {
|
||||||
const cancelHint = cancelBlockedReason(item)
|
|
||||||
const banner = getItemBanner(item)
|
const banner = getItemBanner(item)
|
||||||
return (
|
return (
|
||||||
<li key={item.id} className={styles.item}>
|
<li key={item.id} className={styles.item}>
|
||||||
@@ -340,17 +332,13 @@ export const OrderStatusView = ({
|
|||||||
aria-controls={`steps-${item.id}`}
|
aria-controls={`steps-${item.id}`}
|
||||||
onClick={() => toggleSteps(item.id)}
|
onClick={() => toggleSteps(item.id)}
|
||||||
>
|
>
|
||||||
{expandedItemIds.has(item.id)
|
{expandedItemIds.has(item.id) ? 'Hide' : 'Steps'}
|
||||||
? 'Hide steps'
|
|
||||||
: 'Show steps'}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{item.etaSeconds !== null && (
|
{item.etaSeconds !== null && (
|
||||||
<p className={styles.eta}>
|
<EtaMessage etaSeconds={item.etaSeconds} />
|
||||||
{formatEtaMessage(item.etaSeconds)}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<button
|
<button
|
||||||
@@ -360,6 +348,7 @@ export const OrderStatusView = ({
|
|||||||
//
|
//
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ActionIcon name="buyAgain" />
|
||||||
Buy again
|
Buy again
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -375,7 +364,7 @@ export const OrderStatusView = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
I picked this up
|
<ActionIcon name="pickedUp" />I picked this up
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
@@ -386,32 +375,9 @@ export const OrderStatusView = ({
|
|||||||
status: item.status,
|
status: item.status,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ActionIcon name="help" />
|
||||||
Get help
|
Get help
|
||||||
</Link>
|
</Link>
|
||||||
{cancelHint && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={styles.infoButton}
|
|
||||||
popoverTarget={`cancel-hint-${item.id}`}
|
|
||||||
style={{ anchorName: `--cancel-hint-anchor-${item.id}` }}
|
|
||||||
>
|
|
||||||
<InfoIcon />
|
|
||||||
Why can't I cancel?
|
|
||||||
</button>
|
|
||||||
<p
|
|
||||||
id={`cancel-hint-${item.id}`}
|
|
||||||
popover="auto"
|
|
||||||
role="tooltip"
|
|
||||||
className={styles.cancelHint}
|
|
||||||
style={{
|
|
||||||
positionAnchor: `--cancel-hint-anchor-${item.id}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cancelHint}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{item.cancellable && (
|
{item.cancellable && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -425,6 +391,7 @@ export const OrderStatusView = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<ActionIcon name="cancel" />
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -789,8 +789,6 @@ export type OrderItemStatusView = {
|
|||||||
menuItemName: string
|
menuItemName: string
|
||||||
status: schema.OrderItemStatus
|
status: schema.OrderItemStatus
|
||||||
cancelledBy: schema.OrderItemCancelledBy | null
|
cancelledBy: schema.OrderItemCancelledBy | null
|
||||||
// Total delay reported across the item's Steps so far.
|
|
||||||
delaySeconds: number
|
|
||||||
cancellable: boolean
|
cancellable: boolean
|
||||||
steps: Array<{
|
steps: Array<{
|
||||||
sequence: number
|
sequence: number
|
||||||
@@ -1016,7 +1014,6 @@ export const getOrderStatus = async (
|
|||||||
menuItemName: menuItem?.name ?? '',
|
menuItemName: menuItem?.name ?? '',
|
||||||
status: item.status,
|
status: item.status,
|
||||||
cancelledBy: item.cancelledBy,
|
cancelledBy: item.cancelledBy,
|
||||||
delaySeconds: instances.reduce((sum, row) => sum + row.delaySeconds, 0),
|
|
||||||
cancellable:
|
cancellable:
|
||||||
item.status !== 'cancelled' &&
|
item.status !== 'cancelled' &&
|
||||||
item.status !== 'ready' &&
|
item.status !== 'ready' &&
|
||||||
|
|||||||
+3
-3
@@ -58,9 +58,9 @@ export const formatEtaClock = (etaSeconds: number, now = new Date()) => {
|
|||||||
|
|
||||||
const ABSOLUTE_TIME_THRESHOLD_SECONDS = 3 * 60 * 60
|
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
|
etaSeconds > ABSOLUTE_TIME_THRESHOLD_SECONDS
|
||||||
? `Ready ${formatEtaClock(etaSeconds, now)}`
|
? { prefix: 'Ready', value: formatEtaClock(etaSeconds, now) }
|
||||||
: `Ready in about ${formatDuration(etaSeconds)}`
|
: { prefix: 'Ready in about', value: formatDuration(etaSeconds) }
|
||||||
|
|
||||||
export const formatStatus = (status: string) => status.replaceAll('_', ' ')
|
export const formatStatus = (status: string) => status.replaceAll('_', ' ')
|
||||||
|
|||||||
+18
-6
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { formatDuration, formatEtaClock, formatEtaMessage } from '#/lib/format.ts'
|
import { formatDuration, formatEtaClock, formatEtaParts } from '#/lib/format.ts'
|
||||||
|
|
||||||
describe('formatDuration', () => {
|
describe('formatDuration', () => {
|
||||||
it('rounds up to at least 1 minute', () => {
|
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)
|
const noon = new Date(2026, 0, 15, 12, 0, 0)
|
||||||
|
|
||||||
it('shows the relative time at or under the 3 hour threshold', () => {
|
it('shows the relative time at or under the 3 hour threshold', () => {
|
||||||
expect(formatEtaMessage(34 * 60, noon)).toBe('Ready in about 34 min')
|
expect(formatEtaParts(34 * 60, noon)).toEqual({
|
||||||
expect(formatEtaMessage(3 * 60 * 60, noon)).toBe('Ready in about 3 hr')
|
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', () => {
|
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(formatEtaParts(3 * 60 * 60 + 60, noon)).toEqual({
|
||||||
expect(formatEtaMessage(230 * 60, noon)).toBe('Ready today, 03:50 PM')
|
prefix: 'Ready',
|
||||||
|
value: 'today, 03:01 PM',
|
||||||
|
})
|
||||||
|
expect(formatEtaParts(230 * 60, noon)).toEqual({
|
||||||
|
prefix: 'Ready',
|
||||||
|
value: 'today, 03:50 PM',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -667,7 +667,6 @@ describe('kitchen simulation', () => {
|
|||||||
|
|
||||||
const after = (await getStatus(order)).items[0]
|
const after = (await getStatus(order)).items[0]
|
||||||
expect(after.steps[0].status).toBe('in_progress')
|
expect(after.steps[0].status).toBe('in_progress')
|
||||||
expect(after.delaySeconds).toBe(180)
|
|
||||||
expect(after.etaSeconds).toBeGreaterThanOrEqual(before.etaSeconds! + 170)
|
expect(after.etaSeconds).toBeGreaterThanOrEqual(before.etaSeconds! + 170)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user