mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 21:04:03 +00:00
fix(miniapp): clarify cycle summary and balance sections
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Show } from 'solid-js'
|
||||
|
||||
import { cn } from '../../lib/cn'
|
||||
import { formatFriendlyDate } from '../../lib/dates'
|
||||
import { formatCyclePeriod, formatFriendlyDate } from '../../lib/dates'
|
||||
import { majorStringToMinor, sumMajorStrings } from '../../lib/money'
|
||||
import type { MiniAppDashboard } from '../../miniapp-api'
|
||||
import { MiniChip, StatCard } from '../ui'
|
||||
@@ -44,25 +44,25 @@ export function MemberBalanceCard(props: Props) {
|
||||
<header class="balance-spotlight__header">
|
||||
<div class="balance-spotlight__copy">
|
||||
<strong>{props.copy.yourBalanceTitle ?? ''}</strong>
|
||||
<p>{props.copy.yourBalanceBody ?? ''}</p>
|
||||
<Show when={props.copy.yourBalanceBody}>{(body) => <p>{body()}</p>}</Show>
|
||||
</div>
|
||||
<div class="balance-spotlight__hero">
|
||||
<span>{props.copy.remainingLabel ?? ''}</span>
|
||||
<strong>
|
||||
{props.member.remainingMajor} {props.dashboard.currency}
|
||||
</strong>
|
||||
<small>
|
||||
{props.copy.totalDue ?? ''}: {props.member.netDueMajor} {props.dashboard.currency}
|
||||
</small>
|
||||
<Show when={majorStringToMinor(props.member.paidMajor) > 0n}>
|
||||
<small>
|
||||
{props.copy.totalDue ?? ''}: {props.member.netDueMajor} {props.dashboard.currency}
|
||||
</small>
|
||||
</Show>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="balance-spotlight__stats">
|
||||
<StatCard class="balance-spotlight__stat">
|
||||
<span>{props.copy.totalDue ?? ''}</span>
|
||||
<strong>
|
||||
{props.member.netDueMajor} {props.dashboard.currency}
|
||||
</strong>
|
||||
<span>{props.copy.currentCycleLabel ?? ''}</span>
|
||||
<strong>{formatCyclePeriod(props.dashboard.period, props.locale)}</strong>
|
||||
</StatCard>
|
||||
<StatCard class="balance-spotlight__stat">
|
||||
<span>{props.copy.paidLabel ?? ''}</span>
|
||||
|
||||
@@ -26,6 +26,9 @@ export const demoTelegramUser: NonNullable<MiniAppSession['telegramUser']> = {
|
||||
export const demoDashboard: MiniAppDashboard = {
|
||||
period: '2026-03',
|
||||
currency: 'GEL',
|
||||
timezone: 'Asia/Tbilisi',
|
||||
rentDueDay: 20,
|
||||
utilitiesDueDay: 4,
|
||||
paymentBalanceAdjustmentPolicy: 'utilities',
|
||||
totalDueMajor: '2410.00',
|
||||
totalPaidMajor: '650.00',
|
||||
|
||||
@@ -58,17 +58,25 @@ export const dictionary = {
|
||||
ledgerEntries: 'Ledger entries',
|
||||
pendingRequests: 'Pending requests',
|
||||
yourBalanceTitle: 'Your balance',
|
||||
yourBalanceBody:
|
||||
'See rent, pure utilities, purchase balance adjustment, and what is still left to pay.',
|
||||
payNowTitle: 'Pay now',
|
||||
payNowBody:
|
||||
'Your current-cycle summary stays here so you can see the number that matters first.',
|
||||
yourBalanceBody: 'Current cycle breakdown.',
|
||||
payNowTitle: 'This month',
|
||||
payNowBody: '',
|
||||
homeDueTitle: 'Due',
|
||||
homeSettledTitle: 'Settled',
|
||||
currentCycleLabel: 'Current cycle',
|
||||
cycleTotalLabel: 'Cycle total',
|
||||
cycleBillLabel: 'Cycle bill',
|
||||
balanceAdjustmentLabel: 'Balance adjustment',
|
||||
pureUtilitiesLabel: 'Pure utilities',
|
||||
utilitiesBalanceLabel: 'Utilities + balance',
|
||||
rentAdjustedTotalLabel: 'Rent after adjustment',
|
||||
utilitiesAdjustedTotalLabel: 'Utilities after adjustment',
|
||||
paidThisCycleLabel: 'Paid this cycle',
|
||||
rentPaidLabel: 'Rent paid',
|
||||
utilitiesPaidLabel: 'Utilities paid',
|
||||
dueOnLabel: 'Due {date}',
|
||||
upcomingLabel: 'Upcoming',
|
||||
notBilledYetLabel: 'Not billed yet',
|
||||
baseDue: 'Base due',
|
||||
finalDue: 'Final due',
|
||||
houseSnapshotTitle: 'House totals',
|
||||
@@ -338,17 +346,25 @@ export const dictionary = {
|
||||
ledgerEntries: 'Записи леджера',
|
||||
pendingRequests: 'Ожидают подтверждения',
|
||||
yourBalanceTitle: 'Твой баланс',
|
||||
yourBalanceBody:
|
||||
'Здесь отдельно видно аренду, чистую коммуналку, поправку по покупкам и то, что осталось оплатить.',
|
||||
payNowTitle: 'К оплате сейчас',
|
||||
payNowBody:
|
||||
'Здесь остаётся только короткая сводка по текущему циклу, чтобы сразу видеть нужную сумму.',
|
||||
yourBalanceBody: 'Разбор по текущему циклу.',
|
||||
payNowTitle: 'Этот месяц',
|
||||
payNowBody: '',
|
||||
homeDueTitle: 'К оплате',
|
||||
homeSettledTitle: 'Закрыто',
|
||||
currentCycleLabel: 'Текущий цикл',
|
||||
cycleTotalLabel: 'Всего за цикл',
|
||||
cycleBillLabel: 'Счёт за цикл',
|
||||
balanceAdjustmentLabel: 'Поправка по балансу',
|
||||
pureUtilitiesLabel: 'Чистая коммуналка',
|
||||
utilitiesBalanceLabel: 'Коммуналка + баланс',
|
||||
rentAdjustedTotalLabel: 'Аренда после зачёта',
|
||||
utilitiesAdjustedTotalLabel: 'Коммуналка после зачёта',
|
||||
paidThisCycleLabel: 'Оплачено за цикл',
|
||||
rentPaidLabel: 'По аренде оплачено',
|
||||
utilitiesPaidLabel: 'По коммуналке оплачено',
|
||||
dueOnLabel: 'Срок {date}',
|
||||
upcomingLabel: 'Ещё не срок',
|
||||
notBilledYetLabel: 'Ещё не начислено',
|
||||
baseDue: 'База к оплате',
|
||||
finalDue: 'Итог к оплате',
|
||||
houseSnapshotTitle: 'Сводка по дому',
|
||||
|
||||
@@ -490,6 +490,12 @@ button:disabled {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.balance-spotlight__copy small,
|
||||
.balance-detail-row__main small {
|
||||
color: #c6c2bb;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.balance-spotlight__hero {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -1176,6 +1182,32 @@ button:disabled {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.balance-section {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.balance-section__header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.balance-section__copy {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.household-balance-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.household-balance-list__card .ledger-compact-card__meta {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.app-context-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -32,6 +32,48 @@ function formatCalendarDate(
|
||||
}).format(new Date(Date.UTC(year, month - 1, day)))
|
||||
}
|
||||
|
||||
function parsePeriod(period: string): { year: number; month: number } | null {
|
||||
const [yearValue, monthValue] = period.split('-')
|
||||
const year = Number.parseInt(yearValue ?? '', 10)
|
||||
const month = Number.parseInt(monthValue ?? '', 10)
|
||||
|
||||
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
year,
|
||||
month
|
||||
}
|
||||
}
|
||||
|
||||
function daysInMonth(year: number, month: number): number {
|
||||
return new Date(Date.UTC(year, month, 0)).getUTCDate()
|
||||
}
|
||||
|
||||
function formatTodayParts(timezone: string): { year: number; month: number; day: number } | null {
|
||||
try {
|
||||
const parts = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: timezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}).formatToParts(new Date())
|
||||
|
||||
const year = Number.parseInt(parts.find((part) => part.type === 'year')?.value ?? '', 10)
|
||||
const month = Number.parseInt(parts.find((part) => part.type === 'month')?.value ?? '', 10)
|
||||
const day = Number.parseInt(parts.find((part) => part.type === 'day')?.value ?? '', 10)
|
||||
|
||||
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return { year, month, day }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function formatFriendlyDate(value: string, locale: Locale): string {
|
||||
const calendarDateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
|
||||
if (calendarDateMatch) {
|
||||
@@ -61,19 +103,56 @@ export function formatFriendlyDate(value: string, locale: Locale): string {
|
||||
}
|
||||
|
||||
export function formatCyclePeriod(period: string, locale: Locale): string {
|
||||
const [yearValue, monthValue] = period.split('-')
|
||||
const year = Number.parseInt(yearValue ?? '', 10)
|
||||
const month = Number.parseInt(monthValue ?? '', 10)
|
||||
|
||||
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
|
||||
const parsed = parsePeriod(period)
|
||||
if (!parsed) {
|
||||
return period
|
||||
}
|
||||
|
||||
const date = new Date(Date.UTC(year, month - 1, 1))
|
||||
const includeYear = year !== new Date().getUTCFullYear()
|
||||
const date = new Date(Date.UTC(parsed.year, parsed.month - 1, 1))
|
||||
const includeYear = parsed.year !== new Date().getUTCFullYear()
|
||||
|
||||
return new Intl.DateTimeFormat(localeTag(locale), {
|
||||
month: 'long',
|
||||
...(includeYear ? { year: 'numeric' } : {})
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
export function formatPeriodDay(period: string, day: number, locale: Locale): string {
|
||||
const parsed = parsePeriod(period)
|
||||
if (!parsed) {
|
||||
return period
|
||||
}
|
||||
|
||||
const safeDay = Math.max(1, Math.min(day, daysInMonth(parsed.year, parsed.month)))
|
||||
|
||||
return (
|
||||
formatCalendarDate(parsed.year, parsed.month, safeDay, locale) ??
|
||||
`${formatCyclePeriod(period, locale)} ${safeDay}`
|
||||
)
|
||||
}
|
||||
|
||||
export function compareTodayToPeriodDay(
|
||||
period: string,
|
||||
day: number,
|
||||
timezone: string
|
||||
): -1 | 0 | 1 | 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)
|
||||
|
||||
if (todayValue < dueValue) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (todayValue > dueValue) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ const CURATED_TIMEZONES = [
|
||||
'Asia/Tbilisi',
|
||||
'Europe/Berlin',
|
||||
'Europe/London',
|
||||
'Europe/Moscow',
|
||||
'Europe/Paris',
|
||||
'Europe/Warsaw',
|
||||
'America/New_York',
|
||||
|
||||
@@ -95,6 +95,9 @@ export interface MiniAppTopicBinding {
|
||||
export interface MiniAppDashboard {
|
||||
period: string
|
||||
currency: 'USD' | 'GEL'
|
||||
timezone: string
|
||||
rentDueDay: number
|
||||
utilitiesDueDay: number
|
||||
paymentBalanceAdjustmentPolicy: 'utilities' | 'rent' | 'separate'
|
||||
totalDueMajor: string
|
||||
totalPaidMajor: string
|
||||
|
||||
@@ -107,46 +107,54 @@ export function BalancesScreen(props: Props) {
|
||||
purchaseShareLabel: props.copy.purchaseShareLabel ?? ''
|
||||
}}
|
||||
/>
|
||||
<article class="balance-item balance-item--wide">
|
||||
<header>
|
||||
<strong>{props.copy.householdBalancesTitle ?? ''}</strong>
|
||||
<span>{String(dashboard().members.length)}</span>
|
||||
<section class="balance-item balance-item--wide balance-section">
|
||||
<header class="balance-section__header">
|
||||
<div class="balance-section__copy">
|
||||
<strong>{props.copy.householdBalancesTitle ?? ''}</strong>
|
||||
<p>{props.copy.householdBalancesBody ?? ''}</p>
|
||||
</div>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{String(dashboard().members.length)} {props.copy.membersCount ?? ''}
|
||||
</span>
|
||||
</header>
|
||||
<p>{props.copy.householdBalancesBody ?? ''}</p>
|
||||
</article>
|
||||
<For each={dashboard().members}>
|
||||
{(member) => (
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{member.displayName}</strong>
|
||||
<span>
|
||||
{member.remainingMajor} {dashboard().currency}
|
||||
</span>
|
||||
</header>
|
||||
<p>
|
||||
{props.copy.baseDue ?? ''}: {props.memberBaseDueMajor(member)}{' '}
|
||||
{dashboard().currency}
|
||||
</p>
|
||||
<p>
|
||||
{props.copy.shareRent ?? ''}: {member.rentShareMajor} {dashboard().currency}
|
||||
</p>
|
||||
<p>
|
||||
{props.copy.shareUtilities ?? ''}: {member.utilityShareMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</p>
|
||||
<p>
|
||||
{props.copy.shareOffset ?? ''}: {member.purchaseOffsetMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</p>
|
||||
<p>
|
||||
{props.copy.paidLabel ?? ''}: {member.paidMajor} {dashboard().currency}
|
||||
</p>
|
||||
<p class={`balance-status ${props.memberRemainingClass(member)}`}>
|
||||
{props.copy.remainingLabel ?? ''}: {member.remainingMajor} {dashboard().currency}
|
||||
</p>
|
||||
</article>
|
||||
)}
|
||||
</For>
|
||||
<div class="household-balance-list">
|
||||
<For each={dashboard().members}>
|
||||
{(member) => (
|
||||
<article class="ledger-compact-card household-balance-list__card">
|
||||
<div class="ledger-compact-card__main">
|
||||
<header>
|
||||
<strong>{member.displayName}</strong>
|
||||
<span class={`balance-status ${props.memberRemainingClass(member)}`}>
|
||||
{member.remainingMajor} {dashboard().currency}
|
||||
</span>
|
||||
</header>
|
||||
<div class="ledger-compact-card__meta">
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.baseDue ?? ''}: {props.memberBaseDueMajor(member)}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.shareRent ?? ''}: {member.rentShareMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.shareUtilities ?? ''}: {member.utilityShareMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.shareOffset ?? ''}: {member.purchaseOffsetMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.paidLabel ?? ''}: {member.paidMajor} {dashboard().currency}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Show } from 'solid-js'
|
||||
|
||||
import { FinanceSummaryCards } from '../components/finance/finance-summary-cards'
|
||||
import { formatCyclePeriod } from '../lib/dates'
|
||||
import { sumMajorStrings } from '../lib/money'
|
||||
import { compareTodayToPeriodDay, formatCyclePeriod, formatPeriodDay } from '../lib/dates'
|
||||
import { majorStringToMinor, minorToMajorString, sumMajorStrings } from '../lib/money'
|
||||
import type { MiniAppDashboard } from '../miniapp-api'
|
||||
|
||||
type Props = {
|
||||
@@ -15,6 +15,43 @@ type Props = {
|
||||
}
|
||||
|
||||
export function HomeScreen(props: Props) {
|
||||
const rentPaidMajor = () => {
|
||||
if (!props.dashboard || !props.currentMemberLine) {
|
||||
return '0.00'
|
||||
}
|
||||
|
||||
const totalMinor = props.dashboard.ledger
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.kind === 'payment' &&
|
||||
entry.memberId === props.currentMemberLine?.memberId &&
|
||||
entry.paymentKind === 'rent'
|
||||
)
|
||||
.reduce((sum, entry) => sum + majorStringToMinor(entry.displayAmountMajor), 0n)
|
||||
|
||||
return minorToMajorString(totalMinor)
|
||||
}
|
||||
|
||||
const utilitiesPaidMajor = () => {
|
||||
if (!props.dashboard || !props.currentMemberLine) {
|
||||
return '0.00'
|
||||
}
|
||||
|
||||
const totalMinor = props.dashboard.ledger
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.kind === 'payment' &&
|
||||
entry.memberId === props.currentMemberLine?.memberId &&
|
||||
entry.paymentKind === 'utilities'
|
||||
)
|
||||
.reduce((sum, entry) => sum + majorStringToMinor(entry.displayAmountMajor), 0n)
|
||||
|
||||
return minorToMajorString(totalMinor)
|
||||
}
|
||||
|
||||
const hasUtilityBills = () =>
|
||||
Boolean(props.dashboard?.ledger.some((entry) => entry.kind === 'utility'))
|
||||
|
||||
const adjustedRentMajor = () => {
|
||||
if (!props.currentMemberLine) {
|
||||
return null
|
||||
@@ -37,6 +74,115 @@ export function HomeScreen(props: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
const rentDueMajor = () => {
|
||||
if (!props.currentMemberLine || !props.dashboard) {
|
||||
return null
|
||||
}
|
||||
|
||||
return props.dashboard.paymentBalanceAdjustmentPolicy === 'rent'
|
||||
? adjustedRentMajor()
|
||||
: props.currentMemberLine.rentShareMajor
|
||||
}
|
||||
|
||||
const utilitiesDueMajor = () => {
|
||||
if (!props.currentMemberLine || !props.dashboard || !hasUtilityBills()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return props.dashboard.paymentBalanceAdjustmentPolicy === 'utilities'
|
||||
? adjustedUtilitiesMajor()
|
||||
: props.currentMemberLine.utilityShareMajor
|
||||
}
|
||||
|
||||
const separateBalanceMajor = () => {
|
||||
if (
|
||||
!props.currentMemberLine ||
|
||||
props.dashboard?.paymentBalanceAdjustmentPolicy !== 'separate'
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return props.currentMemberLine.purchaseOffsetMajor
|
||||
}
|
||||
|
||||
const heroState = () => {
|
||||
if (!props.dashboard || !props.currentMemberLine) {
|
||||
return {
|
||||
title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '',
|
||||
label: props.copy.remainingLabel ?? '',
|
||||
amountMajor: '—'
|
||||
}
|
||||
}
|
||||
|
||||
const remainingMinor = majorStringToMinor(props.currentMemberLine.remainingMajor)
|
||||
const paidMinor = majorStringToMinor(props.currentMemberLine.paidMajor)
|
||||
const rentStatus = compareTodayToPeriodDay(
|
||||
props.dashboard.period,
|
||||
props.dashboard.rentDueDay,
|
||||
props.dashboard.timezone
|
||||
)
|
||||
const utilitiesStatus = compareTodayToPeriodDay(
|
||||
props.dashboard.period,
|
||||
props.dashboard.utilitiesDueDay,
|
||||
props.dashboard.timezone
|
||||
)
|
||||
const hasDueNow =
|
||||
(rentStatus !== null &&
|
||||
rentStatus >= 0 &&
|
||||
majorStringToMinor(rentDueMajor() ?? '0.00') > 0n) ||
|
||||
(utilitiesStatus !== null &&
|
||||
utilitiesStatus >= 0 &&
|
||||
majorStringToMinor(utilitiesDueMajor() ?? '0.00') > 0n) ||
|
||||
(props.dashboard.paymentBalanceAdjustmentPolicy === 'separate' &&
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDueNow) {
|
||||
return {
|
||||
title: props.copy.homeDueTitle ?? props.copy.payNowTitle ?? '',
|
||||
label: props.copy.remainingLabel ?? '',
|
||||
amountMajor: props.currentMemberLine.remainingMajor
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? '',
|
||||
label: props.copy.cycleTotalLabel ?? props.copy.totalDue ?? '',
|
||||
amountMajor: props.currentMemberLine.netDueMajor
|
||||
}
|
||||
}
|
||||
|
||||
const dueLabel = (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)
|
||||
|
||||
if (comparison !== null && comparison < 0) {
|
||||
return `${template}${template.length > 0 ? ' ' : ''}${date}`.trim()
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.dashboard}
|
||||
@@ -64,17 +210,14 @@ export function HomeScreen(props: Props) {
|
||||
<article class="balance-item balance-item--accent home-pay-card">
|
||||
<header class="home-pay-card__header">
|
||||
<div class="home-pay-card__copy">
|
||||
<strong>{props.copy.payNowTitle ?? props.copy.yourBalanceTitle ?? ''}</strong>
|
||||
<p>{props.copy.payNowBody ?? ''}</p>
|
||||
<strong>{heroState().title}</strong>
|
||||
<small>{formatCyclePeriod(dashboard().period, props.locale)}</small>
|
||||
</div>
|
||||
<div class="balance-spotlight__hero">
|
||||
<span>{props.copy.remainingLabel ?? ''}</span>
|
||||
<span>{heroState().label}</span>
|
||||
<strong>
|
||||
{member().remainingMajor} {dashboard().currency}
|
||||
{heroState().amountMajor} {dashboard().currency}
|
||||
</strong>
|
||||
<small>
|
||||
{props.copy.totalDue ?? ''}: {member().netDueMajor} {dashboard().currency}
|
||||
</small>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -86,36 +229,66 @@ export function HomeScreen(props: Props) {
|
||||
</strong>
|
||||
</article>
|
||||
<article class="stat-card balance-spotlight__stat">
|
||||
<span>{props.copy.currentCycleLabel ?? ''}</span>
|
||||
<strong>{formatCyclePeriod(dashboard().period, props.locale)}</strong>
|
||||
<span>{props.copy.remainingLabel ?? ''}</span>
|
||||
<strong>
|
||||
{member().remainingMajor} {dashboard().currency}
|
||||
</strong>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="home-pay-card__chips">
|
||||
<span class="mini-chip">
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'rent'
|
||||
? props.copy.rentAdjustedTotalLabel
|
||||
: props.copy.shareRent}
|
||||
:{' '}
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'rent'
|
||||
? adjustedRentMajor()
|
||||
: member().rentShareMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'utilities'
|
||||
? props.copy.utilitiesAdjustedTotalLabel
|
||||
: props.copy.shareUtilities}
|
||||
:{' '}
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'utilities'
|
||||
? adjustedUtilitiesMajor()
|
||||
: member().utilityShareMajor}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.balanceAdjustmentLabel ?? props.copy.shareOffset}:{' '}
|
||||
{member().purchaseOffsetMajor} {dashboard().currency}
|
||||
</span>
|
||||
<div class="balance-spotlight__rows">
|
||||
<article class="balance-detail-row">
|
||||
<div class="balance-detail-row__main">
|
||||
<span>
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'rent'
|
||||
? props.copy.rentAdjustedTotalLabel
|
||||
: props.copy.shareRent}
|
||||
</span>
|
||||
<strong>
|
||||
{rentDueMajor()} {dashboard().currency}
|
||||
</strong>
|
||||
<small>{dueLabel('rent')}</small>
|
||||
</div>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.rentPaidLabel ?? props.copy.paidLabel}: {rentPaidMajor()}{' '}
|
||||
{dashboard().currency}
|
||||
</span>
|
||||
</article>
|
||||
|
||||
<article class="balance-detail-row">
|
||||
<div class="balance-detail-row__main">
|
||||
<span>
|
||||
{dashboard().paymentBalanceAdjustmentPolicy === 'utilities'
|
||||
? props.copy.utilitiesAdjustedTotalLabel
|
||||
: (props.copy.utilitiesBalanceLabel ?? props.copy.shareUtilities)}
|
||||
</span>
|
||||
<strong>
|
||||
{utilitiesDueMajor() !== null
|
||||
? `${utilitiesDueMajor()} ${dashboard().currency}`
|
||||
: (props.copy.notBilledYetLabel ?? '')}
|
||||
</strong>
|
||||
<small>
|
||||
{utilitiesDueMajor() !== null
|
||||
? dueLabel('utilities')
|
||||
: dueLabel('utilities')}
|
||||
</small>
|
||||
</div>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{props.copy.utilitiesPaidLabel ?? props.copy.paidLabel}:{' '}
|
||||
{utilitiesPaidMajor()} {dashboard().currency}
|
||||
</span>
|
||||
</article>
|
||||
|
||||
<Show when={dashboard().paymentBalanceAdjustmentPolicy === 'separate'}>
|
||||
<article class="balance-detail-row">
|
||||
<div class="balance-detail-row__main">
|
||||
<span>{props.copy.balanceAdjustmentLabel ?? props.copy.shareOffset}</span>
|
||||
<strong>
|
||||
{separateBalanceMajor()} {dashboard().currency}
|
||||
</strong>
|
||||
</div>
|
||||
</article>
|
||||
</Show>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user