Skip to main content

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ó:

ModuleVí dụ
portal-credit-walletPortalCreditTransactionsLedger — list + filter + table
portal-usd-walletSummary grid, filters bar, ledger table
portal-ledgerDetail 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

MockupComponent packageGhi chú
sell-credit.htmlSellCreditsCashModalContext bar: flow, buyer name, cost rate — không buyer picker
cash-transaction-list.htmlPortalCashTransactionsLedgerKhông nút Sell; export qua onExport
cash-transaction-detail.htmlPortalCashTransactionDetailRead-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_full khi submit
  • Section 4: external ref, note
  • Dùng Modal / Form / FormField / Input / Select / Textarea từ @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
  • readOnly banner built-in

Portal integration (mỏng)

Mỗi app chỉ cần:

PortalSell modal mở từList routeDetail route
AdminSuper Agent detail/cash-transactions/cash-transactions/:id
Super AgentAgent detail/cash-transactions/cash-transactions/:id
AgentShop 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_shop
  • CashTransactionStatus: 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.1Scaffold portal-cash-settlement/ + export index.ts
UI.2types.ts, constants.ts, formatters.ts, cashTransactionPresentation.ts
UI.3useSellCreditsCashForm + unit tests (expected cash, defaults)
UI.4SellCreditsCashModal
UI.5PortalCashTransactionSummaryGrid, FiltersBar, Table
UI.6PortalCashTransactionsLedger (compose + pagination)
UI.7PortalCashTransactionDetail (view-only)
UI.8Storybook / dev preview trong packages/dev (optional)
UI.9Jest 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.