diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud-port-3020.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud-port-3020.png new file mode 100644 index 00000000..b8a1be5f Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud-port-3020.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud.png new file mode 100644 index 00000000..f722702c Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate-port-3020.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate-port-3020.png new file mode 100644 index 00000000..1b33b340 Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate-port-3020.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate.png new file mode 100644 index 00000000..cb334428 Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/03-admin-happy-hour-crud-port-3020.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/03-admin-happy-hour-crud-port-3020.png new file mode 100644 index 00000000..9e905d47 Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/03-admin-happy-hour-crud-port-3020.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/04-pos-queue-display-port-3020.png b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/04-pos-queue-display-port-3020.png new file mode 100644 index 00000000..aa4c6ec8 Binary files /dev/null and b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/04-pos-queue-display-port-3020.png differ diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/README.md b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/README.md new file mode 100644 index 00000000..01275ce2 --- /dev/null +++ b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/README.md @@ -0,0 +1,28 @@ +# Cafe Admin Table/QR/Happy-Hour Fixes - Port 3020 + +Chrome test target: `http://localhost:3020` + +Shop: `GoodGo Cafe MVP` (`0bc5ab7c-0b6d-4746-a2ef-c61307b90199`) + +## Workflow Screenshots + +1. `01-admin-tables-crud-port-3020.png` + - `/admin/shop/:shopId/tables` + - Confirms the Cafe admin route is no longer 404. + - Creates table `QA-920111` through the admin CRUD form. + +2. `02-admin-qr-regenerate-port-3020.png` + - `/admin/shop/:shopId/qr-codes` + - Selects `QR bàn QA-920111`. + - Regenerates QR from `/table/ca767ce906b94401` to `/table/c1b3754b580e419e`. + +3. `03-admin-happy-hour-crud-port-3020.png` + - `/admin/shop/:shopId/happy-hour` + - Confirms route and campaign-backed CRUD console. + - Creates campaign `Happy QA 81191`. + +4. `04-pos-queue-display-port-3020.png` + - `/pos/:shopId/cafe/queue-display` + - Confirms the active queue lane title is `Chờ pha / đang pha`, matching tickets that are still `Chờ pha`. + +Structured Chrome results are stored in `chrome-table-qr-results.json`. diff --git a/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/chrome-table-qr-results.json b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/chrome-table-qr-results.json new file mode 100644 index 00000000..04ab6af4 --- /dev/null +++ b/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/chrome-table-qr-results.json @@ -0,0 +1,43 @@ +{ + "baseUrl": "http://localhost:3020", + "shopId": "0bc5ab7c-0b6d-4746-a2ef-c61307b90199", + "tableNumber": "QA-920111", + "happyHourName": "Happy QA 81191", + "checks": { + "tablesRoute": { + "url": "http://localhost:3020/admin/shop/0bc5ab7c-0b6d-4746-a2ef-c61307b90199/tables", + "title": "Bàn/phòng", + "hasCrud": true, + "createdVisible": true, + "successMessage": "Tạo mới thành công" + }, + "qrRoute": { + "url": "http://localhost:3020/admin/shop/0bc5ab7c-0b6d-4746-a2ef-c61307b90199/qr-codes", + "title": "QR bàn/phòng", + "selectedText": "QR bàn QA-920111 · Bàn/phòng", + "beforeToken": "/table/ca767ce906b94401", + "afterToken": "/table/c1b3754b580e419e", + "tokenChanged": true, + "successMessage": "Tạo lại QR thành công" + }, + "happyHourRoute": { + "url": "http://localhost:3020/admin/shop/0bc5ab7c-0b6d-4746-a2ef-c61307b90199/happy-hour", + "title": "Khung giờ ưu đãi", + "hasCrud": true, + "createdVisible": true, + "successMessage": "Tạo mới thành công" + }, + "queueDisplay": { + "url": "http://localhost:3020/pos/0bc5ab7c-0b6d-4746-a2ef-c61307b90199/cafe/queue-display", + "title": "Màn hình chờ lấy nước", + "activeLaneTitle": "Chờ pha / đang pha", + "titleMatchesPendingTickets": true + } + }, + "files": { + "tablesCrud": "/Users/velikho/Desktop/WORKING/pos-system/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/01-admin-tables-crud-port-3020.png", + "qrRegenerate": "/Users/velikho/Desktop/WORKING/pos-system/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/02-admin-qr-regenerate-port-3020.png", + "happyHourCrud": "/Users/velikho/Desktop/WORKING/pos-system/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/03-admin-happy-hour-crud-port-3020.png", + "queueDisplay": "/Users/velikho/Desktop/WORKING/pos-system/microservices/apps/tpos-mvp-next/output/demo-cafe-e2e-audit-3020/22-admin-table-qr-fixes/04-pos-queue-display-port-3020.png" + } +} diff --git a/microservices/apps/tpos-mvp-next/src/app/admin/[...path]/page.tsx b/microservices/apps/tpos-mvp-next/src/app/admin/[...path]/page.tsx index 410110b9..76f59ba0 100644 --- a/microservices/apps/tpos-mvp-next/src/app/admin/[...path]/page.tsx +++ b/microservices/apps/tpos-mvp-next/src/app/admin/[...path]/page.tsx @@ -371,7 +371,7 @@ async function loadItems(section: string, shop?: Shop | null, allowedShopIds?: s id: String(row.id), kind: "leave-request", title: String(row.reason ?? "Nghỉ phép"), - meta: `${formatDate(row.from_date)} - ${formatDate(row.to_date)} · ${`${row.first_name ?? "Nhân viên"} ${row.last_name ?? ""}`.trim()}`, + meta: `${formatDateOnly(row.from_date)} - ${formatDateOnly(row.to_date)} · ${`${row.first_name ?? "Nhân viên"} ${row.last_name ?? ""}`.trim()}`, value: displayStatus(row.status), edit: { staffId: String(row.staff_id ?? ""), @@ -405,7 +405,18 @@ async function loadItems(section: string, shop?: Shop | null, allowedShopIds?: s if (section === "promotions" || section === "happy-hour") { const campaigns = await listCampaigns(shopId); return campaigns - .map((item) => ({ id: String(item.id), kind: "campaign", title: String(item.name), meta: String(item.description ?? "Chiến dịch"), value: displayStatus(item.status) })); + .map((item) => ({ + id: String(item.id), + kind: "campaign", + title: String(item.name), + meta: String(item.description ?? "Chiến dịch"), + value: displayStatus(item.status), + edit: { + description: String(item.description ?? ""), + amount: String(item.discount_value ?? item.face_value ?? ""), + status: String(item.status ?? "draft") + } + })); } if (section === "appointments" || section === "spa/appointments") { const appointments = shopId ? await listAppointments(shopId) : []; @@ -623,6 +634,10 @@ function displayStatus(value: unknown, fallback = "Chưa cấu hình") { draft: "Đang thiết lập", published: "Đang hoạt động", pending: "Chờ xử lý", + checked_in: "Đã check-in", + checked_out: "Đã check-out", + approved: "Đã duyệt", + rejected: "Từ chối", current: "Đang làm", next: "Tiếp theo", done: "Đã xong", @@ -715,7 +730,11 @@ function isKnownAdminSection(section: string, shop?: Shop | null) { "doctors", "history", "orders", + "tables", + "rooms", + "zones", "qr-codes", + "happy-hour", "reports/eod", "reports/revenue", "reports/staff" @@ -791,6 +810,14 @@ function formatDate(value: unknown) { return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString("vi-VN"); } +function formatDateOnly(value: unknown) { + if (!value) return ""; + const date = new Date(String(value)); + return Number.isNaN(date.getTime()) + ? String(value) + : date.toLocaleDateString("vi-VN", { day: "2-digit", month: "2-digit", year: "numeric" }); +} + function formatReportDay(value: unknown) { if (!value) return ""; const date = new Date(String(value)); diff --git a/microservices/apps/tpos-mvp-next/src/components/TposWorkflowScreen.tsx b/microservices/apps/tpos-mvp-next/src/components/TposWorkflowScreen.tsx index 705e1180..5ce91255 100644 --- a/microservices/apps/tpos-mvp-next/src/components/TposWorkflowScreen.tsx +++ b/microservices/apps/tpos-mvp-next/src/components/TposWorkflowScreen.tsx @@ -562,7 +562,7 @@ export function WorkflowScreen({ )) :
Chưa có món sẵn sàng
}
-

Đang pha chế

+

Chờ pha / đang pha

{activeItems.length ? activeItems.map((item) => (
{pickupNumberFromValue(item.order_id ?? item.id)} diff --git a/microservices/apps/tpos-mvp-next/src/components/admin/reference/sectionViews.tsx b/microservices/apps/tpos-mvp-next/src/components/admin/reference/sectionViews.tsx index 25a7b4de..6f63ef5e 100644 --- a/microservices/apps/tpos-mvp-next/src/components/admin/reference/sectionViews.tsx +++ b/microservices/apps/tpos-mvp-next/src/components/admin/reference/sectionViews.tsx @@ -114,6 +114,7 @@ const crudSections = new Set([ "staff", "customers", "promotions", + "happy-hour", "drive", "receipt-templates", "ai-chat" @@ -526,8 +527,8 @@ function AdminCrudConsole({ section, shop, vertical, items }: { section: string; const amountName = selectedKind === "campaign" ? "discountValue" : selectedKind === "member" ? "points" : selectedKind === "inventory" ? "costPerUnit" : selectedKind === "file" ? "byteSize" : selectedKind === "table" ? "hourlyRate" : "price"; const statusName = selectedKind === "staff" ? "role" : selectedKind === "inventory" ? "unit" : selectedKind === "file" ? "accessLevel" : selectedKind === "table" ? "statusId" : "status"; const descriptionLabel = selectedKind === "file" ? "MIME type" : selectedKind === "folder" ? "ID thư mục cha" : selectedKind === "table" ? "Khu vực" : "Mô tả / ghi chú"; - const amountLabel = selectedKind === "file" ? "Dung lượng byte" : selectedKind === "table" ? "Giá theo giờ" : "Giá / chi phí / điểm"; - const statusLabel = selectedKind === "file" ? "Quyền truy cập" : selectedKind === "folder" ? "Trạng thái" : selectedKind === "table" ? "Trạng thái bàn/phòng" : "Trạng thái / vai trò / đơn vị"; + const amountLabel = selectedKind === "file" ? "Dung lượng byte" : selectedKind === "table" ? "Giá theo giờ" : selectedKind === "campaign" ? "Giá trị giảm" : "Giá / chi phí / điểm"; + const statusLabel = selectedKind === "file" ? "Quyền truy cập" : selectedKind === "folder" ? "Trạng thái" : selectedKind === "table" ? "Trạng thái bàn/phòng" : selectedKind === "campaign" ? "Trạng thái" : "Trạng thái / vai trò / đơn vị"; const deleteLabel = selectedKind === "file" || selectedKind === "folder" ? "Ẩn metadata" : selectedKind === "receipt-template" ? "Tắt mẫu" : "Xóa / tắt"; const canEditAmountAndStatus = selectedKind !== "folder"; const categoryItems = items.filter((item) => item.kind === "category" && item.id); @@ -623,6 +624,44 @@ function AdminCrudConsole({ section, shop, vertical, items }: { section: string;
+ ) : selectedKind === "table" ? ( + <> +
+ + +
+
+ + +
+
+ + +
+ ) : selectedKind === "receipt-template" ? ( <>
@@ -758,6 +797,13 @@ function AdminCrudConsole({ section, shop, vertical, items }: { section: string;
) : null} + {selectedItem?.kind === "table" ? ( +
+ + {selectedItem.edit?.qrToken ? Mở menu QR : null} +
+ ) : null} + {message ?
{message}
: null} @@ -780,6 +826,7 @@ function createEndpoint(action: string) { product: "products", inventory: "inventory/items", recipe: "recipes", + table: "tables", staff: "staff", member: "members", campaign: "campaigns", @@ -793,9 +840,10 @@ function createEndpoint(action: string) { function crudCoverageLabel(section: string) { if (section === "ai-chat") return "Cấu hình"; - if (section === "drive") return "Metadata thư mục/tệp"; - if (section === "recipes") return "Recipe CRUD"; - return "Create · Update · Delete"; + if (section === "drive") return "Thông tin thư mục/tệp"; + if (section === "recipes") return "Tạo · Sửa · Xóa công thức"; + if (section === "tables" || section === "rooms" || section === "qr-codes") return "Bàn/phòng · QR"; + return "Tạo · Sửa · Xóa"; } function kindLabel(kind?: string) { @@ -804,6 +852,7 @@ function kindLabel(kind?: string) { product: "Sản phẩm", inventory: "Tồn kho", recipe: "Công thức", + table: "Bàn/phòng", staff: "Nhân sự", member: "Khách hàng", campaign: "Khuyến mãi", @@ -857,6 +906,24 @@ function renderCreateForms(section: string, onSubmit: (event: FormEvent ); } + if (section === "tables" || section === "rooms" || section === "qr-codes") { + const isRoom = section === "rooms" || vertical === "karaoke"; + return ( +
+ +

{isRoom ? "Tạo phòng" : "Tạo bàn"}

+
+ + +
+
+ + +
+ +
+ ); + } if (section === "inventory") { return (
@@ -917,18 +984,24 @@ function renderCreateForms(section: string, onSubmit: (event: FormEvent ); } - if (section === "promotions") { + if (section === "promotions" || section === "happy-hour") { + const isHappyHour = section === "happy-hour"; return ( -

Tạo khuyến mãi

+

{isHappyHour ? "Tạo khung ưu đãi" : "Tạo khuyến mãi"}

- +
+ +
+ + +
- + ); } diff --git a/microservices/apps/tpos-mvp-next/src/components/admin/reference/utils.ts b/microservices/apps/tpos-mvp-next/src/components/admin/reference/utils.ts index 84955929..a68d95e0 100644 --- a/microservices/apps/tpos-mvp-next/src/components/admin/reference/utils.ts +++ b/microservices/apps/tpos-mvp-next/src/components/admin/reference/utils.ts @@ -73,7 +73,7 @@ export function shopPrimaryAction(shopId: string, vertical: VerticalKind, sectio return { href: `/admin/shop/${shopId}/ai-chat#crud-console`, label: "Cấu hình AI", Icon: Database }; } if (["tables", "rooms", "zones", "qr-codes", "reservations"].includes(section)) { - return { href: `/admin/shop/${shopId}/${section}`, label: section === "rooms" ? "Quản lý phòng" : "Quản lý bàn", Icon: PlusCircle }; + return { href: `/admin/shop/${shopId}/${section}#crud-console`, label: section === "rooms" ? "Quản lý phòng" : section === "qr-codes" ? "Tạo QR" : "Quản lý bàn", Icon: PlusCircle }; } if (["history", "orders"].includes(section)) { return { href: `/pos/${shopId}/${vertical}?tab=history&filter=30d`, label: "Mở lịch sử bán hàng", Icon: Receipt };