mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 22:14:02 +00:00
refactor(miniapp): extract ledger screen
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Match, Show, Switch, createMemo, createSignal, onMount, type JSX } from 'solid-js'
|
||||
import { Match, Show, Switch, createMemo, createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { dictionary, type Locale } from './i18n'
|
||||
import {
|
||||
@@ -45,6 +45,7 @@ import { LoadingState } from './components/session/loading-state'
|
||||
import { OnboardingState } from './components/session/onboarding-state'
|
||||
import { BalancesScreen } from './screens/balances-screen'
|
||||
import { HomeScreen } from './screens/home-screen'
|
||||
import { LedgerScreen } from './screens/ledger-screen'
|
||||
import {
|
||||
demoAdminSettings,
|
||||
demoCycleState,
|
||||
@@ -1868,523 +1869,131 @@ function App() {
|
||||
)
|
||||
case 'ledger':
|
||||
return (
|
||||
<div class="ledger-list">
|
||||
<ShowDashboard
|
||||
dashboard={dashboard()}
|
||||
fallback={<p>{copy().emptyDashboard}</p>}
|
||||
render={() => (
|
||||
<>
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>
|
||||
{readySession()?.member.isAdmin
|
||||
? copy().purchaseReviewTitle
|
||||
: copy().purchasesTitle}
|
||||
</strong>
|
||||
</header>
|
||||
<Show when={readySession()?.member.isAdmin}>
|
||||
<p>{copy().purchaseReviewBody}</p>
|
||||
</Show>
|
||||
{purchaseLedger().length === 0 ? (
|
||||
<p>{copy().purchasesEmpty}</p>
|
||||
) : (
|
||||
<div class="ledger-list">
|
||||
{purchaseLedger().map((entry) => (
|
||||
<article class="ledger-compact-card">
|
||||
<div class="ledger-compact-card__main">
|
||||
<header>
|
||||
<strong>{entry.title}</strong>
|
||||
<span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span>
|
||||
</header>
|
||||
<p>{entry.actorDisplayName ?? copy().ledgerActorFallback}</p>
|
||||
<div class="ledger-compact-card__meta">
|
||||
<span class="mini-chip">{ledgerPrimaryAmount(entry)}</span>
|
||||
<Show when={ledgerSecondaryAmount(entry)}>
|
||||
{(secondary) => (
|
||||
<span class="mini-chip mini-chip--muted">{secondary()}</span>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={entry.kind === 'purchase'}>
|
||||
<span class="mini-chip mini-chip--muted">
|
||||
{purchaseParticipantSummary(entry)}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={readySession()?.member.isAdmin}>
|
||||
<div class="ledger-compact-card__actions">
|
||||
<IconButton
|
||||
label={copy().editEntryAction}
|
||||
onClick={() => setEditingPurchaseId(entry.id)}
|
||||
>
|
||||
...
|
||||
</IconButton>
|
||||
</div>
|
||||
</Show>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
<Modal
|
||||
open={Boolean(editingPurchaseEntry())}
|
||||
title={copy().purchaseReviewTitle}
|
||||
description={copy().purchaseEditorBody}
|
||||
closeLabel={copy().closeEditorAction}
|
||||
onClose={() => setEditingPurchaseId(null)}
|
||||
footer={(() => {
|
||||
const entry = editingPurchaseEntry()
|
||||
|
||||
if (!entry) {
|
||||
return null
|
||||
<LedgerScreen
|
||||
copy={copy()}
|
||||
dashboard={dashboard()}
|
||||
readyIsAdmin={readySession()?.member.isAdmin === true}
|
||||
adminMembers={adminSettings()?.members ?? []}
|
||||
purchaseEntries={purchaseLedger()}
|
||||
utilityEntries={utilityLedger()}
|
||||
paymentEntries={paymentLedger()}
|
||||
editingPurchaseEntry={editingPurchaseEntry()}
|
||||
editingPaymentEntry={editingPaymentEntry()}
|
||||
purchaseDraftMap={purchaseDraftMap()}
|
||||
paymentDraftMap={paymentDraftMap()}
|
||||
paymentForm={paymentForm()}
|
||||
addingPaymentOpen={addingPaymentOpen()}
|
||||
savingPurchaseId={savingPurchaseId()}
|
||||
deletingPurchaseId={deletingPurchaseId()}
|
||||
savingPaymentId={savingPaymentId()}
|
||||
deletingPaymentId={deletingPaymentId()}
|
||||
addingPayment={addingPayment()}
|
||||
ledgerTitle={ledgerTitle}
|
||||
ledgerPrimaryAmount={ledgerPrimaryAmount}
|
||||
ledgerSecondaryAmount={ledgerSecondaryAmount}
|
||||
purchaseParticipantSummary={purchaseParticipantSummary}
|
||||
purchaseDraftForEntry={purchaseDraftForEntry}
|
||||
paymentDraftForEntry={paymentDraftForEntry}
|
||||
purchaseSplitPreview={purchaseSplitPreview}
|
||||
paymentMemberName={paymentMemberName}
|
||||
onOpenPurchaseEditor={setEditingPurchaseId}
|
||||
onClosePurchaseEditor={() => setEditingPurchaseId(null)}
|
||||
onDeletePurchase={handleDeletePurchase}
|
||||
onSavePurchase={handleUpdatePurchase}
|
||||
onPurchaseDescriptionChange={(purchaseId, entry, value) =>
|
||||
updatePurchaseDraft(purchaseId, entry, (current) => ({
|
||||
...current,
|
||||
description: value
|
||||
}))
|
||||
}
|
||||
onPurchaseAmountChange={(purchaseId, entry, value) =>
|
||||
updatePurchaseDraft(purchaseId, entry, (current) => ({
|
||||
...current,
|
||||
amountMajor: value
|
||||
}))
|
||||
}
|
||||
onPurchaseCurrencyChange={(purchaseId, entry, value) =>
|
||||
updatePurchaseDraft(purchaseId, entry, (current) => ({
|
||||
...current,
|
||||
currency: value
|
||||
}))
|
||||
}
|
||||
onPurchaseSplitModeChange={(purchaseId, entry, value) =>
|
||||
updatePurchaseDraft(purchaseId, entry, (current) => ({
|
||||
...current,
|
||||
splitMode: value
|
||||
}))
|
||||
}
|
||||
onTogglePurchaseParticipant={togglePurchaseParticipant}
|
||||
onPurchaseParticipantShareChange={(purchaseId, entry, memberId, value) =>
|
||||
updatePurchaseDraft(purchaseId, entry, (current) => ({
|
||||
...current,
|
||||
participants: current.participants.map((participant) =>
|
||||
participant.memberId === memberId
|
||||
? {
|
||||
...participant,
|
||||
shareAmountMajor: value
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="modal-action-row">
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => void handleDeletePurchase(entry.id)}
|
||||
>
|
||||
{deletingPurchaseId() === entry.id
|
||||
? copy().deletingPurchase
|
||||
: copy().purchaseDeleteAction}
|
||||
</Button>
|
||||
<div class="modal-action-row__primary">
|
||||
<Button variant="ghost" onClick={() => setEditingPurchaseId(null)}>
|
||||
{copy().closeEditorAction}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={savingPurchaseId() === entry.id}
|
||||
onClick={() => void handleUpdatePurchase(entry.id)}
|
||||
>
|
||||
{savingPurchaseId() === entry.id
|
||||
? copy().savingPurchase
|
||||
: copy().purchaseSaveAction}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
>
|
||||
{(() => {
|
||||
const entry = editingPurchaseEntry()
|
||||
|
||||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
|
||||
const draft = purchaseDraftMap()[entry.id] ?? purchaseDraftForEntry(entry)
|
||||
const splitPreview = purchaseSplitPreview(entry.id)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="editor-grid">
|
||||
<Field label={copy().purchaseReviewTitle} wide>
|
||||
<input
|
||||
value={draft.description}
|
||||
onInput={(event) =>
|
||||
updatePurchaseDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
description: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={copy().paymentAmount}>
|
||||
<input
|
||||
value={draft.amountMajor}
|
||||
onInput={(event) =>
|
||||
updatePurchaseDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
amountMajor: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={copy().settlementCurrency}>
|
||||
<select
|
||||
value={draft.currency}
|
||||
onChange={(event) =>
|
||||
updatePurchaseDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
currency: 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>{copy().purchaseSplitTitle}</strong>
|
||||
<span>
|
||||
{draft.splitMode === 'custom_amounts'
|
||||
? copy().purchaseSplitCustom
|
||||
: copy().purchaseSplitEqual}
|
||||
</span>
|
||||
</header>
|
||||
<div class="editor-grid">
|
||||
<Field label={copy().purchaseSplitModeLabel} wide>
|
||||
<select
|
||||
value={draft.splitMode}
|
||||
onChange={(event) =>
|
||||
updatePurchaseDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
splitMode: event.currentTarget.value as
|
||||
| 'equal'
|
||||
| 'custom_amounts'
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="equal">{copy().purchaseSplitEqual}</option>
|
||||
<option value="custom_amounts">
|
||||
{copy().purchaseSplitCustom}
|
||||
</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
<div class="participant-list">
|
||||
{(adminSettings()?.members ?? []).map((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={() =>
|
||||
togglePurchaseParticipant(
|
||||
entry.id,
|
||||
entry,
|
||||
member.id,
|
||||
!included
|
||||
)
|
||||
}
|
||||
>
|
||||
{included
|
||||
? copy().participantIncluded
|
||||
: copy().participantExcluded}
|
||||
</Button>
|
||||
<Show when={included && draft.splitMode === 'custom_amounts'}>
|
||||
<Field
|
||||
label={copy().purchaseCustomShareLabel}
|
||||
class="participant-card__field"
|
||||
>
|
||||
<input
|
||||
value={
|
||||
draft.participants.find(
|
||||
(participant) => participant.memberId === member.id
|
||||
)?.shareAmountMajor ?? ''
|
||||
}
|
||||
onInput={(event) =>
|
||||
updatePurchaseDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
participants: current.participants.map(
|
||||
(participant) =>
|
||||
participant.memberId === member.id
|
||||
? {
|
||||
...participant,
|
||||
shareAmountMajor:
|
||||
event.currentTarget.value
|
||||
}
|
||||
: participant
|
||||
)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</Show>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</Modal>
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{copy().utilityLedgerTitle}</strong>
|
||||
</header>
|
||||
{utilityLedger().length === 0 ? (
|
||||
<p>{copy().utilityLedgerEmpty}</p>
|
||||
) : (
|
||||
<div class="ledger-list">
|
||||
{utilityLedger().map((entry) => (
|
||||
<article class="ledger-item">
|
||||
<header>
|
||||
<strong>{ledgerTitle(entry)}</strong>
|
||||
<span>{ledgerPrimaryAmount(entry)}</span>
|
||||
</header>
|
||||
<Show when={ledgerSecondaryAmount(entry)}>
|
||||
{(secondary) => <p>{secondary()}</p>}
|
||||
</Show>
|
||||
<p>{entry.actorDisplayName ?? copy().ledgerActorFallback}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{copy().paymentsAdminTitle}</strong>
|
||||
</header>
|
||||
<Show when={readySession()?.member.isAdmin}>
|
||||
<p>{copy().paymentsAdminBody}</p>
|
||||
<div class="panel-toolbar">
|
||||
<Button variant="secondary" onClick={() => setAddingPaymentOpen(true)}>
|
||||
{copy().paymentsAddAction}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
{paymentLedger().length === 0 ? (
|
||||
<p>{copy().paymentsEmpty}</p>
|
||||
) : (
|
||||
<div class="ledger-list">
|
||||
{paymentLedger().map((entry) => (
|
||||
<article class="ledger-compact-card">
|
||||
<div class="ledger-compact-card__main">
|
||||
<header>
|
||||
<strong>{paymentMemberName(entry)}</strong>
|
||||
<span>{entry.occurredAt?.slice(0, 10) ?? '—'}</span>
|
||||
</header>
|
||||
<p>{ledgerTitle(entry)}</p>
|
||||
<div class="ledger-compact-card__meta">
|
||||
<span class="mini-chip">{ledgerPrimaryAmount(entry)}</span>
|
||||
<Show when={ledgerSecondaryAmount(entry)}>
|
||||
{(secondary) => (
|
||||
<span class="mini-chip mini-chip--muted">{secondary()}</span>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={readySession()?.member.isAdmin}>
|
||||
<div class="ledger-compact-card__actions">
|
||||
<IconButton
|
||||
label={copy().editEntryAction}
|
||||
onClick={() => setEditingPaymentId(entry.id)}
|
||||
>
|
||||
...
|
||||
</IconButton>
|
||||
</div>
|
||||
</Show>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
<Modal
|
||||
open={addingPaymentOpen()}
|
||||
title={copy().paymentsAddAction}
|
||||
description={copy().paymentCreateBody}
|
||||
closeLabel={copy().closeEditorAction}
|
||||
onClose={() => setAddingPaymentOpen(false)}
|
||||
footer={
|
||||
<div class="modal-action-row modal-action-row--single">
|
||||
<Button variant="ghost" onClick={() => setAddingPaymentOpen(false)}>
|
||||
{copy().closeEditorAction}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={
|
||||
addingPayment() || paymentForm().amountMajor.trim().length === 0
|
||||
}
|
||||
onClick={() => void handleAddPayment()}
|
||||
>
|
||||
{addingPayment() ? copy().addingPayment : copy().paymentsAddAction}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="editor-grid">
|
||||
<Field label={copy().paymentMember} wide>
|
||||
<select
|
||||
value={paymentForm().memberId}
|
||||
onChange={(event) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
memberId: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
>
|
||||
{adminSettings()?.members.map((member) => (
|
||||
<option value={member.id}>{member.displayName}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label={copy().paymentKind}>
|
||||
<select
|
||||
value={paymentForm().kind}
|
||||
onChange={(event) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
kind: event.currentTarget.value as 'rent' | 'utilities'
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="rent">{copy().paymentLedgerRent}</option>
|
||||
<option value="utilities">{copy().paymentLedgerUtilities}</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label={copy().paymentAmount}>
|
||||
<input
|
||||
value={paymentForm().amountMajor}
|
||||
onInput={(event) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
amountMajor: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={copy().settlementCurrency}>
|
||||
<select
|
||||
value={paymentForm().currency}
|
||||
onChange={(event) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
currency: event.currentTarget.value as 'USD' | 'GEL'
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="GEL">GEL</option>
|
||||
<option value="USD">USD</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={Boolean(editingPaymentEntry())}
|
||||
title={copy().paymentsAdminTitle}
|
||||
description={copy().paymentEditorBody}
|
||||
closeLabel={copy().closeEditorAction}
|
||||
onClose={() => setEditingPaymentId(null)}
|
||||
footer={(() => {
|
||||
const entry = editingPaymentEntry()
|
||||
|
||||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="modal-action-row">
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => void handleDeletePayment(entry.id)}
|
||||
>
|
||||
{deletingPaymentId() === entry.id
|
||||
? copy().deletingPayment
|
||||
: copy().paymentDeleteAction}
|
||||
</Button>
|
||||
<div class="modal-action-row__primary">
|
||||
<Button variant="ghost" onClick={() => setEditingPaymentId(null)}>
|
||||
{copy().closeEditorAction}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={savingPaymentId() === entry.id}
|
||||
onClick={() => void handleUpdatePayment(entry.id)}
|
||||
>
|
||||
{savingPaymentId() === entry.id
|
||||
? copy().addingPayment
|
||||
: copy().paymentSaveAction}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
>
|
||||
{(() => {
|
||||
const entry = editingPaymentEntry()
|
||||
|
||||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
|
||||
const draft = paymentDraftMap()[entry.id] ?? paymentDraftForEntry(entry)
|
||||
|
||||
return (
|
||||
<div class="editor-grid">
|
||||
<Field label={copy().paymentMember} wide>
|
||||
<select
|
||||
value={draft.memberId}
|
||||
onChange={(event) =>
|
||||
updatePaymentDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
memberId: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
>
|
||||
{adminSettings()?.members.map((member) => (
|
||||
<option value={member.id}>{member.displayName}</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label={copy().paymentKind}>
|
||||
<select
|
||||
value={draft.kind}
|
||||
onChange={(event) =>
|
||||
updatePaymentDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
kind: event.currentTarget.value as 'rent' | 'utilities'
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="rent">{copy().paymentLedgerRent}</option>
|
||||
<option value="utilities">{copy().paymentLedgerUtilities}</option>
|
||||
</select>
|
||||
</Field>
|
||||
<Field label={copy().paymentAmount}>
|
||||
<input
|
||||
value={draft.amountMajor}
|
||||
onInput={(event) =>
|
||||
updatePaymentDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
amountMajor: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={copy().settlementCurrency}>
|
||||
<select
|
||||
value={draft.currency}
|
||||
onChange={(event) =>
|
||||
updatePaymentDraft(entry.id, entry, (current) => ({
|
||||
...current,
|
||||
currency: event.currentTarget.value as 'USD' | 'GEL'
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="GEL">GEL</option>
|
||||
<option value="USD">USD</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
: participant
|
||||
)
|
||||
}))
|
||||
}
|
||||
onOpenAddPayment={() => setAddingPaymentOpen(true)}
|
||||
onCloseAddPayment={() => setAddingPaymentOpen(false)}
|
||||
onAddPayment={handleAddPayment}
|
||||
onPaymentFormMemberChange={(value) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
memberId: value
|
||||
}))
|
||||
}
|
||||
onPaymentFormKindChange={(value) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
kind: value
|
||||
}))
|
||||
}
|
||||
onPaymentFormAmountChange={(value) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
amountMajor: value
|
||||
}))
|
||||
}
|
||||
onPaymentFormCurrencyChange={(value) =>
|
||||
setPaymentForm((current) => ({
|
||||
...current,
|
||||
currency: value
|
||||
}))
|
||||
}
|
||||
onOpenPaymentEditor={setEditingPaymentId}
|
||||
onClosePaymentEditor={() => setEditingPaymentId(null)}
|
||||
onDeletePayment={handleDeletePayment}
|
||||
onSavePayment={handleUpdatePayment}
|
||||
onPaymentDraftMemberChange={(paymentId, entry, value) =>
|
||||
updatePaymentDraft(paymentId, entry, (current) => ({
|
||||
...current,
|
||||
memberId: value
|
||||
}))
|
||||
}
|
||||
onPaymentDraftKindChange={(paymentId, entry, value) =>
|
||||
updatePaymentDraft(paymentId, entry, (current) => ({
|
||||
...current,
|
||||
kind: value
|
||||
}))
|
||||
}
|
||||
onPaymentDraftAmountChange={(paymentId, entry, value) =>
|
||||
updatePaymentDraft(paymentId, entry, (current) => ({
|
||||
...current,
|
||||
amountMajor: value
|
||||
}))
|
||||
}
|
||||
onPaymentDraftCurrencyChange={(paymentId, entry, value) =>
|
||||
updatePaymentDraft(paymentId, entry, (current) => ({
|
||||
...current,
|
||||
currency: value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
)
|
||||
case 'house':
|
||||
return readySession()?.member.isAdmin ? (
|
||||
@@ -3663,12 +3272,4 @@ function App() {
|
||||
)
|
||||
}
|
||||
|
||||
function ShowDashboard(props: {
|
||||
dashboard: MiniAppDashboard | null
|
||||
fallback: JSX.Element
|
||||
render: (dashboard: MiniAppDashboard) => JSX.Element
|
||||
}) {
|
||||
return <>{props.dashboard ? props.render(props.dashboard) : props.fallback}</>
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
Reference in New Issue
Block a user