Skip to main content

UI — @kioskgaming/ui / portal-cash-settlement

3 mockup screens (sell modal, list, detail view-only) shared across Admin / Super Agent / Agent portals → implement in packages/ui, each portal only wires API + route + opens modal from entity detail.

Mockup: prototype · Schema: schema-cash-transactions


Reference pattern

Same as existing modules:

ModuleExample
portal-credit-walletPortalCreditTransactionsLedger — list + filter + table
portal-usd-walletSummary grid, filters bar, ledger table
portal-ledgerDetail modal, presentation helpers

Cash settlement follows the same approach: UI + format + form state in package; fetch/submit injected via props/callback.


Suggested directory structure

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 # Sell credits modal (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 from packages/ui/src/index.ts:

export {} from './portal-cash-settlement';

Component map ↔ mockup

MockupPackage componentNotes
sell-credit.htmlSellCreditsCashModalContext bar: flow, buyer name, cost rate — no buyer picker
cash-transaction-list.htmlPortalCashTransactionsLedgerNo Sell button; export via 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;
};

Behavior

  • Section 1: credits, cost rate (read-only), expected cash (auto)
  • Section 2: cash received, payment method, received at, currency
  • Section 3: V1 hides status — always paid_full on submit
  • Section 4: external ref, note
  • Uses Modal / Form / FormField / Input / Select / Textarea from @kioskgaming/ui
  • Copy in English (matches mockup)

PortalCashTransactionsLedger

Pattern like 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 limits visible flows
headerExtra?: React.ReactNode;
};

Sub-components split for testing and layout reuse.


PortalCashTransactionDetail

type PortalCashTransactionDetailProps = {
loading?: boolean;
transaction: CashTransactionDetail | null;
readOnly?: boolean; // default true (V1)
onBack?: () => void;
};
  • Summary cards + KV table + linked credit block + audit timeline
  • No action buttons (cancel, update, add note, open credit tx link) — V1 view-only
  • Built-in readOnly banner

Portal integration (thin layer)

Each app only needs:

PortalSell modal opens fromList 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

Example 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 })}
/>

Admin list page:

<PortalCashTransactionsLedger
data={listQuery.data}
loading={listQuery.isLoading}
onFetch={(p) => listQuery.refetch(/* map p */)}
onViewDetail={(row) => navigate(`/cash-transactions/${row.id}`)}
onExport={exportCsv}
/>

Shared types (package)

Align with DB schema:

  • CashTransactionFlow: admin_super_agent | super_agent_agent | agent_shop
  • CashTransactionStatus: paid_full | …
  • CashPaymentMethod: cash | bank_transfer | check | other

Portal API response maps → CashTransactionRow / CashTransactionDetail in types.ts (do not leak Sequelize models into 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 in packages/dev (optional)
UI.9Jest tests presentation + form hook

After UI.1–UI.7, portal tasks only need API + routing wiring.


Suggested implementation order

1. UI package (UI.1–UI.7) ← parallel with backend Phase 1
2. Admin wire (tasks flow 1 Phase 2–4)
3. Super Agent wire (tasks flow 2)
4. Agent wire (tasks flow 3)

Flows 2–3 do not copy UI — only add API hook + open modal with different flow / buyer context.