UI — @kioskgaming/ui / portal-cash-settlement
3 màn hình mockup (sell modal, list, detail view-only) dùng chung Admin / Super Agent / Agent portal → implement trong packages/ui, mỗi portal chỉ wire API + route + mở modal từ entity detail.
Mockup: prototype · Schema: schema-cash-transactions
Pattern tham chiếu
Giống các module sẵn có:
| Module | Ví dụ |
|---|---|
portal-credit-wallet | PortalCreditTransactionsLedger — list + filter + table |
portal-usd-wallet | Summary grid, filters bar, ledger table |
portal-ledger | Detail modal, presentation helpers |
Cash settlement làm tương tự: UI + format + form state trong package; fetch/submit inject qua props/callback.
Cấu trúc thư mục đề xuất
packages/ui/src/portal-cash-settlement/
├── index.ts
├── types.ts # CashTransactionRow, filters, sell form values, flow context
├── constants.ts # flow/status/payment_method labels (EN)
├── formatters.ts # formatCash, formatCredits, formatCostRate
├── cashTransactionPresentation.ts # badge tone, flow label, status label
├── useSellCreditsCashForm.ts # expected cash calc, validation
├── SellCreditsCashModal.tsx # Modal bán credit (entity detail)
├── PortalCashTransactionSummaryGrid.tsx
├── PortalCashTransactionFiltersBar.tsx
├── PortalCashTransactionTable.tsx
├── PortalCashTransactionsLedger.tsx # Compose list page (summary + filter + table + pagination)
├── PortalCashTransactionDetail.tsx # View-only detail page body
└── *.test.ts
Export từ packages/ui/src/index.ts:
export { … } from './portal-cash-settlement';
Component map ↔ mockup
| Mockup | Component package | Ghi chú |
|---|---|---|
| sell-credit.html | SellCreditsCashModal | Context bar: flow, buyer name, cost rate — không buyer picker |
| cash-transaction-list.html | PortalCashTransactionsLedger | Không nút Sell; export qua onExport |
| cash-transaction-detail.html | PortalCashTransactionDetail | Read-only; banner + KV + linked credit + audit |
SellCreditsCashModal
Props (draft)
type SellCreditsCashModalProps = {
open: boolean;
onClose: () => void;
flow: CashTransactionFlow;
buyerName: string;
buyerCostRate: number; // decimal 0–1
currency?: string; // default USD
submitting?: boolean;
error?: string | null;
onSubmit: (values: SellCreditsCashFormValues) => void | Promise<void>;
};
type SellCreditsCashFormValues = {
creditAmount: number;
cashReceived: number;
paymentMethod: CashPaymentMethod;
receivedAt: string; // ISO
externalReference?: string;
note?: string;
};
Hành vi
- Section 1: credits, cost rate (read-only), expected cash (auto)
- Section 2: cash received, payment method, received at, currency
- Section 3: V1 ẩn status — luôn
paid_fullkhi submit - Section 4: external ref, note
- Dùng
Modal/Form/FormField/Input/Select/Textareatừ@kioskgaming/ui - Copy tiếng Anh (khớp mockup)
PortalCashTransactionsLedger
Pattern giống PortalCreditTransactionsLedger:
type PortalCashTransactionsLedgerProps = {
title?: string;
description?: string;
loading?: boolean;
data: CashTransactionListResponse | null;
onFetch: (params: { filters: CashTransactionFilterState; page: number; limit: number }) => void | Promise<void>;
onViewDetail?: (row: CashTransactionRow) => void;
onExport?: (params: { filters: CashTransactionFilterState }) => void | Promise<void>;
flowFilterOptions?: CashTransactionFlow[]; // portal giới hạn flow hiển thị
headerExtra?: React.ReactNode;
};
Sub-components tách riêng để test và tái sử dụng layout.
PortalCashTransactionDetail
type PortalCashTransactionDetailProps = {
loading?: boolean;
transaction: CashTransactionDetail | null;
readOnly?: boolean; // default true (V1)
onBack?: () => void;
};
- Summary cards + KV table + linked credit block + audit timeline
- Không action buttons (cancel, update, add note, open credit tx link) — V1 view-only
readOnlybanner built-in
Portal integration (mỏng)
Mỗi app chỉ cần:
| Portal | Sell modal mở từ | List route | Detail route |
|---|---|---|---|
| Admin | Super Agent detail | /cash-transactions | /cash-transactions/:id |
| Super Agent | Agent detail | /cash-transactions | /cash-transactions/:id |
| Agent | Shop detail | /cash-transactions | /cash-transactions/:id |
Ví dụ Admin Super Agent detail:
<SellCreditsCashModal
open={sellOpen}
onClose={() => setSellOpen(false)}
flow="admin_super_agent"
buyerName={superAgent.name}
buyerCostRate={superAgent.costRate}
submitting={mutation.isPending}
onSubmit={(values) => mutation.mutateAsync({ superAgentId, ...values })}
/>
List page Admin:
<PortalCashTransactionsLedger
data={listQuery.data}
loading={listQuery.isLoading}
onFetch={(p) => listQuery.refetch(/* map p */)}
onViewDetail={(row) => navigate(`/cash-transactions/${row.id}`)}
onExport={exportCsv}
/>
Types dùng chung (package)
Align với schema DB:
CashTransactionFlow:admin_super_agent|super_agent_agent|agent_shopCashTransactionStatus:paid_full| …CashPaymentMethod:cash|bank_transfer|check|other
Portal API response map → CashTransactionRow / CashTransactionDetail trong types.ts (không leak Sequelize model vào UI).
Task breakdown (UI package)
| # | Task |
|---|---|
| UI.1 | Scaffold portal-cash-settlement/ + export index.ts |
| UI.2 | types.ts, constants.ts, formatters.ts, cashTransactionPresentation.ts |
| UI.3 | useSellCreditsCashForm + unit tests (expected cash, defaults) |
| UI.4 | SellCreditsCashModal |
| UI.5 | PortalCashTransactionSummaryGrid, FiltersBar, Table |
| UI.6 | PortalCashTransactionsLedger (compose + pagination) |
| UI.7 | PortalCashTransactionDetail (view-only) |
| UI.8 | Storybook / dev preview trong packages/dev (optional) |
| UI.9 | Jest tests presentation + form hook |
Sau UI.1–UI.7, các file tasks portal chỉ còn wire API + routing.
Thứ tự triển khai đề xuất
1. UI package (UI.1–UI.7) ← làm song song backend Phase 1
2. Admin wire (tasks luồng 1 Phase 2–4)
3. Super Agent wire (tasks luồng 2)
4. Agent wire (tasks luồng 3)
Luồng 2–3 không copy UI — chỉ thêm API hook + mở modal với flow / buyer context khác.