feat(miniapp): add billing review tools and house sections

This commit is contained in:
2026-03-10 22:04:43 +04:00
parent 7f8c238a23
commit 8f9abf998f
4 changed files with 1468 additions and 696 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,10 @@ export const dictionary = {
balances: 'Balances',
ledger: 'Ledger',
house: 'House',
houseSectionBilling: 'Billing',
houseSectionUtilities: 'Utilities',
houseSectionMembers: 'Members',
houseSectionTopics: 'Topics',
welcome: 'Welcome back',
adminTag: 'Admin',
residentTag: 'Resident',
@@ -69,6 +73,22 @@ export const dictionary = {
emptyDashboard: 'No billing cycle is ready yet.',
latestActivityTitle: 'Latest activity',
latestActivityEmpty: 'Recent utility and purchase entries will appear here.',
purchaseReviewTitle: 'Purchases',
purchaseReviewBody: 'Edit or remove purchases if the bot recorded the wrong item.',
paymentsAdminTitle: 'Payments',
paymentsAdminBody: 'Add, fix, or remove payment records for the current cycle.',
paymentsAddAction: 'Add payment',
addingPayment: 'Adding payment…',
paymentKind: 'Payment kind',
paymentAmount: 'Payment amount',
paymentMember: 'Member',
paymentSaveAction: 'Save payment',
paymentDeleteAction: 'Delete payment',
deletingPayment: 'Deleting payment…',
purchaseSaveAction: 'Save purchase',
purchaseDeleteAction: 'Delete purchase',
deletingPurchase: 'Deleting purchase…',
savingPurchase: 'Saving purchase…',
householdSettingsTitle: 'Household settings',
householdSettingsBody: 'Control household defaults and approve roommates who requested access.',
topicBindingsTitle: 'Topic bindings',
@@ -171,6 +191,10 @@ export const dictionary = {
balances: 'Баланс',
ledger: 'Леджер',
house: 'Дом',
houseSectionBilling: 'Биллинг',
houseSectionUtilities: 'Коммуналка',
houseSectionMembers: 'Участники',
houseSectionTopics: 'Топики',
welcome: 'С возвращением',
adminTag: 'Админ',
residentTag: 'Житель',
@@ -204,6 +228,23 @@ export const dictionary = {
emptyDashboard: 'Пока нет готового billing cycle.',
latestActivityTitle: 'Последняя активность',
latestActivityEmpty: 'Здесь появятся последние коммунальные платежи и покупки.',
purchaseReviewTitle: 'Покупки',
purchaseReviewBody:
'Здесь можно исправить или удалить покупку, если бот распознал её неправильно.',
paymentsAdminTitle: 'Оплаты',
paymentsAdminBody: 'Добавляй, исправляй или удаляй оплаты за текущий цикл.',
paymentsAddAction: 'Добавить оплату',
addingPayment: 'Добавляем оплату…',
paymentKind: 'Тип оплаты',
paymentAmount: 'Сумма оплаты',
paymentMember: 'Участник',
paymentSaveAction: 'Сохранить оплату',
paymentDeleteAction: 'Удалить оплату',
deletingPayment: 'Удаляем оплату…',
purchaseSaveAction: 'Сохранить покупку',
purchaseDeleteAction: 'Удалить покупку',
deletingPurchase: 'Удаляем покупку…',
savingPurchase: 'Сохраняем покупку…',
householdSettingsTitle: 'Настройки household',
householdSettingsBody: 'Здесь можно менять язык household и подтверждать новых соседей.',
topicBindingsTitle: 'Привязанные топики',

View File

@@ -327,6 +327,26 @@ button {
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 {
gap: 18px;
}
@@ -396,7 +416,8 @@ button {
padding: 12px 14px;
background: rgb(255 255 255 / 0.04);
color: inherit;
line-height: 1.35;
font-size: 1rem;
line-height: 1.2;
}
.settings-field__value {
@@ -452,6 +473,13 @@ button {
grid-column: 1 / -1;
}
.activity-row p,
.ledger-item p,
.utility-bill-row p,
.balance-item p {
overflow-wrap: anywhere;
}
@media (min-width: 760px) {
.shell {
max-width: 920px;
@@ -498,6 +526,10 @@ button {
.balance-item--wide {
grid-column: 1 / -1;
}
.section-switch {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 980px) {

View File

@@ -96,6 +96,7 @@ export interface MiniAppDashboard {
id: string
kind: 'purchase' | 'utility' | 'payment'
title: string
memberId: string | null
paymentKind: 'rent' | 'utilities' | null
amountMajor: string
currency: 'USD' | 'GEL'
@@ -731,3 +732,118 @@ export async function deleteMiniAppUtilityBill(
return payload.cycleState
}
export async function updateMiniAppPurchase(
initData: string,
input: {
purchaseId: string
description: string
amountMajor: string
currency: 'USD' | 'GEL'
}
): Promise<void> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/purchases/update`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
initData,
...input
})
})
const payload = (await response.json()) as { ok: boolean; authorized?: boolean; error?: string }
if (!response.ok || !payload.authorized) {
throw new Error(payload.error ?? 'Failed to update purchase')
}
}
export async function deleteMiniAppPurchase(initData: string, purchaseId: string): Promise<void> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/purchases/delete`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
initData,
purchaseId
})
})
const payload = (await response.json()) as { ok: boolean; authorized?: boolean; error?: string }
if (!response.ok || !payload.authorized) {
throw new Error(payload.error ?? 'Failed to delete purchase')
}
}
export async function addMiniAppPayment(
initData: string,
input: {
memberId: string
kind: 'rent' | 'utilities'
amountMajor: string
currency: 'USD' | 'GEL'
}
): Promise<void> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/payments/add`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
initData,
...input
})
})
const payload = (await response.json()) as { ok: boolean; authorized?: boolean; error?: string }
if (!response.ok || !payload.authorized) {
throw new Error(payload.error ?? 'Failed to add payment')
}
}
export async function updateMiniAppPayment(
initData: string,
input: {
paymentId: string
memberId: string
kind: 'rent' | 'utilities'
amountMajor: string
currency: 'USD' | 'GEL'
}
): Promise<void> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/payments/update`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
initData,
...input
})
})
const payload = (await response.json()) as { ok: boolean; authorized?: boolean; error?: string }
if (!response.ok || !payload.authorized) {
throw new Error(payload.error ?? 'Failed to update payment')
}
}
export async function deleteMiniAppPayment(initData: string, paymentId: string): Promise<void> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/payments/delete`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
initData,
paymentId
})
})
const payload = (await response.json()) as { ok: boolean; authorized?: boolean; error?: string }
if (!response.ok || !payload.authorized) {
throw new Error(payload.error ?? 'Failed to delete payment')
}
}