diff --git a/docs/architecture/frontend-boundary.md b/docs/architecture/frontend-boundary.md index 00f46fb..d6e27e5 100644 --- a/docs/architecture/frontend-boundary.md +++ b/docs/architecture/frontend-boundary.md @@ -23,8 +23,8 @@ ## UI Workflow Coverage -- Waiter assignment snapshot with location metadata and active-order counts +- Waiter assignment snapshot with location metadata and active-order counts derived from the shared restaurant lifecycle - Recent waiter activity history feed -- Floor order submission and order update workflows +- Floor order submission and order update workflows that feed the shared restaurant order/check model - Session-expired handling with reauthentication guidance - Protected route shell for assignments, order actions, and session inspection diff --git a/docs/runbooks/local-development.md b/docs/runbooks/local-development.md index b46de46..e0c9bd9 100644 --- a/docs/runbooks/local-development.md +++ b/docs/runbooks/local-development.md @@ -26,7 +26,7 @@ npm run dev ## Available Screens - `/assignments`: waiter assignment snapshot and recent activity feed -- `/orders`: floor order submit and update actions +- `/orders`: floor order submit and update actions with shared-lifecycle progression hints - `/session`: current Thalos session profile payload ## Build diff --git a/docs/runbooks/testing.md b/docs/runbooks/testing.md index f896e88..19ef30a 100644 --- a/docs/runbooks/testing.md +++ b/docs/runbooks/testing.md @@ -17,7 +17,7 @@ npm run test:ci - `src/api/client.test.ts`: runtime-config precedence and fallback behavior. - `src/api/dashboardApi.test.ts`: endpoint path/query composition, activity loading, and order update payload mapping. - `src/auth/oidcLogin.test.ts`: OIDC start-url generation and safe return-url fallback. -- `src/App.test.tsx`: central login screen, assignment and activity loading, order submit/update workflows, and session-expired reauthentication guidance. +- `src/App.test.tsx`: central login screen, shared-lifecycle assignment messaging, order submit/update progression hints, and session-expired reauthentication guidance. ## Notes diff --git a/src/App.test.tsx b/src/App.test.tsx index 81d179b..56412fd 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -58,7 +58,7 @@ describe('Waiter Floor App', () => { contextId: 'demo-context', locationId: 'floor-a', summary: '2 waiters assigned', - assignments: [{ waiterId: 'w-1', tableId: 'T-12', status: 'Assigned', activeOrders: 3 }], + assignments: [{ waiterId: 'service-pool', tableId: 'T-12', status: 'Preparing', activeOrders: 3 }], recentActivity: ['legacy assignment feed'] }); vi.mocked(loadRecentActivity).mockResolvedValue({ @@ -77,6 +77,7 @@ describe('Waiter Floor App', () => { expect(loadRecentActivity).toHaveBeenCalledWith('demo-context'); expect(await screen.findByText('floor-a')).toBeInTheDocument(); expect(await screen.findByText('Waiter w-1 picked up table T-12')).toBeInTheDocument(); + expect(await screen.findByText('Floor actions create or update shared restaurant orders that kitchen and POS observe next.')).toBeInTheDocument(); }); it('submits and updates floor orders from the order route', async () => { @@ -90,16 +91,16 @@ describe('Waiter Floor App', () => { contextId: 'demo-context', orderId: 'ORD-2200', accepted: true, - summary: 'submitted', - status: 'Queued', + summary: 'Order ORD-2200 was accepted and is ready for kitchen dispatch.', + status: 'accepted', processedAtUtc: '2026-03-31T12:00:00Z' }); vi.mocked(updateFloorOrder).mockResolvedValue({ contextId: 'demo-context', orderId: 'ORD-2200', accepted: true, - summary: 'updated', - status: 'Updated', + summary: 'Updated order ORD-2200. Order ORD-2200 was accepted and is ready for kitchen dispatch.', + status: 'accepted', processedAtUtc: '2026-03-31T12:05:00Z' }); @@ -111,13 +112,14 @@ describe('Waiter Floor App', () => { fireEvent.click(screen.getByRole('button', { name: 'Submit Floor Order' })); await waitFor(() => expect(submitFloorOrder).toHaveBeenCalledTimes(1)); - expect((await screen.findAllByText('Queued')).length).toBeGreaterThan(0); + expect((await screen.findAllByText('accepted')).length).toBeGreaterThan(0); + expect(await screen.findByText('Kitchen should pick this order up next.')).toBeInTheDocument(); fireEvent.change(screen.getAllByPlaceholderText('Order Id')[1], { target: { value: 'ORD-2200' } }); fireEvent.click(screen.getByRole('button', { name: 'Update Floor Order' })); await waitFor(() => expect(updateFloorOrder).toHaveBeenCalledTimes(1)); - expect((await screen.findAllByText('Updated')).length).toBeGreaterThan(0); + expect((await screen.findAllByText('accepted')).length).toBeGreaterThan(0); }); it('shows reauthentication guidance when the workflow returns session expired', async () => { diff --git a/src/App.tsx b/src/App.tsx index 72141c7..27014ed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -66,7 +66,7 @@ const assignmentColumns = [ { title: 'Status', dataIndex: 'status', - render: (value: string) => {value} + render: (value: string) => {value} }, { title: 'Active Orders', dataIndex: 'activeOrders' } ]; @@ -227,7 +227,7 @@ function WaiterFloorShell() { Waiter Floor Operations - Protected floor workflows for assignment visibility, recent activity, and order submit or update actions. + Protected floor workflows for assignment visibility, recent activity, and order actions that now feed the same restaurant lifecycle used by kitchen and POS. {session.error && } {workflowState.error && ( @@ -273,6 +273,9 @@ function WaiterFloorShell() { {assignments.contextId} {assignments.locationId} {assignments.summary} + + Floor actions create or update shared restaurant orders that kitchen and POS observe next. + + + New floor orders are accepted into the shared restaurant lifecycle first, then they progress through kitchen preparation and payment readiness. +
+ + Updates keep the same shared order identity so the downstream kitchen and POS views stay consistent. + {lastOrderResponse.contextId} {lastOrderResponse.orderId} {String(lastOrderResponse.accepted)} - {lastOrderResponse.status} + + {lastOrderResponse.status} + {lastOrderResponse.summary} + {orderProgressHint(lastOrderResponse.status)} {lastOrderResponse.processedAtUtc} ) : ( @@ -387,7 +399,10 @@ function WaiterFloorShell() { render: (_, record) => {record.kind} }, { title: 'Order Id', render: (_, record) => record.response.orderId }, - { title: 'Status', render: (_, record) => record.response.status }, + { + title: 'Status', + render: (_, record) => {record.response.status} + }, { title: 'Summary', render: (_, record) => record.response.summary }, { title: 'Processed At', render: (_, record) => record.response.processedAtUtc } ]} @@ -429,4 +444,45 @@ function providerLabel(provider: IdentityProvider): string { return String(provider); } +function workflowTagColor(status: string): string { + switch (status.toLowerCase()) { + case 'accepted': + return 'blue'; + case 'preparing': + case 'cooking': + return 'gold'; + case 'ready': + case 'readyforpickup': + return 'cyan'; + case 'served': + case 'paid': + return 'green'; + case 'blocked': + case 'failed': + case 'canceled': + return 'red'; + default: + return 'default'; + } +} + +function orderProgressHint(status: string): string { + switch (status.toLowerCase()) { + case 'accepted': + return 'Kitchen should pick this order up next.'; + case 'preparing': + case 'cooking': + return 'Kitchen is actively preparing this order.'; + case 'ready': + case 'readyforpickup': + return 'The order is ready for handoff or service.'; + case 'served': + return 'POS can now treat this check as payable.'; + case 'paid': + return 'This restaurant check is fully closed.'; + default: + return 'Track this order across the shared restaurant lifecycle.'; + } +} + export default App;