fix(miniapp): clarify cycle summary and balance sections

This commit is contained in:
2026-03-12 13:12:39 +04:00
parent a38686c8b0
commit 6053379f31
15 changed files with 440 additions and 101 deletions

View File

@@ -32,6 +32,48 @@ function formatCalendarDate(
}).format(new Date(Date.UTC(year, month - 1, day)))
}
function parsePeriod(period: string): { year: number; month: number } | null {
const [yearValue, monthValue] = period.split('-')
const year = Number.parseInt(yearValue ?? '', 10)
const month = Number.parseInt(monthValue ?? '', 10)
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
return null
}
return {
year,
month
}
}
function daysInMonth(year: number, month: number): number {
return new Date(Date.UTC(year, month, 0)).getUTCDate()
}
function formatTodayParts(timezone: string): { year: number; month: number; day: number } | null {
try {
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).formatToParts(new Date())
const year = Number.parseInt(parts.find((part) => part.type === 'year')?.value ?? '', 10)
const month = Number.parseInt(parts.find((part) => part.type === 'month')?.value ?? '', 10)
const day = Number.parseInt(parts.find((part) => part.type === 'day')?.value ?? '', 10)
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
return null
}
return { year, month, day }
} catch {
return null
}
}
export function formatFriendlyDate(value: string, locale: Locale): string {
const calendarDateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
if (calendarDateMatch) {
@@ -61,19 +103,56 @@ export function formatFriendlyDate(value: string, locale: Locale): string {
}
export function formatCyclePeriod(period: string, locale: Locale): string {
const [yearValue, monthValue] = period.split('-')
const year = Number.parseInt(yearValue ?? '', 10)
const month = Number.parseInt(monthValue ?? '', 10)
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
const parsed = parsePeriod(period)
if (!parsed) {
return period
}
const date = new Date(Date.UTC(year, month - 1, 1))
const includeYear = year !== new Date().getUTCFullYear()
const date = new Date(Date.UTC(parsed.year, parsed.month - 1, 1))
const includeYear = parsed.year !== new Date().getUTCFullYear()
return new Intl.DateTimeFormat(localeTag(locale), {
month: 'long',
...(includeYear ? { year: 'numeric' } : {})
}).format(date)
}
export function formatPeriodDay(period: string, day: number, locale: Locale): string {
const parsed = parsePeriod(period)
if (!parsed) {
return period
}
const safeDay = Math.max(1, Math.min(day, daysInMonth(parsed.year, parsed.month)))
return (
formatCalendarDate(parsed.year, parsed.month, safeDay, locale) ??
`${formatCyclePeriod(period, locale)} ${safeDay}`
)
}
export function compareTodayToPeriodDay(
period: string,
day: number,
timezone: string
): -1 | 0 | 1 | null {
const parsed = parsePeriod(period)
const today = formatTodayParts(timezone)
if (!parsed || !today) {
return null
}
const safeDay = Math.max(1, Math.min(day, daysInMonth(parsed.year, parsed.month)))
const dueValue = Date.UTC(parsed.year, parsed.month - 1, safeDay)
const todayValue = Date.UTC(today.year, today.month - 1, today.day)
if (todayValue < dueValue) {
return -1
}
if (todayValue > dueValue) {
return 1
}
return 0
}

View File

@@ -2,6 +2,7 @@ const CURATED_TIMEZONES = [
'Asia/Tbilisi',
'Europe/Berlin',
'Europe/London',
'Europe/Moscow',
'Europe/Paris',
'Europe/Warsaw',
'America/New_York',