diff --git a/apps/bot/src/miniapp-dashboard.ts b/apps/bot/src/miniapp-dashboard.ts index c2ea8f7..5d6eab7 100644 --- a/apps/bot/src/miniapp-dashboard.ts +++ b/apps/bot/src/miniapp-dashboard.ts @@ -103,6 +103,7 @@ export function createMiniAppDashboardHandler(options: { members: dashboard.members.map((line) => ({ memberId: line.memberId, displayName: line.displayName, + predictedUtilityShareMajor: line.predictedUtilityShare?.toMajorString() ?? null, rentShareMajor: line.rentShare.toMajorString(), utilityShareMajor: line.utilityShare.toMajorString(), purchaseOffsetMajor: line.purchaseOffset.toMajorString(), diff --git a/apps/miniapp/src/App.tsx b/apps/miniapp/src/App.tsx index 0b0b8f0..476146a 100644 --- a/apps/miniapp/src/App.tsx +++ b/apps/miniapp/src/App.tsx @@ -314,6 +314,7 @@ function App() { status: 'loading' }) const [activeNav, setActiveNav] = createSignal('home') + const [selectedBalanceMemberId, setSelectedBalanceMemberId] = createSignal(null) const [dashboard, setDashboard] = createSignal(null) const [pendingMembers, setPendingMembers] = createSignal([]) const [adminSettings, setAdminSettings] = createSignal(null) @@ -444,6 +445,21 @@ function App() { return data.members.find((member) => member.memberId === current.member.id) ?? null }) + const inspectedBalanceMember = createMemo(() => { + const data = dashboard() + if (!data) { + return null + } + + const selected = selectedBalanceMemberId() + + return ( + data.members.find((member) => member.memberId === selected) ?? + currentMemberLine() ?? + data.members[0] ?? + null + ) + }) const purchaseLedger = createMemo(() => (dashboard()?.ledger ?? []).filter((entry) => entry.kind === 'purchase') ) @@ -2024,12 +2040,15 @@ function App() { locale={locale()} dashboard={dashboard()} currentMemberLine={currentMemberLine()} + inspectedMember={inspectedBalanceMember()} + selectedMemberId={inspectedBalanceMember()?.memberId ?? ''} utilityTotalMajor={utilityTotalMajor()} purchaseTotalMajor={purchaseTotalMajor()} memberBalanceVisuals={memberBalanceVisuals()} purchaseChart={purchaseInvestmentChart()} memberBaseDueMajor={memberBaseDueMajor} memberRemainingClass={memberRemainingClass} + onSelectedMemberChange={setSelectedBalanceMemberId} /> ) case 'ledger': @@ -2468,8 +2487,10 @@ function App() { locale={locale()} dashboard={dashboard()} currentMemberLine={currentMemberLine()} - utilityTotalMajor={utilityTotalMajor()} - purchaseTotalMajor={purchaseTotalMajor()} + onExplainBalance={() => { + setSelectedBalanceMemberId(currentMemberLine()?.memberId ?? null) + setActiveNav('balances') + }} /> ) } diff --git a/apps/miniapp/src/demo/miniapp-demo.ts b/apps/miniapp/src/demo/miniapp-demo.ts index 4290a0e..d4137ce 100644 --- a/apps/miniapp/src/demo/miniapp-demo.ts +++ b/apps/miniapp/src/demo/miniapp-demo.ts @@ -42,6 +42,7 @@ export const demoDashboard: MiniAppDashboard = { { memberId: 'demo-member', displayName: 'Stas', + predictedUtilityShareMajor: '78.00', rentShareMajor: '603.75', utilityShareMajor: '78.00', purchaseOffsetMajor: '-66.00', @@ -53,6 +54,7 @@ export const demoDashboard: MiniAppDashboard = { { memberId: 'member-chorb', displayName: 'Chorbanaut', + predictedUtilityShareMajor: '78.00', rentShareMajor: '603.75', utilityShareMajor: '78.00', purchaseOffsetMajor: '12.00', @@ -64,6 +66,7 @@ export const demoDashboard: MiniAppDashboard = { { memberId: 'member-el', displayName: 'El', + predictedUtilityShareMajor: '0.00', rentShareMajor: '1207.50', utilityShareMajor: '0.00', purchaseOffsetMajor: '54.00', diff --git a/apps/miniapp/src/i18n.ts b/apps/miniapp/src/i18n.ts index 442822a..b2863f0 100644 --- a/apps/miniapp/src/i18n.ts +++ b/apps/miniapp/src/i18n.ts @@ -63,6 +63,7 @@ export const dictionary = { payNowBody: '', homeDueTitle: 'Due', homeSettledTitle: 'Settled', + whyAction: 'Why?', currentCycleLabel: 'Current cycle', cycleTotalLabel: 'Cycle total', cycleBillLabel: 'Cycle bill', @@ -75,8 +76,12 @@ export const dictionary = { rentPaidLabel: 'Rent paid', utilitiesPaidLabel: 'Utilities paid', dueOnLabel: 'Due {date}', + dueTodayLabel: 'Due today', + overdueLabel: 'Overdue', + daysLeftLabel: '{count}d left', upcomingLabel: 'Upcoming', notBilledYetLabel: 'Not billed yet', + expectedUtilitiesLabel: 'Expected utilities', baseDue: 'Base due', finalDue: 'Final due', houseSnapshotTitle: 'House totals', @@ -86,6 +91,9 @@ export const dictionary = { 'This screen only explains your current cycle balance. Older activity stays in the ledger.', householdBalancesTitle: 'Household balances', householdBalancesBody: 'Everyone’s current split for this cycle.', + inspectMemberTitle: 'Inspect member', + inspectMemberBody: 'Check another member balance without opening a long list.', + inspectMemberLabel: 'Member', financeVisualsTitle: 'Visual balance split', financeVisualsBody: 'Use the bars to see how rent, utilities, and shared-buy adjustments shape each member balance.', @@ -351,6 +359,7 @@ export const dictionary = { payNowBody: '', homeDueTitle: 'К оплате', homeSettledTitle: 'Закрыто', + whyAction: 'Почему?', currentCycleLabel: 'Текущий цикл', cycleTotalLabel: 'Всего за цикл', cycleBillLabel: 'Счёт за цикл', @@ -363,8 +372,12 @@ export const dictionary = { rentPaidLabel: 'По аренде оплачено', utilitiesPaidLabel: 'По коммуналке оплачено', dueOnLabel: 'Срок {date}', + dueTodayLabel: 'Срок сегодня', + overdueLabel: 'Просрочено', + daysLeftLabel: 'Осталось {count} дн.', upcomingLabel: 'Ещё не срок', notBilledYetLabel: 'Ещё не начислено', + expectedUtilitiesLabel: 'Ожидаемая коммуналка', baseDue: 'База к оплате', finalDue: 'Итог к оплате', houseSnapshotTitle: 'Сводка по дому', @@ -374,6 +387,9 @@ export const dictionary = { 'На этом экране только разбор твоего текущего баланса. Более старые записи остаются в леджере.', householdBalancesTitle: 'Баланс дома', householdBalancesBody: 'Текущий расклад по всем участникам за этот цикл.', + inspectMemberTitle: 'Посмотреть участника', + inspectMemberBody: 'Можно быстро проверить чужой баланс без длинного списка карточек.', + inspectMemberLabel: 'Участник', financeVisualsTitle: 'Визуальный разбор баланса', financeVisualsBody: 'Полосы показывают, как аренда, коммуналка и поправка на общие покупки формируют баланс каждого участника.', diff --git a/apps/miniapp/src/index.css b/apps/miniapp/src/index.css index e04969e..3b1fa56 100644 --- a/apps/miniapp/src/index.css +++ b/apps/miniapp/src/index.css @@ -461,11 +461,16 @@ button:disabled { .home-pay-card, .home-pay-card__header, .home-pay-card__copy, -.home-pay-card__chips { +.home-pay-card__chips, +.home-pay-card__actions { display: grid; gap: 12px; } +.home-pay-card__actions { + justify-items: start; +} + .stat-card { display: grid; gap: 8px; @@ -1186,6 +1191,10 @@ button:disabled { gap: 16px; } +.balance-section--secondary { + background: linear-gradient(180deg, rgb(255 255 255 / 0.04), rgb(255 255 255 / 0.02)); +} + .balance-section__header { display: flex; flex-wrap: wrap; @@ -1199,6 +1208,10 @@ button:disabled { gap: 8px; } +.balance-section__field { + min-width: min(220px, 100%); +} + .household-balance-list { display: grid; gap: 12px; @@ -1208,6 +1221,22 @@ button:disabled { margin-top: 12px; } +.balance-detail-card, +.balance-detail-card__rows { + display: grid; + gap: 12px; +} + +.balance-detail-card__header { + display: grid; + gap: 10px; +} + +.balance-detail-card__copy { + display: grid; + gap: 6px; +} + .app-context-row { display: flex; flex-wrap: wrap; @@ -1248,10 +1277,23 @@ button:disabled { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .home-pay-card__header { + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + } + + .balance-spotlight__hero { + grid-column: 1 / -1; + } + .balance-spotlight__header { align-items: start; } + .home-pay-card__actions { + justify-items: end; + } + .balance-spotlight__stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -1298,6 +1340,10 @@ button:disabled { .member-editor-actions__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + + .balance-detail-card__rows { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } @media (max-width: 759px) { diff --git a/apps/miniapp/src/lib/dates.ts b/apps/miniapp/src/lib/dates.ts index 12712b1..57a2cbe 100644 --- a/apps/miniapp/src/lib/dates.ts +++ b/apps/miniapp/src/lib/dates.ts @@ -156,3 +156,17 @@ export function compareTodayToPeriodDay( return 0 } + +export function daysUntilPeriodDay(period: string, day: number, timezone: string): number | null { + const parsed = parsePeriod(period) + const today = formatTodayParts(timezone) + if (!parsed || !today) { + return null + } + + const safeDay = Math.max(1, Math.min(day, daysInMonth(parsed.year, parsed.month))) + const dueValue = Date.UTC(parsed.year, parsed.month - 1, safeDay) + const todayValue = Date.UTC(today.year, today.month - 1, today.day) + + return Math.round((dueValue - todayValue) / 86_400_000) +} diff --git a/apps/miniapp/src/miniapp-api.ts b/apps/miniapp/src/miniapp-api.ts index 21d1051..1c3147e 100644 --- a/apps/miniapp/src/miniapp-api.ts +++ b/apps/miniapp/src/miniapp-api.ts @@ -110,6 +110,7 @@ export interface MiniAppDashboard { members: { memberId: string displayName: string + predictedUtilityShareMajor: string | null rentShareMajor: string utilityShareMajor: string purchaseOffsetMajor: string diff --git a/apps/miniapp/src/screens/balances-screen.tsx b/apps/miniapp/src/screens/balances-screen.tsx index e477b69..56ecb6d 100644 --- a/apps/miniapp/src/screens/balances-screen.tsx +++ b/apps/miniapp/src/screens/balances-screen.tsx @@ -1,8 +1,9 @@ -import { For, Show } from 'solid-js' +import { Show } from 'solid-js' import { FinanceSummaryCards } from '../components/finance/finance-summary-cards' import { FinanceVisuals } from '../components/finance/finance-visuals' import { MemberBalanceCard } from '../components/finance/member-balance-card' +import { Field } from '../components/ui' import { formatCyclePeriod } from '../lib/dates' import type { MiniAppDashboard } from '../miniapp-api' @@ -11,6 +12,8 @@ type Props = { locale: 'en' | 'ru' dashboard: MiniAppDashboard | null currentMemberLine: MiniAppDashboard['members'][number] | null + inspectedMember: MiniAppDashboard['members'][number] | null + selectedMemberId: string utilityTotalMajor: string purchaseTotalMajor: string memberBalanceVisuals: { @@ -39,6 +42,7 @@ type Props = { } memberBaseDueMajor: (member: MiniAppDashboard['members'][number]) => string memberRemainingClass: (member: MiniAppDashboard['members'][number]) => string + onSelectedMemberChange: (memberId: string) => void } export function BalancesScreen(props: Props) { @@ -64,13 +68,110 @@ export function BalancesScreen(props: Props) { /> )} -
-
- {props.copy.balanceScreenScopeTitle ?? ''} - {formatCyclePeriod(dashboard().period, props.locale)} + +
+
+
+ {props.copy.inspectMemberTitle ?? ''} +

{props.copy.inspectMemberBody ?? ''}

+
+ + +
-

{props.copy.balanceScreenScopeBody ?? ''}

-
+ + + {(member) => ( +
+
+
+ {member().displayName} + {formatCyclePeriod(dashboard().period, props.locale)} +
+ + {member().remainingMajor} {dashboard().currency} + +
+ +
+
+
+ {props.copy.baseDue ?? ''} + + {props.memberBaseDueMajor(member())} {dashboard().currency} + +
+
+
+
+ {props.copy.shareRent ?? ''} + + {member().rentShareMajor} {dashboard().currency} + +
+
+
+
+ {props.copy.shareUtilities ?? ''} + + {member().utilityShareMajor} {dashboard().currency} + +
+
+
+
+ {props.copy.shareOffset ?? ''} + + {member().purchaseOffsetMajor} {dashboard().currency} + +
+
+
+
+ {props.copy.paidLabel ?? ''} + + {member().paidMajor} {dashboard().currency} + +
+
+
+
+ {props.copy.remainingLabel ?? ''} + + {member().remainingMajor} {dashboard().currency} + +
+
+
+
+ )} +
+ + + +
{props.copy.houseSnapshotTitle ?? ''} @@ -91,70 +192,6 @@ export function BalancesScreen(props: Props) { />
- -
-
-
- {props.copy.householdBalancesTitle ?? ''} -

{props.copy.householdBalancesBody ?? ''}

-
- - {String(dashboard().members.length)} {props.copy.membersCount ?? ''} - -
-
- - {(member) => ( -
-
-
- {member.displayName} - - {member.remainingMajor} {dashboard().currency} - -
-
- - {props.copy.baseDue ?? ''}: {props.memberBaseDueMajor(member)}{' '} - {dashboard().currency} - - - {props.copy.shareRent ?? ''}: {member.rentShareMajor}{' '} - {dashboard().currency} - - - {props.copy.shareUtilities ?? ''}: {member.utilityShareMajor}{' '} - {dashboard().currency} - - - {props.copy.shareOffset ?? ''}: {member.purchaseOffsetMajor}{' '} - {dashboard().currency} - - - {props.copy.paidLabel ?? ''}: {member.paidMajor} {dashboard().currency} - -
-
-
- )} -
-
-
)} diff --git a/apps/miniapp/src/screens/home-screen.tsx b/apps/miniapp/src/screens/home-screen.tsx index 020323c..ae17cf6 100644 --- a/apps/miniapp/src/screens/home-screen.tsx +++ b/apps/miniapp/src/screens/home-screen.tsx @@ -1,7 +1,12 @@ import { Show } from 'solid-js' -import { FinanceSummaryCards } from '../components/finance/finance-summary-cards' -import { compareTodayToPeriodDay, formatCyclePeriod, formatPeriodDay } from '../lib/dates' +import { Button } from '../components/ui' +import { + compareTodayToPeriodDay, + daysUntilPeriodDay, + formatCyclePeriod, + formatPeriodDay +} from '../lib/dates' import { majorStringToMinor, minorToMajorString, sumMajorStrings } from '../lib/money' import type { MiniAppDashboard } from '../miniapp-api' @@ -10,10 +15,11 @@ type Props = { locale: 'en' | 'ru' dashboard: MiniAppDashboard | null currentMemberLine: MiniAppDashboard['members'][number] | null - utilityTotalMajor: string - purchaseTotalMajor: string + onExplainBalance: () => void } +type HomeMode = 'upcoming' | 'due' | 'settled' + export function HomeScreen(props: Props) { const rentPaidMajor = () => { if (!props.dashboard || !props.currentMemberLine) { @@ -94,6 +100,14 @@ export function HomeScreen(props: Props) { : props.currentMemberLine.utilityShareMajor } + const predictedUtilitiesMajor = () => { + if (!props.currentMemberLine) { + return null + } + + return props.currentMemberLine.predictedUtilityShareMajor + } + const separateBalanceMajor = () => { if ( !props.currentMemberLine || @@ -105,13 +119,9 @@ export function HomeScreen(props: Props) { return props.currentMemberLine.purchaseOffsetMajor } - const heroState = () => { + const homeMode = (): HomeMode => { if (!props.dashboard || !props.currentMemberLine) { - return { - title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '', - label: props.copy.remainingLabel ?? '', - amountMajor: '—' - } + return 'upcoming' } const remainingMinor = majorStringToMinor(props.currentMemberLine.remainingMajor) @@ -126,6 +136,7 @@ export function HomeScreen(props: Props) { props.dashboard.utilitiesDueDay, props.dashboard.timezone ) + const hasDueNow = (rentStatus !== null && rentStatus >= 0 && @@ -137,50 +148,71 @@ export function HomeScreen(props: Props) { majorStringToMinor(separateBalanceMajor() ?? '0.00') > 0n) if (remainingMinor === 0n && paidMinor > 0n) { - return { - title: props.copy.homeSettledTitle ?? '', - label: props.copy.paidThisCycleLabel ?? props.copy.paidLabel ?? '', - amountMajor: props.currentMemberLine.paidMajor - } + return 'settled' } - if (hasDueNow) { + return hasDueNow ? 'due' : 'upcoming' + } + + const heroState = () => { + if (!props.dashboard || !props.currentMemberLine) { return { - title: props.copy.homeDueTitle ?? props.copy.payNowTitle ?? '', + title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '', label: props.copy.remainingLabel ?? '', - amountMajor: props.currentMemberLine.remainingMajor + amountMajor: '—' } } - return { - title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '', - label: props.copy.cycleTotalLabel ?? props.copy.totalDue ?? '', - amountMajor: props.currentMemberLine.netDueMajor + switch (homeMode()) { + case 'settled': + return { + title: props.copy.homeSettledTitle ?? '', + label: props.copy.paidThisCycleLabel ?? props.copy.paidLabel ?? '', + amountMajor: props.currentMemberLine.paidMajor + } + case 'due': + return { + title: props.copy.homeDueTitle ?? props.copy.payNowTitle ?? '', + label: props.copy.remainingLabel ?? '', + amountMajor: props.currentMemberLine.remainingMajor + } + default: + return { + title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '', + label: props.copy.cycleTotalLabel ?? props.copy.totalDue ?? '', + amountMajor: props.currentMemberLine.netDueMajor + } } } - const dueLabel = (kind: 'rent' | 'utilities') => { + const dayCountLabel = (daysLeft: number | null) => { + if (daysLeft === null) { + return null + } + + if (daysLeft < 0) { + return props.copy.overdueLabel ?? '' + } + + if (daysLeft === 0) { + return props.copy.dueTodayLabel ?? '' + } + + return (props.copy.daysLeftLabel ?? '').replace('{count}', String(daysLeft)) + } + + const scheduleLabel = (kind: 'rent' | 'utilities') => { if (!props.dashboard) { return null } const day = kind === 'rent' ? props.dashboard.rentDueDay : props.dashboard.utilitiesDueDay - const comparison = compareTodayToPeriodDay( - props.dashboard.period, - day, - props.dashboard.timezone - ) const date = formatPeriodDay(props.dashboard.period, day, props.locale) - const template = - comparison !== null && comparison < 0 - ? (props.copy.upcomingLabel ?? '') - : (props.copy.dueOnLabel ?? '').replace('{date}', date) + const daysLeft = daysUntilPeriodDay(props.dashboard.period, day, props.dashboard.timezone) + const dayLabel = dayCountLabel(daysLeft) + const dueLabel = (props.copy.dueOnLabel ?? '').replace('{date}', date) - if (comparison !== null && comparison < 0) { - return `${template}${template.length > 0 ? ' ' : ''}${date}`.trim() - } - - return template + return dayLabel ? `${dueLabel} · ${dayLabel}` : dueLabel } return ( @@ -192,7 +224,6 @@ export function HomeScreen(props: Props) {
{props.copy.yourBalanceTitle ?? ''} -

{props.copy.yourBalanceBody ?? ''}

{props.copy.remainingLabel ?? ''} @@ -213,6 +244,11 @@ export function HomeScreen(props: Props) { {heroState().title} {formatCyclePeriod(dashboard().period, props.locale)}
+
+ +
{heroState().label} @@ -221,70 +257,129 @@ export function HomeScreen(props: Props) {
-
-
- {props.copy.paidLabel ?? ''} - - {member().paidMajor} {dashboard().currency} - -
-
- {props.copy.remainingLabel ?? ''} - - {member().remainingMajor} {dashboard().currency} - -
-
+ +
+ {props.copy.paidLabel ?? ''} + + {member().paidMajor} {dashboard().currency} + +
+
+ {props.copy.remainingLabel ?? ''} + + {member().remainingMajor} {dashboard().currency} + +
+ + } + > +
+
+ {props.copy.shareRent ?? ''} + {scheduleLabel('rent')} +
+
+ {props.copy.shareUtilities ?? ''} + {scheduleLabel('utilities')} +
+
+
- - {dashboard().paymentBalanceAdjustmentPolicy === 'rent' - ? props.copy.rentAdjustedTotalLabel - : props.copy.shareRent} - + {props.copy.shareRent ?? ''} - {rentDueMajor()} {dashboard().currency} + {member().rentShareMajor} {dashboard().currency} - {dueLabel('rent')} + {scheduleLabel('rent')}
- - {props.copy.rentPaidLabel ?? props.copy.paidLabel}: {rentPaidMajor()}{' '} - {dashboard().currency} - + + + {props.copy.rentPaidLabel ?? props.copy.paidLabel}: {rentPaidMajor()}{' '} + {dashboard().currency} + +
- {dashboard().paymentBalanceAdjustmentPolicy === 'utilities' - ? props.copy.utilitiesAdjustedTotalLabel - : (props.copy.utilitiesBalanceLabel ?? props.copy.shareUtilities)} + {homeMode() === 'upcoming' + ? (props.copy.expectedUtilitiesLabel ?? props.copy.shareUtilities) + : (props.copy.pureUtilitiesLabel ?? props.copy.shareUtilities)} - {utilitiesDueMajor() !== null - ? `${utilitiesDueMajor()} ${dashboard().currency}` - : (props.copy.notBilledYetLabel ?? '')} + {homeMode() === 'upcoming' + ? predictedUtilitiesMajor() + ? `${predictedUtilitiesMajor()} ${dashboard().currency}` + : (props.copy.notBilledYetLabel ?? '') + : utilitiesDueMajor() + ? `${member().utilityShareMajor} ${dashboard().currency}` + : (props.copy.notBilledYetLabel ?? '')} - - {utilitiesDueMajor() !== null - ? dueLabel('utilities') - : dueLabel('utilities')} - + {scheduleLabel('utilities')}
- - {props.copy.utilitiesPaidLabel ?? props.copy.paidLabel}:{' '} - {utilitiesPaidMajor()} {dashboard().currency} - + 0n + } + > + + {props.copy.utilitiesPaidLabel ?? props.copy.paidLabel}:{' '} + {utilitiesPaidMajor()} {dashboard().currency} + +
- -
+
+
+ {props.copy.balanceAdjustmentLabel ?? props.copy.shareOffset} + + {member().purchaseOffsetMajor} {dashboard().currency} + + {props.copy.currentCycleLabel ?? ''} +
+
+ + +
- {props.copy.balanceAdjustmentLabel ?? props.copy.shareOffset} + {props.copy.rentAdjustedTotalLabel ?? ''} - {separateBalanceMajor()} {dashboard().currency} + {adjustedRentMajor()} {dashboard().currency} + +
+
+
+ + +
+
+ {props.copy.utilitiesAdjustedTotalLabel ?? ''} + + {homeMode() === 'upcoming' + ? predictedUtilitiesMajor() + ? `${sumMajorStrings( + predictedUtilitiesMajor() ?? '0.00', + member().purchaseOffsetMajor + )} ${dashboard().currency}` + : (props.copy.notBilledYetLabel ?? '') + : `${adjustedUtilitiesMajor()} ${dashboard().currency}`} + +
+
+
+ + +
+
+ {props.copy.finalDue ?? props.copy.remainingLabel} + + {member().remainingMajor} {dashboard().currency}
@@ -293,27 +388,6 @@ export function HomeScreen(props: Props) {
)}
- -
-
- {props.copy.houseSnapshotTitle ?? ''} - {formatCyclePeriod(dashboard().period, props.locale)} -
-

{props.copy.houseSnapshotBody ?? ''}

-
- -
-
)} diff --git a/apps/miniapp/src/screens/house-screen.tsx b/apps/miniapp/src/screens/house-screen.tsx index a934757..5efb6d3 100644 --- a/apps/miniapp/src/screens/house-screen.tsx +++ b/apps/miniapp/src/screens/house-screen.tsx @@ -184,6 +184,7 @@ function HouseSection(props: { + + + +
-
-
- {props.copy.householdLanguage ?? ''} - {props.householdDefaultLocale.toUpperCase()} -
-
- - -
-
-
{props.copy.manageProfileAction ?? ''} diff --git a/packages/application/src/finance-command-service.ts b/packages/application/src/finance-command-service.ts index 99239d8..5ef8491 100644 --- a/packages/application/src/finance-command-service.ts +++ b/packages/application/src/finance-command-service.ts @@ -106,6 +106,7 @@ export interface FinanceDashboardMemberLine { status?: 'active' | 'away' | 'left' absencePolicy?: HouseholdMemberAbsencePolicy absencePolicyEffectiveFromPeriod?: string | null + predictedUtilityShare?: Money | null rentShare: Money utilityShare: Money purchaseOffset: Money @@ -321,6 +322,16 @@ async function buildFinanceDashboard( dependencies.repository.listUtilityBillsForCycle(cycle.id) ]) const paymentRecords = await dependencies.repository.listPaymentRecordsForCycle(cycle.id) + const previousCycle = await dependencies.repository.getCycleByPeriod(period.previous().toString()) + const previousSnapshotLines = previousCycle + ? await dependencies.repository.getSettlementSnapshotLines(previousCycle.id) + : [] + const previousUtilityShareByMemberId = new Map( + previousSnapshotLines.map((line) => [ + line.memberId, + Money.fromMinor(line.utilityShareMinor, cycle.currency) + ]) + ) const convertedRent = await convertIntoCycleCurrency(dependencies, { cycle, @@ -476,6 +487,7 @@ async function buildFinanceDashboard( absencePolicy: resolvedAbsencePolicies.get(line.memberId.toString())?.policy ?? 'resident', absencePolicyEffectiveFromPeriod: resolvedAbsencePolicies.get(line.memberId.toString())?.effectiveFromPeriod ?? null, + predictedUtilityShare: previousUtilityShareByMemberId.get(line.memberId.toString()) ?? null, rentShare: line.rentShare, utilityShare: line.utilityShare, purchaseOffset: line.purchaseOffset,