Lewati ke konten utama

Admin Finance & Trust Modules

Four ops panel modules cover the financial and fraud-prevention layers: Transaction Ledger, Financial Reports, Payout & Revenue, and Fraud Monitor. The first three live in the Commerce group; Fraud Monitor lives in the System group.


Transaction Ledger

Component: src/app/components/admin/LedgerView.tsx

Displays every ledger_entries row with type-keyed colour chips, direction badges, and funds-status labels. Paginated at 50 rows, filterable by entry type.

Ledger type config

TypeLabelColourDirectionFunds status
SALESALETeal (#006D77)inRELEASED
REFUNDREFUNDRed (#EF4444)outRELEASED
BPFBPFAmber (#F59E0B)inRELEASED
PLATFORM_FEEFEEBlue (#2563EB)inRELEASED
HOLDHOLDOrange (#F97316)inHELD
RELEASERELEASEGreen (#059669)outRELEASED
FREEZEFREEZEBlue (#2563EB)inHELD
DISPUTE_HOLDDISPUTERed (#EF4444)inHELD

Endpoint

GET /admin/ledger, paginated; allowed roles: administrator, finance.

Implementation notes

  • BRAND-01, FREEZE type colour: Purple (#8B5CF6) is banned across the entire ops panel. The FREEZE chip was purple on initial build; corrected to #2563EB (blue). (Fixed PS-369, 2026-05-12.)
  • Never reintroduce any purple hex to LedgerView.tsx.

Financial Reports

Component: src/app/components/admin/FinancialLedger.tsx

Summary dashboard with four KPI cards and an Escrow Balance Trend area chart.

KPI cards

CardSource tableNotes
Total Ledger Entriesledger_entriesCount
Total Revenueledger_entriesSum of amount
Avg Transactionledger_entriesMean amount
Escrow Heldorders via escrow summarySum of held order value

Escrow Balance Trend chart

7-day daily chart with held, released, and bpf series. Fetched from GET /admin/escrow-trend. Mapped in FinancialLedger.tsx as:

setEscrowTrend((data.trend ?? []).map(d => ({ ...d, bpf: 0 })));

bpf is set to 0 client-side because the endpoint does not yet return a BPF series.

Endpoints

EndpointPurpose
GET /admin/ledgerPaginated ledger entries for the table view
GET /admin/escrow-summary-liveLive escrow KPI card values
GET /admin/escrow-trend7-day escrow trend for the area chart

All three accept roles administrator and finance.

Implementation notes

  • Stale subtitle: The KPI section subtitle previously read "Live data from KV store", corrected to "Live data from ledger_entries" to reflect the live Postgres source. (Fixed PS-368, 2026-05-12.)
  • Escrow trend was hardcoded empty: The initial build had const _escrowTrend = [] as a module-level constant (never fetched). The chart always showed a flat line. Replaced with state + useCallback fetch wired into useEffect and the manual refresh handler. (Fixed PS-370, 2026-05-12.)

Payout & Revenue

Component: src/app/components/admin/PayoutRevenue.tsx

Shows disbursement history and revenue metrics for the finance team.

Data source

Payout history reads from fin_disbursements via container.financeAdminRepo.getAllDisbursements(). Records are sorted descending by created_at after a null-safety filter.

Endpoints

EndpointPurpose
GET /admin/payoutsPayout (disbursement) history from fin_disbursements
GET /admin/revenueRevenue summary metrics

Both endpoints require role administrator or finance.

Implementation notes

  • Dead KV read, payout history: The initial GET /admin/payouts handler called getByPrefixAll("payout:") against the KV store, which was retired in Phase 14. Every call returned an empty array and the payout table was always blank. Replaced with a live fin_disbursements read via financeAdminRepo. The getByPrefixAll import was also removed from the route file. (Fixed PS-371, 2026-05-12.)
  • Role lockout, administrator missing: verifyAdminRole(c, ["finance"]) on both GET /admin/payouts and GET /admin/revenue locked out administrator-role users (same class of bug as PS-367). Fixed by adding "administrator" to both role arrays: verifyAdminRole(c, ["administrator", "finance"]). (Fixed PS-372, 2026-05-12.)

Fraud Monitor

Component: src/app/components/admin/FraudMonitor.tsx

Real-time fraud case viewer with inline state transitions.

Case shape, FraudCaseRow

Backend returns FraudCaseRow objects from the fraud_cases table. The frontend previously expected a FraudAlert shape with fields that do not exist on the backend type, every row rendered blank. The component now maps FraudCaseRow → FraudAlert display shape before rendering.

Backend field (FraudCaseRow)Display field (FraudAlert)Derived how
case_typetypedirect cast
severity (remaps "low""medium")severitylow is treated as medium for display
case_typetitlederiveCaseTitle() helper
subject_type + subject_id + resolution_notesdescriptiontemplate string
opened_byuserdirect
extra.listing_id / extra.order_idlistingId / orderIdoptional chaining on extra
created_attimetoLocaleString("id-ID", ...)
statusstatusdirect cast

Case type → title mapping (deriveCaseTitle)

case_typeDisplayed title
fake_brandFake Brand Detected
price_anomalyPrice Anomaly Detected
account_suspiciousSuspicious Account Activity
disputeDispute Escalated
late_sellerLate Seller Pattern
anything elseTitle-cased, underscores replaced with spaces

5-state machine

Cases progress through five states. The state machine is enforced server-side at POST /admin/fraud-cases/:id/transition.

open → investigating → confirmed → resolved
→ rejected → resolved
StateColourWho can transition to this
openOrangeInitial state
investigatingBlueFrom open
confirmedRedFrom investigating
rejectedGrayFrom investigating
resolvedGreenFrom confirmed or rejected

Transition API

POST /admin/fraud-cases/:id/transition

{
"to": "investigating | confirmed | rejected | resolved",
"resolution_notes": "string (≥ 30 chars for confirmed / rejected / resolved)"
}

The body field is to (target status), not action. The resolution_notes field is required and must be at least 30 characters for any terminal transition (confirmed, rejected, resolved). The textarea in the panel enforces this client-side before enabling the submit button.

Frontend transition UI

The expanded case panel shows contextual action buttons based on current status:

Current statusAvailable actions
openInvestigate
investigatingConfirm Fraud, Reject
confirmed / rejectedResolve
resolved(no actions)

Filter tabs

The tab bar above the case list includes: All, Open, Investigating, Confirmed, Rejected, Resolved. Each tab filters the live FraudCaseRow array by the matching status value. Tab colours match the state colour table above.

Endpoints

EndpointPurpose
GET /admin/fraud-alertsFetch all fraud cases (FraudCaseRow[])
POST /admin/fraud-cases/:id/transitionTransition case state

Both endpoints require role administrator or fraud_ops.

Implementation notes

  • Field mismatch, all rows blank: FraudMonitor.tsx read fields (title, description, user, time) that do not exist on the backend FraudCaseRow type. All renders were undefined. Fixed by adding the deriveCaseTitle() helper and a full FraudCaseRow → FraudAlert mapping in the allFraudAlerts derived array. Also updated FraudCaseRow interface in types.ts, getFraudAlerts return type in api-admin.ts, and useFraudAlerts return type in use-admin.ts. (Fixed PS-373, 2026-05-12.)
  • Transition UI added: Before PS-374, there was no way to change case status from the ops panel. Inline action buttons + resolution notes textarea added with full transitionFraudCase API method. (Fixed PS-374, 2026-05-12.)
  • Filter tabs extended: Original tabs only covered open, investigating, resolved. Added confirmed and rejected tabs to complete 5-state coverage. (Fixed PS-375, 2026-05-12.)

Prev: Admin Commerce Modules →