mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 15:54:03 +00:00
refactor(miniapp): simplify dashboard layout and controls
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Match, Switch, createMemo, createSignal, onMount } from 'solid-js'
|
import { Match, Show, Switch, createMemo, createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { dictionary, type Locale } from './i18n'
|
import { dictionary, type Locale } from './i18n'
|
||||||
import {
|
import {
|
||||||
@@ -38,10 +38,8 @@ import {
|
|||||||
type MiniAppDashboard,
|
type MiniAppDashboard,
|
||||||
type MiniAppPendingMember
|
type MiniAppPendingMember
|
||||||
} from './miniapp-api'
|
} from './miniapp-api'
|
||||||
import { Button, Field, Modal } from './components/ui'
|
import { Button, Field, MiniChip, Modal } from './components/ui'
|
||||||
import { HeroBanner } from './components/layout/hero-banner'
|
|
||||||
import { NavigationTabs } from './components/layout/navigation-tabs'
|
import { NavigationTabs } from './components/layout/navigation-tabs'
|
||||||
import { ProfileCard } from './components/layout/profile-card'
|
|
||||||
import { TopBar } from './components/layout/top-bar'
|
import { TopBar } from './components/layout/top-bar'
|
||||||
import { BlockedState } from './components/session/blocked-state'
|
import { BlockedState } from './components/session/blocked-state'
|
||||||
import { LoadingState } from './components/session/loading-state'
|
import { LoadingState } from './components/session/loading-state'
|
||||||
@@ -2337,10 +2335,7 @@ function App() {
|
|||||||
currentMemberLine={currentMemberLine()}
|
currentMemberLine={currentMemberLine()}
|
||||||
utilityTotalMajor={utilityTotalMajor()}
|
utilityTotalMajor={utilityTotalMajor()}
|
||||||
purchaseTotalMajor={purchaseTotalMajor()}
|
purchaseTotalMajor={purchaseTotalMajor()}
|
||||||
memberBalanceVisuals={memberBalanceVisuals()}
|
|
||||||
purchaseChart={purchaseInvestmentChart()}
|
|
||||||
memberBaseDueMajor={memberBaseDueMajor}
|
memberBaseDueMajor={memberBaseDueMajor}
|
||||||
memberRemainingClass={memberRemainingClass}
|
|
||||||
ledgerTitle={ledgerTitle}
|
ledgerTitle={ledgerTitle}
|
||||||
ledgerPrimaryAmount={ledgerPrimaryAmount}
|
ledgerPrimaryAmount={ledgerPrimaryAmount}
|
||||||
ledgerSecondaryAmount={ledgerSecondaryAmount}
|
ledgerSecondaryAmount={ledgerSecondaryAmount}
|
||||||
@@ -2426,25 +2421,30 @@ function App() {
|
|||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
<Match when={session().status === 'ready'}>
|
<Match when={session().status === 'ready'}>
|
||||||
<HeroBanner
|
<section class="app-context-row">
|
||||||
badges={[
|
<div class="app-context-meta">
|
||||||
readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge,
|
<MiniChip>
|
||||||
readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag,
|
{readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge}
|
||||||
readySession()?.member.status
|
</MiniChip>
|
||||||
|
<MiniChip muted>
|
||||||
|
{readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}
|
||||||
|
</MiniChip>
|
||||||
|
<MiniChip muted>
|
||||||
|
{readySession()?.member.status
|
||||||
? memberStatusLabel(readySession()!.member.status)
|
? memberStatusLabel(readySession()!.member.status)
|
||||||
: copy().memberStatusActive
|
: copy().memberStatusActive}
|
||||||
]}
|
</MiniChip>
|
||||||
title={`${copy().welcome}, ${readySession()?.telegramUser.firstName ?? readySession()?.member.displayName}`}
|
</div>
|
||||||
body={copy().overviewBody}
|
<Show when={readySession()?.mode === 'live'}>
|
||||||
action={
|
<Button
|
||||||
readySession()?.mode === 'live'
|
variant="ghost"
|
||||||
? {
|
class="app-context-row__action"
|
||||||
label: copy().manageProfileAction,
|
onClick={() => setProfileEditorOpen(true)}
|
||||||
onClick: () => setProfileEditorOpen(true)
|
>
|
||||||
}
|
{copy().manageProfileAction}
|
||||||
: undefined
|
</Button>
|
||||||
}
|
</Show>
|
||||||
/>
|
</section>
|
||||||
|
|
||||||
<NavigationTabs
|
<NavigationTabs
|
||||||
items={
|
items={
|
||||||
@@ -2459,21 +2459,7 @@ function App() {
|
|||||||
onChange={setActiveNav}
|
onChange={setActiveNav}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="content-grid">
|
<section class="content-stack">{renderPanel()}</section>
|
||||||
<ProfileCard
|
|
||||||
displayName={readySession()?.member.displayName ?? ''}
|
|
||||||
roleLabel={readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}
|
|
||||||
statusSummary={copy().memberStatusSummary.replace(
|
|
||||||
'{status}',
|
|
||||||
readySession()?.member.status
|
|
||||||
? memberStatusLabel(readySession()!.member.status)
|
|
||||||
: copy().memberStatusActive
|
|
||||||
)}
|
|
||||||
modeBadge={readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge}
|
|
||||||
localeBadge={locale().toUpperCase()}
|
|
||||||
/>
|
|
||||||
<div class="content-stack">{renderPanel()}</div>
|
|
||||||
</section>
|
|
||||||
<Modal
|
<Modal
|
||||||
open={profileEditorOpen()}
|
open={profileEditorOpen()}
|
||||||
title={copy().displayNameLabel}
|
title={copy().displayNameLabel}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type Props = {
|
|||||||
export function TopBar(props: Props) {
|
export function TopBar(props: Props) {
|
||||||
return (
|
return (
|
||||||
<section class="topbar">
|
<section class="topbar">
|
||||||
<div>
|
<div class="topbar__copy">
|
||||||
<p class="eyebrow">{props.subtitle}</p>
|
<p class="eyebrow">{props.subtitle}</p>
|
||||||
<h1>{props.title}</h1>
|
<h1>{props.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as Dialog from '@kobalte/core/dialog'
|
import * as Dialog from '@kobalte/core/dialog'
|
||||||
import { Show, type JSX, type ParentProps } from 'solid-js'
|
import { Show, type JSX, type ParentProps } from 'solid-js'
|
||||||
|
|
||||||
|
import { XIcon } from './icons'
|
||||||
|
|
||||||
export function Modal(
|
export function Modal(
|
||||||
props: ParentProps<{
|
props: ParentProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -24,8 +26,8 @@ export function Modal(
|
|||||||
{(description) => <Dialog.Description>{description()}</Dialog.Description>}
|
{(description) => <Dialog.Description>{description()}</Dialog.Description>}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Dialog.CloseButton class="ui-button ui-button--icon">
|
<Dialog.CloseButton class="ui-button ui-button--icon modal-close-button">
|
||||||
<span aria-hidden="true">x</span>
|
<XIcon />
|
||||||
<span class="sr-only">{props.closeLabel}</span>
|
<span class="sr-only">{props.closeLabel}</span>
|
||||||
</Dialog.CloseButton>
|
</Dialog.CloseButton>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -65,3 +65,12 @@ export function GlobeIcon(props: IconProps) {
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function XIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<path d="m6 6 12 12" />
|
||||||
|
<path d="M18 6 6 18" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,7 +82,12 @@ button {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar__copy {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar h1,
|
.topbar h1,
|
||||||
@@ -94,7 +99,8 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar h1 {
|
.topbar h1 {
|
||||||
font-size: clamp(2rem, 5vw, 3rem);
|
font-size: clamp(1.55rem, 4.4vw, 2.35rem);
|
||||||
|
line-height: 0.96;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow {
|
.eyebrow {
|
||||||
@@ -117,14 +123,15 @@ button {
|
|||||||
.locale-switch--compact {
|
.locale-switch--compact {
|
||||||
justify-items: end;
|
justify-items: end;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch__buttons {
|
.locale-switch__buttons {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
padding: 4px;
|
padding: 3px;
|
||||||
border: 1px solid rgb(255 255 255 / 0.1);
|
border: 1px solid rgb(255 255 255 / 0.1);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgb(255 255 255 / 0.04);
|
background: rgb(255 255 255 / 0.04);
|
||||||
@@ -135,8 +142,8 @@ button {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 30px;
|
width: 24px;
|
||||||
height: 30px;
|
height: 24px;
|
||||||
color: rgb(247 179 137 / 0.88);
|
color: rgb(247 179 137 / 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +161,17 @@ button {
|
|||||||
|
|
||||||
.locale-switch__buttons button {
|
.locale-switch__buttons button {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
min-width: 42px;
|
min-width: 36px;
|
||||||
padding: 7px 10px;
|
padding: 6px 8px;
|
||||||
font-size: 0.78rem;
|
font-size: 0.72rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.locale-switch__buttons--inline {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.locale-switch__buttons button.is-active,
|
.locale-switch__buttons button.is-active,
|
||||||
.nav-grid button.is-active {
|
.nav-grid button.is-active {
|
||||||
border-color: rgb(247 179 137 / 0.7);
|
border-color: rgb(247 179 137 / 0.7);
|
||||||
@@ -263,15 +274,22 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui-button--primary {
|
.ui-button--primary {
|
||||||
border-color: rgb(247 179 137 / 0.42);
|
border-color: rgb(247 179 137 / 0.52);
|
||||||
background: rgb(247 179 137 / 0.16);
|
background: rgb(247 179 137 / 0.18);
|
||||||
color: #fff4ea;
|
color: #fff4ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-button--secondary,
|
.ui-button--secondary {
|
||||||
|
background: rgb(255 255 255 / 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
.ui-button--ghost,
|
.ui-button--ghost,
|
||||||
.ui-button--icon {
|
.ui-button--icon {
|
||||||
background: rgb(255 255 255 / 0.04);
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-button--ghost {
|
||||||
|
border-color: rgb(255 255 255 / 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-button--danger {
|
.ui-button--danger {
|
||||||
@@ -296,13 +314,19 @@ button {
|
|||||||
.nav-grid {
|
.nav-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
margin-top: 18px;
|
margin-top: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.08);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: rgb(255 255 255 / 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-grid button {
|
.nav-grid button {
|
||||||
border-radius: 18px;
|
border: none;
|
||||||
padding: 14px 8px;
|
border-radius: 14px;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 9px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-grid {
|
.content-grid {
|
||||||
@@ -314,12 +338,13 @@ button {
|
|||||||
.content-stack {
|
.content-stack {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-card-grid {
|
.summary-card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
@@ -342,9 +367,11 @@ button {
|
|||||||
.stat-card,
|
.stat-card,
|
||||||
.activity-row,
|
.activity-row,
|
||||||
.utility-bill-row {
|
.utility-bill-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
border: 1px solid rgb(255 255 255 / 0.08);
|
border: 1px solid rgb(255 255 255 / 0.08);
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
padding: 14px;
|
padding: 16px;
|
||||||
background: rgb(255 255 255 / 0.03);
|
background: rgb(255 255 255 / 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +394,7 @@ button {
|
|||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-item strong,
|
.balance-item strong,
|
||||||
@@ -382,7 +409,7 @@ button {
|
|||||||
.ledger-item p,
|
.ledger-item p,
|
||||||
.activity-row p,
|
.activity-row p,
|
||||||
.utility-bill-row p {
|
.utility-bill-row p {
|
||||||
margin-top: 6px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-status.is-credit {
|
.balance-status.is-credit {
|
||||||
@@ -409,7 +436,6 @@ button {
|
|||||||
.balance-breakdown {
|
.balance-breakdown {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.member-visual-list {
|
.member-visual-list {
|
||||||
@@ -596,32 +622,8 @@ button {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-switch {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-switch button {
|
|
||||||
border: 1px solid rgb(255 255 255 / 0.12);
|
|
||||||
border-radius: 16px;
|
|
||||||
min-height: 44px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
background: rgb(255 255 255 / 0.04);
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-switch button.is-active {
|
|
||||||
border-color: rgb(247 179 137 / 0.7);
|
|
||||||
background: rgb(247 179 137 / 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-layout {
|
.admin-layout {
|
||||||
gap: 18px;
|
gap: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
.admin-hero {
|
|
||||||
gap: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-summary-grid,
|
.admin-summary-grid,
|
||||||
@@ -636,24 +638,6 @@ button {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-section__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-section__header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif;
|
|
||||||
letter-spacing: -0.04em;
|
|
||||||
font-size: 1.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-section__header p {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-sublist {
|
.admin-sublist {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
@@ -728,10 +712,9 @@ button {
|
|||||||
.panel-toolbar {
|
.panel-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 14px;
|
margin-top: 8px;
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ledger-compact-card {
|
.ledger-compact-card {
|
||||||
@@ -806,7 +789,7 @@ button {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid rgb(255 255 255 / 0.1);
|
border: 1px solid rgb(255 255 255 / 0.1);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
padding: 18px;
|
padding: 16px;
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgb(255 255 255 / 0.07), rgb(255 255 255 / 0.03)), rgb(18 26 36 / 0.96);
|
linear-gradient(180deg, rgb(255 255 255 / 0.07), rgb(255 255 255 / 0.03)), rgb(18 26 36 / 0.96);
|
||||||
box-shadow: 0 28px 80px rgb(0 0 0 / 0.35);
|
box-shadow: 0 28px 80px rgb(0 0 0 / 0.35);
|
||||||
@@ -815,7 +798,7 @@ button {
|
|||||||
.modal-sheet__header {
|
.modal-sheet__header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,17 +810,25 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-sheet__header p {
|
.modal-sheet__header p {
|
||||||
margin-top: 8px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-sheet__body {
|
.modal-sheet__body {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 16px;
|
gap: 14px;
|
||||||
margin-top: 18px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-sheet__footer {
|
.modal-sheet__footer {
|
||||||
margin-top: 18px;
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-button {
|
||||||
|
align-self: start;
|
||||||
|
width: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
border-color: rgb(255 255 255 / 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-grid {
|
.editor-grid {
|
||||||
@@ -952,6 +943,29 @@ button {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.balance-item > p + .ledger-list,
|
||||||
|
.balance-item > p + .balance-list {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-context-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-context-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-context-row__action {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 760px) {
|
@media (min-width: 760px) {
|
||||||
.shell {
|
.shell {
|
||||||
max-width: 920px;
|
max-width: 920px;
|
||||||
@@ -976,10 +990,6 @@ button {
|
|||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-summary-grid {
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-grid {
|
.admin-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -999,10 +1009,6 @@ button {
|
|||||||
.balance-item--wide {
|
.balance-item--wide {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-switch {
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 980px) {
|
@media (min-width: 980px) {
|
||||||
@@ -1025,21 +1031,17 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch {
|
.locale-switch {
|
||||||
justify-items: stretch;
|
width: auto;
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch--compact {
|
.locale-switch--compact {
|
||||||
justify-items: start;
|
justify-items: end;
|
||||||
}
|
|
||||||
|
|
||||||
.locale-switch__buttons {
|
|
||||||
justify-self: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-grid {
|
.nav-grid {
|
||||||
@@ -1054,10 +1056,6 @@ button {
|
|||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-section__header {
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity-row header,
|
.activity-row header,
|
||||||
.ledger-compact-card header,
|
.ledger-compact-card header,
|
||||||
.ledger-item header,
|
.ledger-item header,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { FinanceSummaryCards } from '../components/finance/finance-summary-cards'
|
import { FinanceSummaryCards } from '../components/finance/finance-summary-cards'
|
||||||
import { FinanceVisuals } from '../components/finance/finance-visuals'
|
|
||||||
import type { MiniAppDashboard } from '../miniapp-api'
|
import type { MiniAppDashboard } from '../miniapp-api'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -12,32 +11,7 @@ type Props = {
|
|||||||
currentMemberLine: MiniAppDashboard['members'][number] | null
|
currentMemberLine: MiniAppDashboard['members'][number] | null
|
||||||
utilityTotalMajor: string
|
utilityTotalMajor: string
|
||||||
purchaseTotalMajor: string
|
purchaseTotalMajor: string
|
||||||
memberBalanceVisuals: {
|
|
||||||
member: MiniAppDashboard['members'][number]
|
|
||||||
totalMinor: bigint
|
|
||||||
barWidthPercent: number
|
|
||||||
segments: {
|
|
||||||
key: string
|
|
||||||
label: string
|
|
||||||
amountMajor: string
|
|
||||||
amountMinor: bigint
|
|
||||||
widthPercent: number
|
|
||||||
}[]
|
|
||||||
}[]
|
|
||||||
purchaseChart: {
|
|
||||||
totalMajor: string
|
|
||||||
slices: {
|
|
||||||
key: string
|
|
||||||
label: string
|
|
||||||
amountMajor: string
|
|
||||||
color: string
|
|
||||||
percentage: number
|
|
||||||
dasharray: string
|
|
||||||
dashoffset: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
memberBaseDueMajor: (member: MiniAppDashboard['members'][number]) => string
|
memberBaseDueMajor: (member: MiniAppDashboard['members'][number]) => string
|
||||||
memberRemainingClass: (member: MiniAppDashboard['members'][number]) => string
|
|
||||||
ledgerTitle: (entry: MiniAppDashboard['ledger'][number]) => string
|
ledgerTitle: (entry: MiniAppDashboard['ledger'][number]) => string
|
||||||
ledgerPrimaryAmount: (entry: MiniAppDashboard['ledger'][number]) => string
|
ledgerPrimaryAmount: (entry: MiniAppDashboard['ledger'][number]) => string
|
||||||
ledgerSecondaryAmount: (entry: MiniAppDashboard['ledger'][number]) => string | null
|
ledgerSecondaryAmount: (entry: MiniAppDashboard['ledger'][number]) => string | null
|
||||||
@@ -91,17 +65,7 @@ export function HomeScreen(props: Props) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show
|
<Show when={props.currentMemberLine}>
|
||||||
when={props.currentMemberLine}
|
|
||||||
fallback={
|
|
||||||
<article class="balance-item">
|
|
||||||
<header>
|
|
||||||
<strong>{props.copy.overviewTitle ?? ''}</strong>
|
|
||||||
</header>
|
|
||||||
<p>{props.copy.overviewBody ?? ''}</p>
|
|
||||||
</article>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(member) => (
|
{(member) => (
|
||||||
<article class="balance-item balance-item--accent">
|
<article class="balance-item balance-item--accent">
|
||||||
<header>
|
<header>
|
||||||
@@ -110,7 +74,6 @@ export function HomeScreen(props: Props) {
|
|||||||
{member().remainingMajor} {props.dashboard!.currency}
|
{member().remainingMajor} {props.dashboard!.currency}
|
||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{props.copy.yourBalanceBody ?? ''}</p>
|
|
||||||
<p>
|
<p>
|
||||||
{props.copy.shareRent ?? ''}: {props.dashboard!.rentSourceAmountMajor}{' '}
|
{props.copy.shareRent ?? ''}: {props.dashboard!.rentSourceAmountMajor}{' '}
|
||||||
{props.dashboard!.rentSourceCurrency}
|
{props.dashboard!.rentSourceCurrency}
|
||||||
@@ -154,23 +117,6 @@ export function HomeScreen(props: Props) {
|
|||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<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 balance-item--wide">
|
<article class="balance-item balance-item--wide">
|
||||||
<header>
|
<header>
|
||||||
<strong>{props.copy.latestActivityTitle ?? ''}</strong>
|
<strong>{props.copy.latestActivityTitle ?? ''}</strong>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
SettingsIcon
|
SettingsIcon
|
||||||
} from '../components/ui'
|
} from '../components/ui'
|
||||||
|
import { NavigationTabs } from '../components/layout/navigation-tabs'
|
||||||
import type {
|
import type {
|
||||||
MiniAppAdminCycleState,
|
MiniAppAdminCycleState,
|
||||||
MiniAppAdminSettingsPayload,
|
MiniAppAdminSettingsPayload,
|
||||||
@@ -175,63 +176,21 @@ export function HouseScreen(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="admin-layout">
|
<div class="admin-layout">
|
||||||
<article class="balance-item balance-item--accent admin-hero">
|
<NavigationTabs
|
||||||
<header>
|
items={
|
||||||
<strong>{props.copy.householdSettingsTitle ?? ''}</strong>
|
|
||||||
<span>{props.adminSettings?.settings.settlementCurrency ?? '—'}</span>
|
|
||||||
</header>
|
|
||||||
<p>{props.copy.householdSettingsBody ?? ''}</p>
|
|
||||||
<div class="admin-summary-grid">
|
|
||||||
<article class="stat-card">
|
|
||||||
<span>{props.copy.billingCycleTitle ?? ''}</span>
|
|
||||||
<strong>{props.cycleState?.cycle?.period ?? props.copy.billingCycleEmpty ?? ''}</strong>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span>{props.copy.settlementCurrency ?? ''}</span>
|
|
||||||
<strong>{props.adminSettings?.settings.settlementCurrency ?? '—'}</strong>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span>{props.copy.membersCount ?? ''}</span>
|
|
||||||
<strong>{String(props.adminSettings?.members.length ?? 0)}</strong>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<span>{props.copy.pendingRequests ?? ''}</span>
|
|
||||||
<strong>{String(props.pendingMembers.length)}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<div class="section-switch">
|
|
||||||
<For
|
|
||||||
each={
|
|
||||||
[
|
[
|
||||||
['billing', props.copy.houseSectionBilling],
|
{ key: 'billing', label: props.copy.houseSectionBilling ?? '' },
|
||||||
['utilities', props.copy.houseSectionUtilities],
|
{ key: 'utilities', label: props.copy.houseSectionUtilities ?? '' },
|
||||||
['members', props.copy.houseSectionMembers],
|
{ key: 'members', label: props.copy.houseSectionMembers ?? '' },
|
||||||
['topics', props.copy.houseSectionTopics]
|
{ key: 'topics', label: props.copy.houseSectionTopics ?? '' }
|
||||||
] as const
|
] as const
|
||||||
}
|
}
|
||||||
>
|
active={props.activeHouseSection}
|
||||||
{([key, label]) => (
|
onChange={props.onChangeHouseSection}
|
||||||
<button
|
/>
|
||||||
classList={{ 'is-active': props.activeHouseSection === key }}
|
|
||||||
type="button"
|
|
||||||
onClick={() => props.onChangeHouseSection(key)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Show when={props.activeHouseSection === 'billing'}>
|
<Show when={props.activeHouseSection === 'billing'}>
|
||||||
<section class="admin-section">
|
<section class="admin-section">
|
||||||
<header class="admin-section__header">
|
|
||||||
<div>
|
|
||||||
<h3>{props.copy.billingCycleTitle ?? ''}</h3>
|
|
||||||
<p>{props.copy.billingSettingsTitle ?? ''}</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="admin-grid">
|
<div class="admin-grid">
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
@@ -310,8 +269,7 @@ export function HouseScreen(props: Props) {
|
|||||||
<strong>{props.copy.householdLanguage ?? ''}</strong>
|
<strong>{props.copy.householdLanguage ?? ''}</strong>
|
||||||
<span>{props.householdDefaultLocale.toUpperCase()}</span>
|
<span>{props.householdDefaultLocale.toUpperCase()}</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{props.copy.householdSettingsBody ?? ''}</p>
|
<div class="locale-switch__buttons locale-switch__buttons--inline">
|
||||||
<div class="locale-switch__buttons">
|
|
||||||
<button
|
<button
|
||||||
classList={{ 'is-active': props.householdDefaultLocale === 'en' }}
|
classList={{ 'is-active': props.householdDefaultLocale === 'en' }}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -530,12 +488,6 @@ export function HouseScreen(props: Props) {
|
|||||||
|
|
||||||
<Show when={props.activeHouseSection === 'utilities'}>
|
<Show when={props.activeHouseSection === 'utilities'}>
|
||||||
<section class="admin-section">
|
<section class="admin-section">
|
||||||
<header class="admin-section__header">
|
|
||||||
<div>
|
|
||||||
<h3>{props.copy.utilityCategoriesTitle ?? ''}</h3>
|
|
||||||
<p>{props.copy.utilityCategoriesBody ?? ''}</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="admin-grid">
|
<div class="admin-grid">
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
@@ -861,12 +813,6 @@ export function HouseScreen(props: Props) {
|
|||||||
|
|
||||||
<Show when={props.activeHouseSection === 'members'}>
|
<Show when={props.activeHouseSection === 'members'}>
|
||||||
<section class="admin-section">
|
<section class="admin-section">
|
||||||
<header class="admin-section__header">
|
|
||||||
<div>
|
|
||||||
<h3>{props.copy.adminsTitle ?? ''}</h3>
|
|
||||||
<p>{props.copy.adminsBody ?? ''}</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="admin-grid">
|
<div class="admin-grid">
|
||||||
<article class="balance-item admin-card--wide">
|
<article class="balance-item admin-card--wide">
|
||||||
<header>
|
<header>
|
||||||
@@ -1118,12 +1064,6 @@ export function HouseScreen(props: Props) {
|
|||||||
|
|
||||||
<Show when={props.activeHouseSection === 'topics'}>
|
<Show when={props.activeHouseSection === 'topics'}>
|
||||||
<section class="admin-section">
|
<section class="admin-section">
|
||||||
<header class="admin-section__header">
|
|
||||||
<div>
|
|
||||||
<h3>{props.copy.topicBindingsTitle ?? ''}</h3>
|
|
||||||
<p>{props.copy.topicBindingsBody ?? ''}</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="admin-grid">
|
<div class="admin-grid">
|
||||||
<article class="balance-item admin-card--wide">
|
<article class="balance-item admin-card--wide">
|
||||||
<header>
|
<header>
|
||||||
|
|||||||
Reference in New Issue
Block a user