From 8fa921f61e333b983cf162594e9a4ade74cd4a7a Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Thu, 16 Jul 2026 20:54:20 +0530 Subject: [PATCH] feat(mobile): per-flat statement on the bills screen (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Residents see a read-only running-balance statement for their flat, below the bill list + payment history. - api/client.ts: getApartmentStatement + ApartmentStatement type (role-scoped endpoint returns only the resident's own flat). - bills.tsx: statement section — dated debit/credit entries with running balance + closing balance, sourced from the primary flat (/apartments/mine). - 2 mobile i18n keys (en + hi); parity green. - Validated: shared build, mobile check-types + lint, i18n-parity. --- apps/mobile/api/client.ts | 12 ++++++++++++ apps/mobile/app/bills.tsx | 34 ++++++++++++++++++++++++++++++++++ packages/shared/src/i18n.ts | 4 ++++ 3 files changed, 50 insertions(+) diff --git a/apps/mobile/api/client.ts b/apps/mobile/api/client.ts index a8b80e9..6392dd6 100644 --- a/apps/mobile/api/client.ts +++ b/apps/mobile/api/client.ts @@ -12,6 +12,7 @@ import type { CreateHouseHelpReview, MaintenanceBill, Payment, + StatementEntry, Notice, SocietyConfig, Ticket, @@ -26,6 +27,15 @@ import type { export const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:8787' +// Per-flat statement of account (#96) — read-only running balance for residents. +export type ApartmentStatement = { + apartmentId: string + apartment: string + opening: number + closing: number + entries: StatementEntry[] +} + // House help as returned by the directory list — carries its anonymous rating // summary plus verification/trust signals. export type HouseHelpWithRating = HouseHelp & { @@ -133,6 +143,8 @@ export const apiClient = { checkOutHouseHelpEntry: (entryId: string, userId?: string) => api(`/house-help/entries/${entryId}/checkout`, { method: 'POST', body: JSON.stringify({}) }, userId), listMyApartments: () => api('/apartments/mine'), + getApartmentStatement: (apartmentId: string) => + api(`/apartments/${apartmentId}/statement`), listMyResidents: () => api<{ apartmentId: string; userId: string; name: string; relation: string }[]>('/apartments/mine/residents'), listVehicles: (apartmentId?: string) => diff --git a/apps/mobile/app/bills.tsx b/apps/mobile/app/bills.tsx index 89478b0..e9d7933 100644 --- a/apps/mobile/app/bills.tsx +++ b/apps/mobile/app/bills.tsx @@ -24,6 +24,13 @@ export default function Bills() { const { t } = useT() const bills = useQuery({ queryKey: ['bills'], queryFn: () => apiClient.listBills() }) const payments = useQuery({ queryKey: ['payments'], queryFn: () => apiClient.listPayments() }) + const myApts = useQuery({ queryKey: ['my-apartments'], queryFn: () => apiClient.listMyApartments() }) + const primaryApt = myApts.data?.[0]?.id + const statement = useQuery({ + queryKey: ['my-statement', primaryApt], + queryFn: () => apiClient.getApartmentStatement(primaryApt!), + enabled: !!primaryApt, + }) if (bills.isLoading) return ( @@ -91,6 +98,33 @@ export default function Bills() { {formatPaise(p.amount)} ))} + + {statement.data && statement.data.entries.length > 0 && ( + <> + {t('bills.statement')} + {statement.data.entries.map((e, i) => ( + + + {e.description} + {e.date.slice(0, 10)} + + + + {e.debit ? `+${formatPaise(e.debit)}` : `-${formatPaise(e.credit)}`} + + {formatPaise(e.balance)} + + + ))} + + {t('bills.closingBalance')} + {formatPaise(statement.data.closing)} + + + )} ) } diff --git a/packages/shared/src/i18n.ts b/packages/shared/src/i18n.ts index d23c079..c3673ed 100644 --- a/packages/shared/src/i18n.ts +++ b/packages/shared/src/i18n.ts @@ -1012,6 +1012,8 @@ const mobileEn: Dict = { 'bills.paid': 'Paid', 'bills.paymentHistory': 'Payment history', 'bills.noPayments': 'No payments yet.', + 'bills.statement': 'Statement of account', + 'bills.closingBalance': 'Closing balance', 'common.deny': 'Deny', 'visitors.empty': 'No visitors yet.', 'visitors.denyReason': 'Reason for denial', @@ -1140,6 +1142,8 @@ const mobileHi: Dict = { 'bills.paid': 'भुगतान किया', 'bills.paymentHistory': 'भुगतान इतिहास', 'bills.noPayments': 'अभी कोई भुगतान नहीं।', + 'bills.statement': 'खाता विवरण', + 'bills.closingBalance': 'अंतिम शेष', 'common.deny': 'अस्वीकृत करें', 'visitors.empty': 'अभी कोई आगंतुक नहीं।', 'visitors.denyReason': 'अस्वीकृति का कारण',