From d8da4aae5e1729e052508abae4fd3fa50ff960a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ren=C3=A9=20White=20Enciso?= Date: Tue, 31 Mar 2026 19:03:54 -0600 Subject: [PATCH] feat(customer-orders-web): show shared order progression --- docs/architecture/frontend-boundary.md | 4 +- docs/runbooks/local-development.md | 4 +- docs/runbooks/testing.md | 2 +- src/App.test.tsx | 26 +++++----- src/App.tsx | 70 +++++++++++++++++++++++--- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/docs/architecture/frontend-boundary.md b/docs/architecture/frontend-boundary.md index 28cc840..2c116de 100644 --- a/docs/architecture/frontend-boundary.md +++ b/docs/architecture/frontend-boundary.md @@ -23,9 +23,9 @@ ## UI Workflow Coverage -- Customer order status dashboard with current orders +- Customer order status dashboard with current orders from the shared restaurant lifecycle - Selected order detail lookup - Recent order history and event feed -- Customer order submission and recent submission results +- Customer order submission and recent submission results with shared-lifecycle progression hints - Session-expired handling with reauthentication guidance - Protected route shell for status, submission, and session inspection diff --git a/docs/runbooks/local-development.md b/docs/runbooks/local-development.md index 777b87e..67d2098 100644 --- a/docs/runbooks/local-development.md +++ b/docs/runbooks/local-development.md @@ -25,8 +25,8 @@ npm run dev ## Available Screens -- `/status`: current order status, selected order detail, history, and recent events -- `/submit`: customer order submission and recent submission results +- `/status`: current order status, selected order detail, history, recent events, and shared-lifecycle guidance +- `/submit`: customer order submission and recent submission results with kitchen/payment readiness hints - `/session`: current Thalos session profile payload ## Build diff --git a/docs/runbooks/testing.md b/docs/runbooks/testing.md index 07ca6d3..a1194a1 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 for status, detail, history, and submit flows. - `src/auth/oidcLogin.test.ts`: OIDC start-url generation and safe return-url fallback. -- `src/App.test.tsx`: central login screen, status/detail/history loading, order submission, and session-expired reauthentication guidance. +- `src/App.test.tsx`: central login screen, shared-lifecycle order messaging, order submission progression hints, and session-expired reauthentication guidance. ## Notes diff --git a/src/App.test.tsx b/src/App.test.tsx index 5fd772e..7f10eca 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -57,20 +57,20 @@ describe('Customer Orders App', () => { vi.mocked(loadDashboard).mockResolvedValue({ contextId: 'demo-context', summary: '2 open orders', - orders: [{ orderId: 'CO-1001', tableId: 'T-08', status: 'Submitted', guestCount: 2, itemIds: ['ITEM-101'] }], + orders: [{ orderId: 'ORD-1001', tableId: 'T-08', status: 'Preparing', guestCount: 2, itemIds: ['ITEM-101'] }], recentEvents: ['status payload event'] }); vi.mocked(loadOrderHistory).mockResolvedValue({ contextId: 'demo-context', summary: 'recent history', - orders: [{ orderId: 'CO-0999', tableId: 'T-04', status: 'Completed', guestCount: 3, itemIds: ['ITEM-202'] }], - recentEvents: ['Order CO-0999 completed'] + orders: [{ orderId: 'ORD-0999', tableId: 'T-04', status: 'Served', guestCount: 3, itemIds: ['ITEM-202'] }], + recentEvents: ['Order ORD-0999 completed service and is ready for payment capture'] }); vi.mocked(loadOrderDetail).mockResolvedValue({ contextId: 'demo-context', summary: 'selected order', - order: { orderId: 'CO-1001', tableId: 'T-08', status: 'Submitted', guestCount: 2, itemIds: ['ITEM-101'] }, - recentEvents: ['Order CO-1001 confirmed'] + order: { orderId: 'ORD-1001', tableId: 'T-08', status: 'Preparing', guestCount: 2, itemIds: ['ITEM-101'] }, + recentEvents: ['Order ORD-1001 confirmed'] }); render(); @@ -80,9 +80,10 @@ describe('Customer Orders App', () => { await waitFor(() => expect(loadDashboard).toHaveBeenCalledWith('demo-context')); expect(loadOrderHistory).toHaveBeenCalledWith('demo-context'); - expect(loadOrderDetail).toHaveBeenCalledWith('demo-context', 'CO-1001'); + expect(loadOrderDetail).toHaveBeenCalledWith('demo-context', 'ORD-1001'); expect(await screen.findByText('2 open orders')).toBeInTheDocument(); - expect(await screen.findByText('Order CO-0999 completed')).toBeInTheDocument(); + expect(await screen.findByText('Order ORD-0999 completed service and is ready for payment capture')).toBeInTheDocument(); + expect(await screen.findByText('Submitted customer orders progress through kitchen preparation and become payable only after service is complete.')).toBeInTheDocument(); }); it('submits customer order from action route', async () => { @@ -94,21 +95,22 @@ describe('Customer Orders App', () => { }); vi.mocked(submitCustomerOrder).mockResolvedValue({ contextId: 'demo-context', - orderId: 'CO-2200', + orderId: 'ORD-2200', accepted: true, - summary: 'accepted', - status: 'Submitted' + summary: 'Order ORD-2200 was accepted and is ready for kitchen dispatch.', + status: 'accepted' }); render(); await waitFor(() => expect(screen.getByText('Submit Order')).toBeInTheDocument()); fireEvent.click(screen.getByText('Submit Order')); - fireEvent.change(screen.getByPlaceholderText('Order Id'), { target: { value: 'CO-2200' } }); + fireEvent.change(screen.getByPlaceholderText('Order Id'), { target: { value: 'ORD-2200' } }); fireEvent.click(screen.getByRole('button', { name: 'Submit Customer Order' })); await waitFor(() => expect(submitCustomerOrder).toHaveBeenCalledTimes(1)); - expect((await screen.findAllByText('Submitted')).length).toBeGreaterThan(0); + expect((await screen.findAllByText('accepted')).length).toBeGreaterThan(0); + expect(await screen.findByText('The kitchen should receive this order next.')).toBeInTheDocument(); }); it('shows reauthentication guidance when status loading returns session expired', async () => { diff --git a/src/App.tsx b/src/App.tsx index fa33d9f..e6ecf8f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -71,7 +71,7 @@ const orderColumns = [ { title: 'Status', dataIndex: 'status', - render: (value: string) => {value} + render: (value: string) => {value} }, { title: 'Guests', dataIndex: 'guestCount' }, { @@ -96,7 +96,7 @@ function CustomerOrdersShell() { const navigate = useNavigate(); const [contextId, setContextId] = useState('demo-context'); - const [statusOrderId, setStatusOrderId] = useState('CO-1001'); + const [statusOrderId, setStatusOrderId] = useState('ORD-1001'); const [statusPayload, setStatusPayload] = useState(null); const [detailPayload, setDetailPayload] = useState(null); const [historyPayload, setHistoryPayload] = useState(null); @@ -241,7 +241,7 @@ function CustomerOrdersShell() { Customer Orders - Protected order workflows for status, detail, history, and submission through the customer-orders BFF. + Protected order workflows for status, detail, history, and submission through the shared restaurant lifecycle. {session.error && } {workflowState.error && ( @@ -292,6 +292,9 @@ function CustomerOrdersShell() { {statusPayload.contextId} {statusPayload.summary} + + Submitted customer orders progress through kitchen preparation and become payable only after service is complete. + pagination={false} @@ -313,9 +316,12 @@ function CustomerOrdersShell() { {detailPayload.summary} {detailPayload.order.orderId} {detailPayload.order.tableId} - {detailPayload.order.status} + + {detailPayload.order.status} + {detailPayload.order.guestCount} {detailPayload.order.itemIds.join(', ')} + {orderProgressHint(detailPayload.order.status)} ) : ( @@ -354,7 +360,7 @@ function CustomerOrdersShell() { layout="vertical" initialValues={{ contextId, - orderId: 'CO-1001', + orderId: 'ORD-1001', tableId: 'T-08', guestCount: 2, itemIdsText: 'ITEM-101,ITEM-202' @@ -385,8 +391,11 @@ function CustomerOrdersShell() { {orderResponse.contextId} {orderResponse.orderId} {String(orderResponse.accepted)} - {orderResponse.status} + + {orderResponse.status} + {orderResponse.summary} + {orderProgressHint(orderResponse.status)} ) : ( @@ -406,7 +415,10 @@ function CustomerOrdersShell() { title: 'Accepted', render: (_, record) => {String(record.accepted)} }, - { title: 'Status', dataIndex: 'status' }, + { + title: 'Status', + render: (_, record) => {record.status} + }, { title: 'Summary', dataIndex: 'summary' } ]} /> @@ -447,4 +459,48 @@ function providerLabel(provider: IdentityProvider): string { return String(provider); } +function workflowTagColor(status: string): string { + switch (status.toLowerCase()) { + case 'accepted': + case 'submitted': + return 'blue'; + case 'preparing': + case 'cooking': + return 'gold'; + case 'ready': + case 'readyforpickup': + return 'cyan'; + case 'served': + return 'green'; + case 'paid': + return 'purple'; + case 'blocked': + case 'failed': + case 'canceled': + return 'red'; + default: + return 'default'; + } +} + +function orderProgressHint(status: string): string { + switch (status.toLowerCase()) { + case 'accepted': + case 'submitted': + return 'The kitchen should receive this order next.'; + case 'preparing': + case 'cooking': + return 'Kitchen is actively preparing this order.'; + case 'ready': + case 'readyforpickup': + return 'This order is ready for handoff or table delivery.'; + case 'served': + return 'The restaurant can now open payment capture for this check.'; + case 'paid': + return 'This order and its check are fully completed.'; + default: + return 'Track this order across the shared restaurant lifecycle.'; + } +} + export default App;