fix(miniapp): make dashboard screens react to initial load

This commit is contained in:
2026-03-12 01:32:05 +04:00
parent 7005efbc7b
commit 7467d3a4cf
3 changed files with 700 additions and 687 deletions

View File

@@ -39,129 +39,132 @@ type Props = {
} }
export function BalancesScreen(props: Props) { export function BalancesScreen(props: Props) {
if (!props.dashboard) {
return (
<div class="balance-list">
<p>{props.copy.emptyDashboard ?? ''}</p>
</div>
)
}
return ( return (
<div class="balance-list"> <Show
<Show when={props.currentMemberLine}> when={props.dashboard}
{(member) => ( fallback={
<article class="balance-item balance-item--accent"> <div class="balance-list">
<header> <p>{props.copy.emptyDashboard ?? ''}</p>
<strong>{props.copy.yourBalanceTitle ?? ''}</strong> </div>
<span> }
{member().netDueMajor} {props.dashboard!.currency} >
</span> {(dashboard) => (
</header> <div class="balance-list">
<p>{props.copy.yourBalanceBody ?? ''}</p> <Show when={props.currentMemberLine}>
<div class="balance-breakdown"> {(member) => (
<article class="stat-card"> <article class="balance-item balance-item--accent">
<span>{props.copy.baseDue ?? ''}</span> <header>
<strong> <strong>{props.copy.yourBalanceTitle ?? ''}</strong>
{props.memberBaseDueMajor(member())} {props.dashboard!.currency} <span>
</strong> {member().netDueMajor} {dashboard().currency}
</span>
</header>
<p>{props.copy.yourBalanceBody ?? ''}</p>
<div class="balance-breakdown">
<article class="stat-card">
<span>{props.copy.baseDue ?? ''}</span>
<strong>
{props.memberBaseDueMajor(member())} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.shareOffset ?? ''}</span>
<strong>
{member().purchaseOffsetMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.finalDue ?? ''}</span>
<strong>
{member().netDueMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.paidLabel ?? ''}</span>
<strong>
{member().paidMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.remainingLabel ?? ''}</span>
<strong>
{member().remainingMajor} {dashboard().currency}
</strong>
</article>
</div>
</article> </article>
<article class="stat-card"> )}
<span>{props.copy.shareOffset ?? ''}</span> </Show>
<strong> <div class="summary-card-grid">
{member().purchaseOffsetMajor} {props.dashboard!.currency} <FinanceSummaryCards
</strong> dashboard={dashboard()}
</article> utilityTotalMajor={props.utilityTotalMajor}
<article class="stat-card"> purchaseTotalMajor={props.purchaseTotalMajor}
<span>{props.copy.finalDue ?? ''}</span> labels={{
<strong> remaining: props.copy.remainingLabel ?? '',
{member().netDueMajor} {props.dashboard!.currency} rent: props.copy.shareRent ?? '',
</strong> utilities: props.copy.shareUtilities ?? '',
</article> purchases: props.copy.purchasesTitle ?? ''
<article class="stat-card"> }}
<span>{props.copy.paidLabel ?? ''}</span> />
<strong> </div>
{member().paidMajor} {props.dashboard!.currency} <FinanceVisuals
</strong> dashboard={dashboard()}
</article> memberVisuals={props.memberBalanceVisuals}
<article class="stat-card"> purchaseChart={props.purchaseChart}
<span>{props.copy.remainingLabel ?? ''}</span> remainingClass={props.memberRemainingClass}
<strong> labels={{
{member().remainingMajor} {props.dashboard!.currency} financeVisualsTitle: props.copy.financeVisualsTitle ?? '',
</strong> financeVisualsBody: props.copy.financeVisualsBody ?? '',
</article> membersCount: props.copy.membersCount ?? '',
</div> purchaseInvestmentsTitle: props.copy.purchaseInvestmentsTitle ?? '',
</article> purchaseInvestmentsBody: props.copy.purchaseInvestmentsBody ?? '',
)} purchaseInvestmentsEmpty: props.copy.purchaseInvestmentsEmpty ?? '',
</Show> purchaseTotalLabel: props.copy.purchaseTotalLabel ?? '',
<div class="summary-card-grid"> purchaseShareLabel: props.copy.purchaseShareLabel ?? ''
<FinanceSummaryCards }}
dashboard={props.dashboard} />
utilityTotalMajor={props.utilityTotalMajor}
purchaseTotalMajor={props.purchaseTotalMajor}
labels={{
remaining: props.copy.remainingLabel ?? '',
rent: props.copy.shareRent ?? '',
utilities: props.copy.shareUtilities ?? '',
purchases: props.copy.purchasesTitle ?? ''
}}
/>
</div>
<FinanceVisuals
dashboard={props.dashboard}
memberVisuals={props.memberBalanceVisuals}
purchaseChart={props.purchaseChart}
remainingClass={props.memberRemainingClass}
labels={{
financeVisualsTitle: props.copy.financeVisualsTitle ?? '',
financeVisualsBody: props.copy.financeVisualsBody ?? '',
membersCount: props.copy.membersCount ?? '',
purchaseInvestmentsTitle: props.copy.purchaseInvestmentsTitle ?? '',
purchaseInvestmentsBody: props.copy.purchaseInvestmentsBody ?? '',
purchaseInvestmentsEmpty: props.copy.purchaseInvestmentsEmpty ?? '',
purchaseTotalLabel: props.copy.purchaseTotalLabel ?? '',
purchaseShareLabel: props.copy.purchaseShareLabel ?? ''
}}
/>
<article class="balance-item">
<header>
<strong>{props.copy.householdBalancesTitle ?? ''}</strong>
</header>
<p>{props.copy.householdBalancesBody ?? ''}</p>
</article>
<For each={props.dashboard.members}>
{(member) => (
<article class="balance-item"> <article class="balance-item">
<header> <header>
<strong>{member.displayName}</strong> <strong>{props.copy.householdBalancesTitle ?? ''}</strong>
<span>
{member.remainingMajor} {props.dashboard!.currency}
</span>
</header> </header>
<p> <p>{props.copy.householdBalancesBody ?? ''}</p>
{props.copy.baseDue ?? ''}: {props.memberBaseDueMajor(member)}{' '}
{props.dashboard!.currency}
</p>
<p>
{props.copy.shareRent ?? ''}: {member.rentShareMajor} {props.dashboard!.currency}
</p>
<p>
{props.copy.shareUtilities ?? ''}: {member.utilityShareMajor}{' '}
{props.dashboard!.currency}
</p>
<p>
{props.copy.shareOffset ?? ''}: {member.purchaseOffsetMajor}{' '}
{props.dashboard!.currency}
</p>
<p>
{props.copy.paidLabel ?? ''}: {member.paidMajor} {props.dashboard!.currency}
</p>
<p class={`balance-status ${props.memberRemainingClass(member)}`}>
{props.copy.remainingLabel ?? ''}: {member.remainingMajor} {props.dashboard!.currency}
</p>
</article> </article>
)} <For each={dashboard().members}>
</For> {(member) => (
</div> <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>
)}
</Show>
) )
} }

View File

@@ -18,130 +18,133 @@ type Props = {
} }
export function HomeScreen(props: Props) { export function HomeScreen(props: Props) {
if (!props.dashboard) { return (
return ( <Show
<div class="home-grid"> when={props.dashboard}
<div class="summary-card-grid"> fallback={
<article class="stat-card"> <div class="home-grid">
<span>{props.copy.remainingLabel ?? ''}</span> <div class="summary-card-grid">
<strong></strong> <article class="stat-card">
</article> <span>{props.copy.remainingLabel ?? ''}</span>
<article class="stat-card"> <strong></strong>
<span>{props.copy.shareRent ?? ''}</span> </article>
<strong></strong> <article class="stat-card">
</article> <span>{props.copy.shareRent ?? ''}</span>
<article class="stat-card"> <strong></strong>
<span>{props.copy.shareUtilities ?? ''}</span> </article>
<strong></strong> <article class="stat-card">
</article> <span>{props.copy.shareUtilities ?? ''}</span>
<article class="stat-card"> <strong></strong>
<span>{props.copy.purchasesTitle ?? ''}</span> </article>
<strong></strong> <article class="stat-card">
<span>{props.copy.purchasesTitle ?? ''}</span>
<strong></strong>
</article>
</div>
</div>
}
>
{(dashboard) => (
<div class="home-grid">
<div class="summary-card-grid">
<FinanceSummaryCards
dashboard={dashboard()}
utilityTotalMajor={props.utilityTotalMajor}
purchaseTotalMajor={props.purchaseTotalMajor}
labels={{
remaining: props.copy.remainingLabel ?? '',
rent: props.copy.shareRent ?? '',
utilities: props.copy.shareUtilities ?? '',
purchases: props.copy.purchasesTitle ?? ''
}}
/>
<Show when={props.readyIsAdmin}>
<article class="stat-card">
<span>{props.copy.pendingRequests ?? ''}</span>
<strong>{String(props.pendingMembersCount)}</strong>
</article>
</Show>
</div>
<Show when={props.currentMemberLine}>
{(member) => (
<article class="balance-item balance-item--accent">
<header>
<strong>{props.copy.yourBalanceTitle ?? ''}</strong>
<span>
{member().remainingMajor} {dashboard().currency}
</span>
</header>
<p>
{props.copy.shareRent ?? ''}: {dashboard().rentSourceAmountMajor}{' '}
{dashboard().rentSourceCurrency}
{dashboard().rentSourceCurrency !== dashboard().currency
? ` -> ${dashboard().rentDisplayAmountMajor} ${dashboard().currency}`
: ''}
</p>
<div class="balance-breakdown">
<article class="stat-card">
<span>{props.copy.baseDue ?? ''}</span>
<strong>
{props.memberBaseDueMajor(member())} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.shareOffset ?? ''}</span>
<strong>
{member().purchaseOffsetMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.finalDue ?? ''}</span>
<strong>
{member().netDueMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.paidLabel ?? ''}</span>
<strong>
{member().paidMajor} {dashboard().currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.remainingLabel ?? ''}</span>
<strong>
{member().remainingMajor} {dashboard().currency}
</strong>
</article>
</div>
</article>
)}
</Show>
<article class="balance-item balance-item--wide">
<header>
<strong>{props.copy.latestActivityTitle ?? ''}</strong>
</header>
{dashboard().ledger.length === 0 ? (
<p>{props.copy.latestActivityEmpty ?? ''}</p>
) : (
<div class="activity-list">
<For each={dashboard().ledger.slice(0, 3)}>
{(entry) => (
<article class="activity-row">
<header>
<strong>{props.ledgerTitle(entry)}</strong>
<span>{props.ledgerPrimaryAmount(entry)}</span>
</header>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => <p>{secondary()}</p>}
</Show>
<p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p>
</article>
)}
</For>
</div>
)}
</article> </article>
</div> </div>
</div> )}
) </Show>
}
return (
<div class="home-grid">
<div class="summary-card-grid">
<FinanceSummaryCards
dashboard={props.dashboard}
utilityTotalMajor={props.utilityTotalMajor}
purchaseTotalMajor={props.purchaseTotalMajor}
labels={{
remaining: props.copy.remainingLabel ?? '',
rent: props.copy.shareRent ?? '',
utilities: props.copy.shareUtilities ?? '',
purchases: props.copy.purchasesTitle ?? ''
}}
/>
<Show when={props.readyIsAdmin}>
<article class="stat-card">
<span>{props.copy.pendingRequests ?? ''}</span>
<strong>{String(props.pendingMembersCount)}</strong>
</article>
</Show>
</div>
<Show when={props.currentMemberLine}>
{(member) => (
<article class="balance-item balance-item--accent">
<header>
<strong>{props.copy.yourBalanceTitle ?? ''}</strong>
<span>
{member().remainingMajor} {props.dashboard!.currency}
</span>
</header>
<p>
{props.copy.shareRent ?? ''}: {props.dashboard!.rentSourceAmountMajor}{' '}
{props.dashboard!.rentSourceCurrency}
{props.dashboard!.rentSourceCurrency !== props.dashboard!.currency
? ` -> ${props.dashboard!.rentDisplayAmountMajor} ${props.dashboard!.currency}`
: ''}
</p>
<div class="balance-breakdown">
<article class="stat-card">
<span>{props.copy.baseDue ?? ''}</span>
<strong>
{props.memberBaseDueMajor(member())} {props.dashboard!.currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.shareOffset ?? ''}</span>
<strong>
{member().purchaseOffsetMajor} {props.dashboard!.currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.finalDue ?? ''}</span>
<strong>
{member().netDueMajor} {props.dashboard!.currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.paidLabel ?? ''}</span>
<strong>
{member().paidMajor} {props.dashboard!.currency}
</strong>
</article>
<article class="stat-card">
<span>{props.copy.remainingLabel ?? ''}</span>
<strong>
{member().remainingMajor} {props.dashboard!.currency}
</strong>
</article>
</div>
</article>
)}
</Show>
<article class="balance-item balance-item--wide">
<header>
<strong>{props.copy.latestActivityTitle ?? ''}</strong>
</header>
{props.dashboard.ledger.length === 0 ? (
<p>{props.copy.latestActivityEmpty ?? ''}</p>
) : (
<div class="activity-list">
<For each={props.dashboard.ledger.slice(0, 3)}>
{(entry) => (
<article class="activity-row">
<header>
<strong>{props.ledgerTitle(entry)}</strong>
<span>{props.ledgerPrimaryAmount(entry)}</span>
</header>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => <p>{secondary()}</p>}
</Show>
<p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p>
</article>
)}
</For>
</div>
)}
</article>
</div>
) )
} }

View File

@@ -118,130 +118,467 @@ type Props = {
} }
export function LedgerScreen(props: Props) { export function LedgerScreen(props: Props) {
if (!props.dashboard) {
return (
<div class="ledger-list">
<p>{props.copy.emptyDashboard ?? ''}</p>
</div>
)
}
return ( return (
<div class="ledger-list"> <Show
<article class="balance-item"> when={props.dashboard}
<header> fallback={
<strong> <div class="ledger-list">
{props.readyIsAdmin ? props.copy.purchaseReviewTitle : props.copy.purchasesTitle} <p>{props.copy.emptyDashboard ?? ''}</p>
</strong> </div>
</header> }
<Show when={props.readyIsAdmin}> >
<p>{props.copy.purchaseReviewBody ?? ''}</p> <div class="ledger-list">
</Show> <article class="balance-item">
{props.purchaseEntries.length === 0 ? ( <header>
<p>{props.copy.purchasesEmpty ?? ''}</p> <strong>
) : ( {props.readyIsAdmin ? props.copy.purchaseReviewTitle : props.copy.purchasesTitle}
<div class="ledger-list"> </strong>
<For each={props.purchaseEntries}> </header>
{(entry) => ( <Show when={props.readyIsAdmin}>
<article class="ledger-compact-card"> <p>{props.copy.purchaseReviewBody ?? ''}</p>
<div class="ledger-compact-card__main"> </Show>
<header> {props.purchaseEntries.length === 0 ? (
<strong>{entry.title}</strong> <p>{props.copy.purchasesEmpty ?? ''}</p>
<span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span> ) : (
</header> <div class="ledger-list">
<p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p> <For each={props.purchaseEntries}>
<div class="ledger-compact-card__meta"> {(entry) => (
<span class="mini-chip">{props.ledgerPrimaryAmount(entry)}</span> <article class="ledger-compact-card">
<Show when={props.ledgerSecondaryAmount(entry)}> <div class="ledger-compact-card__main">
{(secondary) => ( <header>
<span class="mini-chip mini-chip--muted">{secondary()}</span> <strong>{entry.title}</strong>
)} <span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span>
</Show> </header>
<span class="mini-chip mini-chip--muted"> <p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p>
{props.purchaseParticipantSummary(entry)} <div class="ledger-compact-card__meta">
</span> <span class="mini-chip">{props.ledgerPrimaryAmount(entry)}</span>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => (
<span class="mini-chip mini-chip--muted">{secondary()}</span>
)}
</Show>
<span class="mini-chip mini-chip--muted">
{props.purchaseParticipantSummary(entry)}
</span>
</div>
</div> </div>
</div> <Show when={props.readyIsAdmin}>
<Show when={props.readyIsAdmin}> <div class="ledger-compact-card__actions">
<div class="ledger-compact-card__actions"> <IconButton
<IconButton label={props.copy.editEntryAction ?? ''}
label={props.copy.editEntryAction ?? ''} onClick={() => props.onOpenPurchaseEditor(entry.id)}
onClick={() => props.onOpenPurchaseEditor(entry.id)} >
> <PencilIcon />
<PencilIcon /> </IconButton>
</IconButton> </div>
</div> </Show>
</Show> </article>
</article> )}
)} </For>
</For>
</div>
)}
</article>
<Modal
open={Boolean(props.editingPurchaseEntry)}
title={props.copy.purchaseReviewTitle ?? ''}
description={props.copy.purchaseEditorBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onClosePurchaseEditor}
footer={(() => {
const entry = props.editingPurchaseEntry
if (!entry) {
return null
}
return (
<div class="modal-action-row">
<Button variant="danger" onClick={() => void props.onDeletePurchase(entry.id)}>
{props.deletingPurchaseId === entry.id
? props.copy.deletingPurchase
: props.copy.purchaseDeleteAction}
</Button>
<div class="modal-action-row__primary">
<Button variant="ghost" onClick={props.onClosePurchaseEditor}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.savingPurchaseId === entry.id}
onClick={() => void props.onSavePurchase(entry.id)}
>
{props.savingPurchaseId === entry.id
? props.copy.savingPurchase
: props.copy.purchaseSaveAction}
</Button>
</div>
</div> </div>
) )}
})()} </article>
> <Modal
{(() => { open={Boolean(props.editingPurchaseEntry)}
const entry = props.editingPurchaseEntry title={props.copy.purchaseReviewTitle ?? ''}
description={props.copy.purchaseEditorBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onClosePurchaseEditor}
footer={(() => {
const entry = props.editingPurchaseEntry
if (!entry) { if (!entry) {
return null return null
}
return (
<div class="modal-action-row">
<Button variant="danger" onClick={() => void props.onDeletePurchase(entry.id)}>
{props.deletingPurchaseId === entry.id
? props.copy.deletingPurchase
: props.copy.purchaseDeleteAction}
</Button>
<div class="modal-action-row__primary">
<Button variant="ghost" onClick={props.onClosePurchaseEditor}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.savingPurchaseId === entry.id}
onClick={() => void props.onSavePurchase(entry.id)}
>
{props.savingPurchaseId === entry.id
? props.copy.savingPurchase
: props.copy.purchaseSaveAction}
</Button>
</div>
</div>
)
})()}
>
{(() => {
const entry = props.editingPurchaseEntry
if (!entry) {
return null
}
const draft = props.purchaseDraftMap[entry.id] ?? props.purchaseDraftForEntry(entry)
const splitPreview = props.purchaseSplitPreview(entry.id)
return (
<>
<div class="editor-grid">
<Field label={props.copy.purchaseReviewTitle ?? ''} wide>
<input
value={draft.description}
onInput={(event) =>
props.onPurchaseDescriptionChange(
entry.id,
entry,
event.currentTarget.value
)
}
/>
</Field>
<Field label={props.copy.paymentAmount ?? ''}>
<input
value={draft.amountMajor}
onInput={(event) =>
props.onPurchaseAmountChange(entry.id, entry, event.currentTarget.value)
}
/>
</Field>
<Field label={props.copy.settlementCurrency ?? ''}>
<select
value={draft.currency}
onChange={(event) =>
props.onPurchaseCurrencyChange(
entry.id,
entry,
event.currentTarget.value as 'USD' | 'GEL'
)
}
>
<option value="GEL">GEL</option>
<option value="USD">USD</option>
</select>
</Field>
</div>
<section class="editor-panel">
<header class="editor-panel__header">
<strong>{props.copy.purchaseSplitTitle ?? ''}</strong>
<span>
{draft.splitMode === 'custom_amounts'
? props.copy.purchaseSplitCustom
: props.copy.purchaseSplitEqual}
</span>
</header>
<div class="editor-grid">
<Field label={props.copy.purchaseSplitModeLabel ?? ''} wide>
<select
value={draft.splitMode}
onChange={(event) =>
props.onPurchaseSplitModeChange(
entry.id,
entry,
event.currentTarget.value as 'equal' | 'custom_amounts'
)
}
>
<option value="equal">{props.copy.purchaseSplitEqual ?? ''}</option>
<option value="custom_amounts">
{props.copy.purchaseSplitCustom ?? ''}
</option>
</select>
</Field>
</div>
<div class="participant-list">
<For each={props.adminMembers}>
{(member) => {
const included = draft.participants.some(
(participant) => participant.memberId === member.id
)
const previewAmount =
splitPreview.find((participant) => participant.memberId === member.id)
?.amountMajor ?? '0.00'
return (
<article class="participant-card">
<header>
<strong>{member.displayName}</strong>
<span>
{previewAmount} {draft.currency}
</span>
</header>
<div class="participant-card__controls">
<Button
variant={included ? 'primary' : 'secondary'}
onClick={() =>
props.onTogglePurchaseParticipant(
entry.id,
entry,
member.id,
!included
)
}
>
{included
? props.copy.participantIncluded
: props.copy.participantExcluded}
</Button>
<Show when={included && draft.splitMode === 'custom_amounts'}>
<Field
label={props.copy.purchaseCustomShareLabel ?? ''}
class="participant-card__field"
>
<input
value={
draft.participants.find(
(participant) => participant.memberId === member.id
)?.shareAmountMajor ?? ''
}
onInput={(event) =>
props.onPurchaseParticipantShareChange(
entry.id,
entry,
member.id,
event.currentTarget.value
)
}
/>
</Field>
</Show>
</div>
</article>
)
}}
</For>
</div>
</section>
</>
)
})()}
</Modal>
<article class="balance-item">
<header>
<strong>{props.copy.utilityLedgerTitle ?? ''}</strong>
</header>
{props.utilityEntries.length === 0 ? (
<p>{props.copy.utilityLedgerEmpty ?? ''}</p>
) : (
<div class="ledger-list">
<For each={props.utilityEntries}>
{(entry) => (
<article class="ledger-item">
<header>
<strong>{props.ledgerTitle(entry)}</strong>
<span>{props.ledgerPrimaryAmount(entry)}</span>
</header>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => <p>{secondary()}</p>}
</Show>
<p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p>
</article>
)}
</For>
</div>
)}
</article>
<article class="balance-item">
<header>
<strong>{props.copy.paymentsAdminTitle ?? ''}</strong>
</header>
<Show when={props.readyIsAdmin}>
<p>{props.copy.paymentsAdminBody ?? ''}</p>
<div class="panel-toolbar">
<Button variant="secondary" onClick={props.onOpenAddPayment}>
{props.copy.paymentsAddAction ?? ''}
</Button>
</div>
</Show>
{props.paymentEntries.length === 0 ? (
<p>{props.copy.paymentsEmpty ?? ''}</p>
) : (
<div class="ledger-list">
<For each={props.paymentEntries}>
{(entry) => (
<article class="ledger-compact-card">
<div class="ledger-compact-card__main">
<header>
<strong>{props.paymentMemberName(entry)}</strong>
<span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span>
</header>
<p>{props.ledgerTitle(entry)}</p>
<div class="ledger-compact-card__meta">
<span class="mini-chip">{props.ledgerPrimaryAmount(entry)}</span>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => (
<span class="mini-chip mini-chip--muted">{secondary()}</span>
)}
</Show>
</div>
</div>
<Show when={props.readyIsAdmin}>
<div class="ledger-compact-card__actions">
<IconButton
label={props.copy.editEntryAction ?? ''}
onClick={() => props.onOpenPaymentEditor(entry.id)}
>
<PencilIcon />
</IconButton>
</div>
</Show>
</article>
)}
</For>
</div>
)}
</article>
<Modal
open={props.addingPaymentOpen}
title={props.copy.paymentsAddAction ?? ''}
description={props.copy.paymentCreateBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onCloseAddPayment}
footer={
<div class="modal-action-row modal-action-row--single">
<Button variant="ghost" onClick={props.onCloseAddPayment}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.addingPayment || props.paymentForm.amountMajor.trim().length === 0}
onClick={() => void props.onAddPayment()}
>
{props.addingPayment ? props.copy.addingPayment : props.copy.paymentsAddAction}
</Button>
</div>
} }
>
<div class="editor-grid">
<Field label={props.copy.paymentMember ?? ''} wide>
<select
value={props.paymentForm.memberId}
onChange={(event) => props.onPaymentFormMemberChange(event.currentTarget.value)}
>
<For each={props.adminMembers}>
{(member) => <option value={member.id}>{member.displayName}</option>}
</For>
</select>
</Field>
<Field label={props.copy.paymentKind ?? ''}>
<select
value={props.paymentForm.kind}
onChange={(event) =>
props.onPaymentFormKindChange(event.currentTarget.value as 'rent' | 'utilities')
}
>
<option value="rent">{props.copy.paymentLedgerRent ?? ''}</option>
<option value="utilities">{props.copy.paymentLedgerUtilities ?? ''}</option>
</select>
</Field>
<Field label={props.copy.paymentAmount ?? ''}>
<input
value={props.paymentForm.amountMajor}
onInput={(event) => props.onPaymentFormAmountChange(event.currentTarget.value)}
/>
</Field>
<Field label={props.copy.settlementCurrency ?? ''}>
<select
value={props.paymentForm.currency}
onChange={(event) =>
props.onPaymentFormCurrencyChange(event.currentTarget.value as 'USD' | 'GEL')
}
>
<option value="GEL">GEL</option>
<option value="USD">USD</option>
</select>
</Field>
</div>
</Modal>
<Modal
open={Boolean(props.editingPaymentEntry)}
title={props.copy.paymentsAdminTitle ?? ''}
description={props.copy.paymentEditorBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onClosePaymentEditor}
footer={(() => {
const entry = props.editingPaymentEntry
const draft = props.purchaseDraftMap[entry.id] ?? props.purchaseDraftForEntry(entry) if (!entry) {
const splitPreview = props.purchaseSplitPreview(entry.id) return null
}
return ( return (
<> <div class="modal-action-row">
<Button variant="danger" onClick={() => void props.onDeletePayment(entry.id)}>
{props.deletingPaymentId === entry.id
? props.copy.deletingPayment
: props.copy.paymentDeleteAction}
</Button>
<div class="modal-action-row__primary">
<Button variant="ghost" onClick={props.onClosePaymentEditor}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.savingPaymentId === entry.id}
onClick={() => void props.onSavePayment(entry.id)}
>
{props.savingPaymentId === entry.id
? props.copy.addingPayment
: props.copy.paymentSaveAction}
</Button>
</div>
</div>
)
})()}
>
{(() => {
const entry = props.editingPaymentEntry
if (!entry) {
return null
}
const draft = props.paymentDraftMap[entry.id] ?? props.paymentDraftForEntry(entry)
return (
<div class="editor-grid"> <div class="editor-grid">
<Field label={props.copy.purchaseReviewTitle ?? ''} wide> <Field label={props.copy.paymentMember ?? ''} wide>
<input <select
value={draft.description} value={draft.memberId}
onInput={(event) => onChange={(event) =>
props.onPurchaseDescriptionChange(entry.id, entry, event.currentTarget.value) props.onPaymentDraftMemberChange(entry.id, entry, event.currentTarget.value)
} }
/> >
<For each={props.adminMembers}>
{(member) => <option value={member.id}>{member.displayName}</option>}
</For>
</select>
</Field>
<Field label={props.copy.paymentKind ?? ''}>
<select
value={draft.kind}
onChange={(event) =>
props.onPaymentDraftKindChange(
entry.id,
entry,
event.currentTarget.value as 'rent' | 'utilities'
)
}
>
<option value="rent">{props.copy.paymentLedgerRent ?? ''}</option>
<option value="utilities">{props.copy.paymentLedgerUtilities ?? ''}</option>
</select>
</Field> </Field>
<Field label={props.copy.paymentAmount ?? ''}> <Field label={props.copy.paymentAmount ?? ''}>
<input <input
value={draft.amountMajor} value={draft.amountMajor}
onInput={(event) => onInput={(event) =>
props.onPurchaseAmountChange(entry.id, entry, event.currentTarget.value) props.onPaymentDraftAmountChange(entry.id, entry, event.currentTarget.value)
} }
/> />
</Field> </Field>
@@ -249,7 +586,7 @@ export function LedgerScreen(props: Props) {
<select <select
value={draft.currency} value={draft.currency}
onChange={(event) => onChange={(event) =>
props.onPurchaseCurrencyChange( props.onPaymentDraftCurrencyChange(
entry.id, entry.id,
entry, entry,
event.currentTarget.value as 'USD' | 'GEL' event.currentTarget.value as 'USD' | 'GEL'
@@ -261,340 +598,10 @@ export function LedgerScreen(props: Props) {
</select> </select>
</Field> </Field>
</div> </div>
)
<section class="editor-panel"> })()}
<header class="editor-panel__header"> </Modal>
<strong>{props.copy.purchaseSplitTitle ?? ''}</strong> </div>
<span> </Show>
{draft.splitMode === 'custom_amounts'
? props.copy.purchaseSplitCustom
: props.copy.purchaseSplitEqual}
</span>
</header>
<div class="editor-grid">
<Field label={props.copy.purchaseSplitModeLabel ?? ''} wide>
<select
value={draft.splitMode}
onChange={(event) =>
props.onPurchaseSplitModeChange(
entry.id,
entry,
event.currentTarget.value as 'equal' | 'custom_amounts'
)
}
>
<option value="equal">{props.copy.purchaseSplitEqual ?? ''}</option>
<option value="custom_amounts">{props.copy.purchaseSplitCustom ?? ''}</option>
</select>
</Field>
</div>
<div class="participant-list">
<For each={props.adminMembers}>
{(member) => {
const included = draft.participants.some(
(participant) => participant.memberId === member.id
)
const previewAmount =
splitPreview.find((participant) => participant.memberId === member.id)
?.amountMajor ?? '0.00'
return (
<article class="participant-card">
<header>
<strong>{member.displayName}</strong>
<span>
{previewAmount} {draft.currency}
</span>
</header>
<div class="participant-card__controls">
<Button
variant={included ? 'primary' : 'secondary'}
onClick={() =>
props.onTogglePurchaseParticipant(
entry.id,
entry,
member.id,
!included
)
}
>
{included
? props.copy.participantIncluded
: props.copy.participantExcluded}
</Button>
<Show when={included && draft.splitMode === 'custom_amounts'}>
<Field
label={props.copy.purchaseCustomShareLabel ?? ''}
class="participant-card__field"
>
<input
value={
draft.participants.find(
(participant) => participant.memberId === member.id
)?.shareAmountMajor ?? ''
}
onInput={(event) =>
props.onPurchaseParticipantShareChange(
entry.id,
entry,
member.id,
event.currentTarget.value
)
}
/>
</Field>
</Show>
</div>
</article>
)
}}
</For>
</div>
</section>
</>
)
})()}
</Modal>
<article class="balance-item">
<header>
<strong>{props.copy.utilityLedgerTitle ?? ''}</strong>
</header>
{props.utilityEntries.length === 0 ? (
<p>{props.copy.utilityLedgerEmpty ?? ''}</p>
) : (
<div class="ledger-list">
<For each={props.utilityEntries}>
{(entry) => (
<article class="ledger-item">
<header>
<strong>{props.ledgerTitle(entry)}</strong>
<span>{props.ledgerPrimaryAmount(entry)}</span>
</header>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => <p>{secondary()}</p>}
</Show>
<p>{entry.actorDisplayName ?? props.copy.ledgerActorFallback ?? ''}</p>
</article>
)}
</For>
</div>
)}
</article>
<article class="balance-item">
<header>
<strong>{props.copy.paymentsAdminTitle ?? ''}</strong>
</header>
<Show when={props.readyIsAdmin}>
<p>{props.copy.paymentsAdminBody ?? ''}</p>
<div class="panel-toolbar">
<Button variant="secondary" onClick={props.onOpenAddPayment}>
{props.copy.paymentsAddAction ?? ''}
</Button>
</div>
</Show>
{props.paymentEntries.length === 0 ? (
<p>{props.copy.paymentsEmpty ?? ''}</p>
) : (
<div class="ledger-list">
<For each={props.paymentEntries}>
{(entry) => (
<article class="ledger-compact-card">
<div class="ledger-compact-card__main">
<header>
<strong>{props.paymentMemberName(entry)}</strong>
<span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span>
</header>
<p>{props.ledgerTitle(entry)}</p>
<div class="ledger-compact-card__meta">
<span class="mini-chip">{props.ledgerPrimaryAmount(entry)}</span>
<Show when={props.ledgerSecondaryAmount(entry)}>
{(secondary) => (
<span class="mini-chip mini-chip--muted">{secondary()}</span>
)}
</Show>
</div>
</div>
<Show when={props.readyIsAdmin}>
<div class="ledger-compact-card__actions">
<IconButton
label={props.copy.editEntryAction ?? ''}
onClick={() => props.onOpenPaymentEditor(entry.id)}
>
<PencilIcon />
</IconButton>
</div>
</Show>
</article>
)}
</For>
</div>
)}
</article>
<Modal
open={props.addingPaymentOpen}
title={props.copy.paymentsAddAction ?? ''}
description={props.copy.paymentCreateBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onCloseAddPayment}
footer={
<div class="modal-action-row modal-action-row--single">
<Button variant="ghost" onClick={props.onCloseAddPayment}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.addingPayment || props.paymentForm.amountMajor.trim().length === 0}
onClick={() => void props.onAddPayment()}
>
{props.addingPayment ? props.copy.addingPayment : props.copy.paymentsAddAction}
</Button>
</div>
}
>
<div class="editor-grid">
<Field label={props.copy.paymentMember ?? ''} wide>
<select
value={props.paymentForm.memberId}
onChange={(event) => props.onPaymentFormMemberChange(event.currentTarget.value)}
>
<For each={props.adminMembers}>
{(member) => <option value={member.id}>{member.displayName}</option>}
</For>
</select>
</Field>
<Field label={props.copy.paymentKind ?? ''}>
<select
value={props.paymentForm.kind}
onChange={(event) =>
props.onPaymentFormKindChange(event.currentTarget.value as 'rent' | 'utilities')
}
>
<option value="rent">{props.copy.paymentLedgerRent ?? ''}</option>
<option value="utilities">{props.copy.paymentLedgerUtilities ?? ''}</option>
</select>
</Field>
<Field label={props.copy.paymentAmount ?? ''}>
<input
value={props.paymentForm.amountMajor}
onInput={(event) => props.onPaymentFormAmountChange(event.currentTarget.value)}
/>
</Field>
<Field label={props.copy.settlementCurrency ?? ''}>
<select
value={props.paymentForm.currency}
onChange={(event) =>
props.onPaymentFormCurrencyChange(event.currentTarget.value as 'USD' | 'GEL')
}
>
<option value="GEL">GEL</option>
<option value="USD">USD</option>
</select>
</Field>
</div>
</Modal>
<Modal
open={Boolean(props.editingPaymentEntry)}
title={props.copy.paymentsAdminTitle ?? ''}
description={props.copy.paymentEditorBody ?? ''}
closeLabel={props.copy.closeEditorAction ?? ''}
onClose={props.onClosePaymentEditor}
footer={(() => {
const entry = props.editingPaymentEntry
if (!entry) {
return null
}
return (
<div class="modal-action-row">
<Button variant="danger" onClick={() => void props.onDeletePayment(entry.id)}>
{props.deletingPaymentId === entry.id
? props.copy.deletingPayment
: props.copy.paymentDeleteAction}
</Button>
<div class="modal-action-row__primary">
<Button variant="ghost" onClick={props.onClosePaymentEditor}>
{props.copy.closeEditorAction ?? ''}
</Button>
<Button
variant="primary"
disabled={props.savingPaymentId === entry.id}
onClick={() => void props.onSavePayment(entry.id)}
>
{props.savingPaymentId === entry.id
? props.copy.addingPayment
: props.copy.paymentSaveAction}
</Button>
</div>
</div>
)
})()}
>
{(() => {
const entry = props.editingPaymentEntry
if (!entry) {
return null
}
const draft = props.paymentDraftMap[entry.id] ?? props.paymentDraftForEntry(entry)
return (
<div class="editor-grid">
<Field label={props.copy.paymentMember ?? ''} wide>
<select
value={draft.memberId}
onChange={(event) =>
props.onPaymentDraftMemberChange(entry.id, entry, event.currentTarget.value)
}
>
<For each={props.adminMembers}>
{(member) => <option value={member.id}>{member.displayName}</option>}
</For>
</select>
</Field>
<Field label={props.copy.paymentKind ?? ''}>
<select
value={draft.kind}
onChange={(event) =>
props.onPaymentDraftKindChange(
entry.id,
entry,
event.currentTarget.value as 'rent' | 'utilities'
)
}
>
<option value="rent">{props.copy.paymentLedgerRent ?? ''}</option>
<option value="utilities">{props.copy.paymentLedgerUtilities ?? ''}</option>
</select>
</Field>
<Field label={props.copy.paymentAmount ?? ''}>
<input
value={draft.amountMajor}
onInput={(event) =>
props.onPaymentDraftAmountChange(entry.id, entry, event.currentTarget.value)
}
/>
</Field>
<Field label={props.copy.settlementCurrency ?? ''}>
<select
value={draft.currency}
onChange={(event) =>
props.onPaymentDraftCurrencyChange(
entry.id,
entry,
event.currentTarget.value as 'USD' | 'GEL'
)
}
>
<option value="GEL">GEL</option>
<option value="USD">USD</option>
</select>
</Field>
</div>
)
})()}
</Modal>
</div>
) )
} }