mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 15:54:03 +00:00
refactor(miniapp): polish admin house layout
This commit is contained in:
@@ -1028,13 +1028,41 @@ function App() {
|
|||||||
)
|
)
|
||||||
case 'house':
|
case 'house':
|
||||||
return readySession()?.member.isAdmin ? (
|
return readySession()?.member.isAdmin ? (
|
||||||
<div class="balance-list">
|
<div class="admin-layout">
|
||||||
<article class="balance-item">
|
<article class="balance-item balance-item--accent admin-hero">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().householdSettingsTitle}</strong>
|
<strong>{copy().householdSettingsTitle}</strong>
|
||||||
|
<span>{adminSettings()?.settings.settlementCurrency ?? '—'}</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{copy().householdSettingsBody}</p>
|
<p>{copy().householdSettingsBody}</p>
|
||||||
|
<div class="admin-summary-grid">
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>{copy().billingCycleTitle}</span>
|
||||||
|
<strong>{cycleState()?.cycle?.period ?? copy().billingCycleEmpty}</strong>
|
||||||
</article>
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>{copy().settlementCurrency}</span>
|
||||||
|
<strong>{adminSettings()?.settings.settlementCurrency ?? '—'}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>{copy().membersCount}</span>
|
||||||
|
<strong>{String(adminSettings()?.members.length ?? 0)}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="stat-card">
|
||||||
|
<span>{copy().pendingRequests}</span>
|
||||||
|
<strong>{String(pendingMembers().length)}</strong>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section class="admin-section">
|
||||||
|
<header class="admin-section__header">
|
||||||
|
<div>
|
||||||
|
<h3>{copy().billingCycleTitle}</h3>
|
||||||
|
<p>{copy().billingSettingsTitle}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="admin-grid">
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().billingCycleTitle}</strong>
|
<strong>{copy().billingCycleTitle}</strong>
|
||||||
@@ -1108,65 +1136,6 @@ function App() {
|
|||||||
{closingCycle() ? copy().closingCycle : copy().closeCycleAction}
|
{closingCycle() ? copy().closingCycle : copy().closeCycleAction}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
|
||||||
<label class="settings-field">
|
|
||||||
<span>{copy().utilityCategoryLabel}</span>
|
|
||||||
<select
|
|
||||||
value={cycleForm().utilityCategorySlug}
|
|
||||||
onChange={(event) =>
|
|
||||||
setCycleForm((current) => ({
|
|
||||||
...current,
|
|
||||||
utilityCategorySlug: event.currentTarget.value
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{adminSettings()
|
|
||||||
?.categories.filter((category) => category.isActive)
|
|
||||||
.map((category) => (
|
|
||||||
<option value={category.slug}>{category.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="settings-field">
|
|
||||||
<span>{copy().utilityAmount}</span>
|
|
||||||
<input
|
|
||||||
value={cycleForm().utilityAmountMajor}
|
|
||||||
onInput={(event) =>
|
|
||||||
setCycleForm((current) => ({
|
|
||||||
...current,
|
|
||||||
utilityAmountMajor: event.currentTarget.value
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="ghost-button"
|
|
||||||
type="button"
|
|
||||||
disabled={
|
|
||||||
savingUtilityBill() || cycleForm().utilityAmountMajor.trim().length === 0
|
|
||||||
}
|
|
||||||
onClick={() => void handleAddUtilityBill()}
|
|
||||||
>
|
|
||||||
{savingUtilityBill() ? copy().savingUtilityBill : copy().addUtilityBillAction}
|
|
||||||
</button>
|
|
||||||
<div class="balance-list">
|
|
||||||
{cycleState()?.utilityBills.length ? (
|
|
||||||
cycleState()?.utilityBills.map((bill) => (
|
|
||||||
<article class="ledger-item">
|
|
||||||
<header>
|
|
||||||
<strong>{bill.billName}</strong>
|
|
||||||
<span>
|
|
||||||
{(Number(bill.amountMinor) / 100).toFixed(2)} {bill.currency}
|
|
||||||
</span>
|
|
||||||
</header>
|
|
||||||
<p>{bill.createdAt.slice(0, 10)}</p>
|
|
||||||
</article>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p>{copy().utilityBillsEmpty}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -1211,9 +1180,11 @@ function App() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().billingSettingsTitle}</strong>
|
<strong>{copy().billingSettingsTitle}</strong>
|
||||||
|
<span>{billingForm().settlementCurrency}</span>
|
||||||
</header>
|
</header>
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
<label class="settings-field">
|
<label class="settings-field">
|
||||||
@@ -1340,11 +1311,13 @@ function App() {
|
|||||||
{savingBillingSettings() ? copy().savingSettings : copy().saveSettingsAction}
|
{savingBillingSettings() ? copy().savingSettings : copy().saveSettingsAction}
|
||||||
</button>
|
</button>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().householdLanguage}</strong>
|
<strong>{copy().householdLanguage}</strong>
|
||||||
<span>{readySession()?.member.householdDefaultLocale.toUpperCase()}</span>
|
<span>{readySession()?.member.householdDefaultLocale.toUpperCase()}</span>
|
||||||
</header>
|
</header>
|
||||||
|
<p>{copy().householdSettingsBody}</p>
|
||||||
<div class="locale-switch__buttons">
|
<div class="locale-switch__buttons">
|
||||||
<button
|
<button
|
||||||
classList={{
|
classList={{
|
||||||
@@ -1368,12 +1341,89 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-section">
|
||||||
|
<header class="admin-section__header">
|
||||||
|
<div>
|
||||||
|
<h3>{copy().utilityCategoriesTitle}</h3>
|
||||||
|
<p>{copy().utilityCategoriesBody}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="admin-grid">
|
||||||
|
<article class="balance-item">
|
||||||
|
<header>
|
||||||
|
<strong>{copy().utilityLedgerTitle}</strong>
|
||||||
|
<span>{cycleState()?.cycle?.currency ?? billingForm().settlementCurrency}</span>
|
||||||
|
</header>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<label class="settings-field">
|
||||||
|
<span>{copy().utilityCategoryLabel}</span>
|
||||||
|
<select
|
||||||
|
value={cycleForm().utilityCategorySlug}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCycleForm((current) => ({
|
||||||
|
...current,
|
||||||
|
utilityCategorySlug: event.currentTarget.value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{adminSettings()
|
||||||
|
?.categories.filter((category) => category.isActive)
|
||||||
|
.map((category) => (
|
||||||
|
<option value={category.slug}>{category.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="settings-field">
|
||||||
|
<span>{copy().utilityAmount}</span>
|
||||||
|
<input
|
||||||
|
value={cycleForm().utilityAmountMajor}
|
||||||
|
onInput={(event) =>
|
||||||
|
setCycleForm((current) => ({
|
||||||
|
...current,
|
||||||
|
utilityAmountMajor: event.currentTarget.value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="ghost-button"
|
||||||
|
type="button"
|
||||||
|
disabled={
|
||||||
|
savingUtilityBill() || cycleForm().utilityAmountMajor.trim().length === 0
|
||||||
|
}
|
||||||
|
onClick={() => void handleAddUtilityBill()}
|
||||||
|
>
|
||||||
|
{savingUtilityBill() ? copy().savingUtilityBill : copy().addUtilityBillAction}
|
||||||
|
</button>
|
||||||
|
<div class="balance-list admin-sublist">
|
||||||
|
{cycleState()?.utilityBills.length ? (
|
||||||
|
cycleState()?.utilityBills.map((bill) => (
|
||||||
|
<article class="ledger-item">
|
||||||
|
<header>
|
||||||
|
<strong>{bill.billName}</strong>
|
||||||
|
<span>
|
||||||
|
{(Number(bill.amountMinor) / 100).toFixed(2)} {bill.currency}
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<p>{bill.createdAt.slice(0, 10)}</p>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>{copy().utilityBillsEmpty}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().utilityCategoriesTitle}</strong>
|
<strong>{copy().utilityCategoriesTitle}</strong>
|
||||||
|
<span>{String(adminSettings()?.categories.length ?? 0)}</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{copy().utilityCategoriesBody}</p>
|
<div class="balance-list admin-sublist">
|
||||||
<div class="balance-list">
|
|
||||||
{adminSettings()?.categories.map((category) => (
|
{adminSettings()?.categories.map((category) => (
|
||||||
<article class="ledger-item">
|
<article class="ledger-item">
|
||||||
<header>
|
<header>
|
||||||
@@ -1439,12 +1489,14 @@ function App() {
|
|||||||
void handleSaveUtilityCategory({
|
void handleSaveUtilityCategory({
|
||||||
slug: category.slug,
|
slug: category.slug,
|
||||||
name:
|
name:
|
||||||
adminSettings()?.categories.find((item) => item.slug === category.slug)
|
adminSettings()?.categories.find(
|
||||||
?.name ?? category.name,
|
(item) => item.slug === category.slug
|
||||||
|
)?.name ?? category.name,
|
||||||
sortOrder: category.sortOrder,
|
sortOrder: category.sortOrder,
|
||||||
isActive:
|
isActive:
|
||||||
adminSettings()?.categories.find((item) => item.slug === category.slug)
|
adminSettings()?.categories.find(
|
||||||
?.isActive ?? category.isActive
|
(item) => item.slug === category.slug
|
||||||
|
)?.isActive ?? category.isActive
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -1466,7 +1518,8 @@ function App() {
|
|||||||
class="ghost-button"
|
class="ghost-button"
|
||||||
type="button"
|
type="button"
|
||||||
disabled={
|
disabled={
|
||||||
newCategoryName().trim().length === 0 || savingCategorySlug() === '__new__'
|
newCategoryName().trim().length === 0 ||
|
||||||
|
savingCategorySlug() === '__new__'
|
||||||
}
|
}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
void handleSaveUtilityCategory({
|
void handleSaveUtilityCategory({
|
||||||
@@ -1483,23 +1536,37 @@ function App() {
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="balance-item">
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-section">
|
||||||
|
<header class="admin-section__header">
|
||||||
|
<div>
|
||||||
|
<h3>{copy().adminsTitle}</h3>
|
||||||
|
<p>{copy().adminsBody}</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="admin-grid">
|
||||||
|
<article class="balance-item admin-card--wide">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().adminsTitle}</strong>
|
<strong>{copy().adminsTitle}</strong>
|
||||||
|
<span>{String(adminSettings()?.members.length ?? 0)}</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{copy().adminsBody}</p>
|
<div class="balance-list admin-sublist">
|
||||||
<div class="balance-list">
|
|
||||||
{adminSettings()?.members.map((member) => (
|
{adminSettings()?.members.map((member) => (
|
||||||
<article class="ledger-item">
|
<article class="ledger-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{member.displayName}</strong>
|
<strong>{member.displayName}</strong>
|
||||||
<span>{member.isAdmin ? copy().adminTag : copy().residentTag}</span>
|
<span>{member.isAdmin ? copy().adminTag : copy().residentTag}</span>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="settings-grid">
|
||||||
<label class="settings-field settings-field--wide">
|
<label class="settings-field settings-field--wide">
|
||||||
<span>{copy().rentWeightLabel}</span>
|
<span>{copy().rentWeightLabel}</span>
|
||||||
<input
|
<input
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
value={rentWeightDrafts()[member.id] ?? String(member.rentShareWeight)}
|
value={
|
||||||
|
rentWeightDrafts()[member.id] ?? String(member.rentShareWeight)
|
||||||
|
}
|
||||||
onInput={(event) =>
|
onInput={(event) =>
|
||||||
setRentWeightDrafts((current) => ({
|
setRentWeightDrafts((current) => ({
|
||||||
...current,
|
...current,
|
||||||
@@ -1508,6 +1575,8 @@ function App() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="inline-actions">
|
||||||
<button
|
<button
|
||||||
class="ghost-button"
|
class="ghost-button"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1533,23 +1602,24 @@ function App() {
|
|||||||
: copy().promoteAdminAction}
|
: copy().promoteAdminAction}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="balance-item">
|
<article class="balance-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{copy().pendingMembersTitle}</strong>
|
<strong>{copy().pendingMembersTitle}</strong>
|
||||||
|
<span>{String(pendingMembers().length)}</span>
|
||||||
</header>
|
</header>
|
||||||
<p>{copy().pendingMembersBody}</p>
|
<p>{copy().pendingMembersBody}</p>
|
||||||
</article>
|
|
||||||
{pendingMembers().length === 0 ? (
|
{pendingMembers().length === 0 ? (
|
||||||
<article class="balance-item">
|
|
||||||
<p>{copy().pendingMembersEmpty}</p>
|
<p>{copy().pendingMembersEmpty}</p>
|
||||||
</article>
|
|
||||||
) : (
|
) : (
|
||||||
pendingMembers().map((member) => (
|
<div class="balance-list admin-sublist">
|
||||||
<article class="balance-item">
|
{pendingMembers().map((member) => (
|
||||||
|
<article class="ledger-item">
|
||||||
<header>
|
<header>
|
||||||
<strong>{member.displayName}</strong>
|
<strong>{member.displayName}</strong>
|
||||||
<span>{member.telegramUserId}</span>
|
<span>{member.telegramUserId}</span>
|
||||||
@@ -1570,8 +1640,12 @@ function App() {
|
|||||||
: copy().approveMemberAction}
|
: copy().approveMemberAction}
|
||||||
</button>
|
</button>
|
||||||
</article>
|
</article>
|
||||||
))
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div class="balance-list">
|
<div class="balance-list">
|
||||||
|
|||||||
@@ -229,7 +229,9 @@ button {
|
|||||||
|
|
||||||
.balance-list,
|
.balance-list,
|
||||||
.ledger-list,
|
.ledger-list,
|
||||||
.home-grid {
|
.home-grid,
|
||||||
|
.admin-layout,
|
||||||
|
.admin-sublist {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
@@ -316,6 +318,52 @@ button {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-layout {
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-hero {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-summary-grid,
|
||||||
|
.admin-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-section {
|
||||||
|
display: grid;
|
||||||
|
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 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card--wide {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-field {
|
.settings-field {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -374,6 +422,14 @@ button {
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-summary-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -381,6 +437,10 @@ button {
|
|||||||
.panel--wide {
|
.panel--wide {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-card--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 980px) {
|
@media (min-width: 980px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user