mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 12:04:02 +00:00
fix(miniapp): address review feedback
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Match, Show, Switch, createMemo, createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { dictionary, type Locale } from './i18n'
|
||||
import { majorStringToMinor, minorToMajorString } from './lib/money'
|
||||
import {
|
||||
fetchAdminSettingsQuery,
|
||||
fetchBillingCycleQuery,
|
||||
@@ -124,6 +125,7 @@ type PaymentDraft = {
|
||||
type TestingRolePreview = 'admin' | 'resident'
|
||||
|
||||
const chartPalette = ['#f7b389', '#6fd3c0', '#f06a8d', '#94a8ff', '#f3d36f', '#7dc96d'] as const
|
||||
const TESTING_ROLE_TAP_WINDOW_MS = 30 * 60 * 1000
|
||||
|
||||
const demoSession: Extract<SessionState, { status: 'ready' }> = {
|
||||
status: 'ready',
|
||||
@@ -178,27 +180,6 @@ function defaultCyclePeriod(): string {
|
||||
return new Date().toISOString().slice(0, 7)
|
||||
}
|
||||
|
||||
function majorStringToMinor(value: string): bigint {
|
||||
const trimmed = value.trim()
|
||||
const negative = trimmed.startsWith('-')
|
||||
const normalized = negative ? trimmed.slice(1) : trimmed
|
||||
const [whole = '0', fraction = ''] = normalized.split('.')
|
||||
const major = BigInt(whole || '0')
|
||||
const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0')
|
||||
const minor = major * 100n + cents
|
||||
|
||||
return negative ? -minor : minor
|
||||
}
|
||||
|
||||
function minorToMajorString(value: bigint): string {
|
||||
const negative = value < 0n
|
||||
const absolute = negative ? -value : value
|
||||
const whole = absolute / 100n
|
||||
const fraction = String(absolute % 100n).padStart(2, '0')
|
||||
|
||||
return `${negative ? '-' : ''}${whole.toString()}.${fraction}`
|
||||
}
|
||||
|
||||
function absoluteMinor(value: bigint): bigint {
|
||||
return value < 0n ? -value : value
|
||||
}
|
||||
@@ -691,7 +672,10 @@ function App() {
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const nextHistory = [...roleChipTapHistory().filter((timestamp) => now - timestamp < 1800), now]
|
||||
const nextHistory = [
|
||||
...roleChipTapHistory().filter((timestamp) => now - timestamp < TESTING_ROLE_TAP_WINDOW_MS),
|
||||
now
|
||||
]
|
||||
|
||||
if (nextHistory.length >= 5) {
|
||||
setRoleChipTapHistory([])
|
||||
@@ -2209,28 +2193,36 @@ function App() {
|
||||
}))
|
||||
}
|
||||
onBillingRentDueDayChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
rentDueDay: value
|
||||
}))
|
||||
value === null
|
||||
? undefined
|
||||
: setBillingForm((current) => ({
|
||||
...current,
|
||||
rentDueDay: value
|
||||
}))
|
||||
}
|
||||
onBillingRentWarningDayChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
rentWarningDay: value
|
||||
}))
|
||||
value === null
|
||||
? undefined
|
||||
: setBillingForm((current) => ({
|
||||
...current,
|
||||
rentWarningDay: value
|
||||
}))
|
||||
}
|
||||
onBillingUtilitiesDueDayChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
utilitiesDueDay: value
|
||||
}))
|
||||
value === null
|
||||
? undefined
|
||||
: setBillingForm((current) => ({
|
||||
...current,
|
||||
utilitiesDueDay: value
|
||||
}))
|
||||
}
|
||||
onBillingUtilitiesReminderDayChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
utilitiesReminderDay: value
|
||||
}))
|
||||
value === null
|
||||
? undefined
|
||||
: setBillingForm((current) => ({
|
||||
...current,
|
||||
utilitiesReminderDay: value
|
||||
}))
|
||||
}
|
||||
onBillingTimezoneChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { For, Show } from 'solid-js'
|
||||
|
||||
import { cn } from '../../lib/cn'
|
||||
import { majorStringToMinor, sumMajorStrings } from '../../lib/money'
|
||||
import type { MiniAppDashboard } from '../../miniapp-api'
|
||||
import { MiniChip, StatCard } from '../ui'
|
||||
|
||||
@@ -11,31 +12,6 @@ type Props = {
|
||||
detail?: boolean
|
||||
}
|
||||
|
||||
function majorStringToMinor(value: string): bigint {
|
||||
const trimmed = value.trim()
|
||||
const negative = trimmed.startsWith('-')
|
||||
const normalized = negative ? trimmed.slice(1) : trimmed
|
||||
const [whole = '0', fraction = ''] = normalized.split('.')
|
||||
const major = BigInt(whole || '0')
|
||||
const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0')
|
||||
const minor = major * 100n + cents
|
||||
|
||||
return negative ? -minor : minor
|
||||
}
|
||||
|
||||
function minorToMajorString(value: bigint): string {
|
||||
const negative = value < 0n
|
||||
const absolute = negative ? -value : value
|
||||
const whole = absolute / 100n
|
||||
const fraction = String(absolute % 100n).padStart(2, '0')
|
||||
|
||||
return `${negative ? '-' : ''}${whole.toString()}.${fraction}`
|
||||
}
|
||||
|
||||
function sumMajorStrings(left: string, right: string): string {
|
||||
return minorToMajorString(majorStringToMinor(left) + majorStringToMinor(right))
|
||||
}
|
||||
|
||||
export function MemberBalanceCard(props: Props) {
|
||||
const utilitiesAdjustedMajor = () =>
|
||||
sumMajorStrings(props.member.utilityShareMajor, props.member.purchaseOffsetMajor)
|
||||
|
||||
@@ -31,6 +31,8 @@ export const dictionary = {
|
||||
language: 'Language',
|
||||
householdLanguage: 'Household language',
|
||||
savingLanguage: 'Saving…',
|
||||
onLabel: 'On',
|
||||
offLabel: 'Off',
|
||||
home: 'Home',
|
||||
balances: 'Balances',
|
||||
ledger: 'Ledger',
|
||||
@@ -273,6 +275,8 @@ export const dictionary = {
|
||||
language: 'Язык',
|
||||
householdLanguage: 'Язык дома',
|
||||
savingLanguage: 'Сохраняем…',
|
||||
onLabel: 'Вкл',
|
||||
offLabel: 'Выкл',
|
||||
home: 'Главная',
|
||||
balances: 'Баланс',
|
||||
ledger: 'Леджер',
|
||||
|
||||
@@ -884,6 +884,13 @@ button {
|
||||
}
|
||||
|
||||
.mini-chip-button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
24
apps/miniapp/src/lib/money.ts
Normal file
24
apps/miniapp/src/lib/money.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export function majorStringToMinor(value: string): bigint {
|
||||
const trimmed = value.trim()
|
||||
const negative = trimmed.startsWith('-')
|
||||
const normalized = negative ? trimmed.slice(1) : trimmed
|
||||
const [whole = '0', fraction = ''] = normalized.split('.')
|
||||
const major = BigInt(whole || '0')
|
||||
const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0')
|
||||
const minor = major * 100n + cents
|
||||
|
||||
return negative ? -minor : minor
|
||||
}
|
||||
|
||||
export function minorToMajorString(value: bigint): string {
|
||||
const negative = value < 0n
|
||||
const absolute = negative ? -value : value
|
||||
const whole = absolute / 100n
|
||||
const fraction = String(absolute % 100n).padStart(2, '0')
|
||||
|
||||
return `${negative ? '-' : ''}${whole.toString()}.${fraction}`
|
||||
}
|
||||
|
||||
export function sumMajorStrings(left: string, right: string): string {
|
||||
return minorToMajorString(majorStringToMinor(left) + majorStringToMinor(right))
|
||||
}
|
||||
@@ -116,10 +116,10 @@ type Props = {
|
||||
onBillingAdjustmentPolicyChange: (value: 'utilities' | 'rent' | 'separate') => void
|
||||
onBillingRentAmountChange: (value: string) => void
|
||||
onBillingRentCurrencyChange: (value: 'USD' | 'GEL') => void
|
||||
onBillingRentDueDayChange: (value: number) => void
|
||||
onBillingRentWarningDayChange: (value: number) => void
|
||||
onBillingUtilitiesDueDayChange: (value: number) => void
|
||||
onBillingUtilitiesReminderDayChange: (value: number) => void
|
||||
onBillingRentDueDayChange: (value: number | null) => void
|
||||
onBillingRentWarningDayChange: (value: number | null) => void
|
||||
onBillingUtilitiesDueDayChange: (value: number | null) => void
|
||||
onBillingUtilitiesReminderDayChange: (value: number | null) => void
|
||||
onBillingTimezoneChange: (value: string) => void
|
||||
onOpenAddUtilityBill: () => void
|
||||
onCloseAddUtilityBill: () => void
|
||||
@@ -165,6 +165,23 @@ type Props = {
|
||||
}
|
||||
|
||||
export function HouseScreen(props: Props) {
|
||||
function parseBillingDayInput(value: string): number | null {
|
||||
const trimmed = value.trim()
|
||||
if (trimmed.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(trimmed, 10)
|
||||
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1 || parsed > 31) {
|
||||
return null
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
const enabledLabel = () => props.copy.onLabel ?? 'ON'
|
||||
const disabledLabel = () => props.copy.offLabel ?? 'OFF'
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.readyIsAdmin}
|
||||
@@ -446,7 +463,9 @@ export function HouseScreen(props: Props) {
|
||||
max="31"
|
||||
value={String(props.billingForm.rentDueDay)}
|
||||
onInput={(event) =>
|
||||
props.onBillingRentDueDayChange(Number(event.currentTarget.value))
|
||||
props.onBillingRentDueDayChange(
|
||||
parseBillingDayInput(event.currentTarget.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
@@ -457,7 +476,9 @@ export function HouseScreen(props: Props) {
|
||||
max="31"
|
||||
value={String(props.billingForm.rentWarningDay)}
|
||||
onInput={(event) =>
|
||||
props.onBillingRentWarningDayChange(Number(event.currentTarget.value))
|
||||
props.onBillingRentWarningDayChange(
|
||||
parseBillingDayInput(event.currentTarget.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
@@ -468,7 +489,9 @@ export function HouseScreen(props: Props) {
|
||||
max="31"
|
||||
value={String(props.billingForm.utilitiesDueDay)}
|
||||
onInput={(event) =>
|
||||
props.onBillingUtilitiesDueDayChange(Number(event.currentTarget.value))
|
||||
props.onBillingUtilitiesDueDayChange(
|
||||
parseBillingDayInput(event.currentTarget.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
@@ -479,7 +502,9 @@ export function HouseScreen(props: Props) {
|
||||
max="31"
|
||||
value={String(props.billingForm.utilitiesReminderDay)}
|
||||
onInput={(event) =>
|
||||
props.onBillingUtilitiesReminderDayChange(Number(event.currentTarget.value))
|
||||
props.onBillingUtilitiesReminderDayChange(
|
||||
parseBillingDayInput(event.currentTarget.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
@@ -562,14 +587,14 @@ export function HouseScreen(props: Props) {
|
||||
<div class="ledger-compact-card__main">
|
||||
<header>
|
||||
<strong>{category.name}</strong>
|
||||
<span>{category.isActive ? 'ON' : 'OFF'}</span>
|
||||
<span>{category.isActive ? enabledLabel() : disabledLabel()}</span>
|
||||
</header>
|
||||
<p>{props.copy.utilityCategoryName ?? ''}</p>
|
||||
<div class="ledger-compact-card__meta">
|
||||
<span
|
||||
class={`mini-chip ${category.isActive ? '' : 'mini-chip--muted'}`}
|
||||
>
|
||||
{category.isActive ? 'ON' : 'OFF'}
|
||||
{category.isActive ? enabledLabel() : disabledLabel()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -815,8 +840,8 @@ export function HouseScreen(props: Props) {
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="true">ON</option>
|
||||
<option value="false">OFF</option>
|
||||
<option value="true">{enabledLabel()}</option>
|
||||
<option value="false">{disabledLabel()}</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user