fix: refine mobile financial workflows

This commit is contained in:
k1nq
2026-09-22 08:36:24 +05:00
parent c70dd11d18
commit 30bd1b48a0
5 changed files with 63 additions and 29 deletions
+8 -8
View File
@@ -146,8 +146,8 @@
"archiveConfirmBody": "Account “{{name}}” will leave working lists but remain in history." "archiveConfirmBody": "Account “{{name}}” will leave working lists but remain in history."
}, },
"operations": { "operations": {
"title": "Operations", "title": "Ledger",
"subtitle": "Expenses, income, transfers and refunds.", "subtitle": "Every expense, income, transfer and refund in one traceable sequence.",
"new": "New operation", "new": "New operation",
"empty": "No operations match the selected filters.", "empty": "No operations match the selected filters.",
"search": "Search", "search": "Search",
@@ -187,8 +187,8 @@
"nextPage": "Next" "nextPage": "Next"
}, },
"budget": { "budget": {
"title": "Budget", "title": "Monthly plan",
"subtitle": "Month plan, funding and the general pool.", "subtitle": "Plan every ruble, then fund the month from the real pool.",
"monthPicker": "Month picker", "monthPicker": "Month picker",
"month": "Month", "month": "Month",
"prevMonth": "Previous month", "prevMonth": "Previous month",
@@ -329,8 +329,8 @@
"revertBody": "Operations created by the “{{fileName}}” import will be removed atomically; affected months will require reopening." "revertBody": "Operations created by the “{{fileName}}” import will be removed atomically; affected months will require reopening."
}, },
"analytics": { "analytics": {
"title": "Analytics", "title": "Financial picture",
"subtitle": "Four fixed reports for your space.", "subtitle": "Spending, plan accuracy, cash flow and balances without noise.",
"range": "Period:", "range": "Period:",
"months_one": "{{count}} month", "months_one": "{{count}} month",
"months_other": "{{count}} months", "months_other": "{{count}} months",
@@ -352,8 +352,8 @@
"unallocated": "Unallocated" "unallocated": "Unallocated"
}, },
"settings": { "settings": {
"title": "Settings & data", "title": "Space & data",
"subtitle": "Profile, export and space data controls.", "subtitle": "Identity, language, export and lifecycle controls for this financial space.",
"profile": "Profile", "profile": "Profile",
"timezone": "Time zone", "timezone": "Time zone",
"dataTitle": "Data", "dataTitle": "Data",
+8 -8
View File
@@ -146,8 +146,8 @@
"archiveConfirmBody": "Счёт «{{name}}» скроется из рабочих списков, но останется в истории." "archiveConfirmBody": "Счёт «{{name}}» скроется из рабочих списков, но останется в истории."
}, },
"operations": { "operations": {
"title": "Операции", "title": "Гроссбух",
"subtitle": "Расходы, доходы, переводы и возвраты.", "subtitle": "Расходы, доходы, переводы и возвраты в одной проверяемой последовательности.",
"new": "Новая операция", "new": "Новая операция",
"empty": "Операций по выбранным условиям не найдено.", "empty": "Операций по выбранным условиям не найдено.",
"search": "Поиск", "search": "Поиск",
@@ -187,8 +187,8 @@
"nextPage": "Дальше" "nextPage": "Дальше"
}, },
"budget": { "budget": {
"title": "Бюджет", "title": "План месяца",
"subtitle": "План месяца, обеспечение и общий пул.", "subtitle": "Спланируйте каждый рубль и обеспечьте месяц из реального пула.",
"monthPicker": "Выбор месяца", "monthPicker": "Выбор месяца",
"month": "Месяц", "month": "Месяц",
"prevMonth": "Предыдущий месяц", "prevMonth": "Предыдущий месяц",
@@ -329,8 +329,8 @@
"revertBody": "Операции, созданные импортом «{{fileName}}», будут удалены атомарно; затронутые месяцы потребуют переоткрытия." "revertBody": "Операции, созданные импортом «{{fileName}}», будут удалены атомарно; затронутые месяцы потребуют переоткрытия."
}, },
"analytics": { "analytics": {
"title": "Аналитика", "title": "Финансовая картина",
"subtitle": "Четыре фиксированных отчёта по вашему пространству.", "subtitle": "Расходы, точность плана, денежный поток и балансы без шума.",
"range": "Период:", "range": "Период:",
"months_one": "{{count}} месяц", "months_one": "{{count}} месяц",
"months_few": "{{count}} месяца", "months_few": "{{count}} месяца",
@@ -354,8 +354,8 @@
"unallocated": "Без распределения" "unallocated": "Без распределения"
}, },
"settings": { "settings": {
"title": "Настройки и данные", "title": "Пространство и данные",
"subtitle": "Профиль, экспорт и управление данными пространства.", "subtitle": "Профиль, язык, экспорт и жизненный цикл финансового пространства.",
"profile": "Профиль", "profile": "Профиль",
"timezone": "Часовой пояс", "timezone": "Часовой пояс",
"dataTitle": "Данные", "dataTitle": "Данные",
+6 -6
View File
@@ -246,22 +246,22 @@ export function ImportDetailPage() {
</thead> </thead>
<tbody> <tbody>
{data.rows.map((row) => ( {data.rows.map((row) => (
<tr key={row.id}> <tr key={row.id} className="statement-row">
<th scope="row" data-label={t('common.date')}> <th scope="row" data-label={t('common.date')} className="statement-date">
{formatDate(row.occurredOn, locale)} {formatDate(row.occurredOn, locale)}
</th> </th>
<td data-label={t('operations.description')}> <td data-label={t('operations.description')} className="statement-description">
{row.description ?? '—'} {row.description ?? '—'}
{row.payeeHint ? <span className="muted"> · {row.payeeHint}</span> : null} {row.payeeHint ? <span className="muted"> · {row.payeeHint}</span> : null}
</td> </td>
<td <td
data-label={t('common.amount')} data-label={t('common.amount')}
className={`num ${row.direction === 'in' ? 'pos' : 'neg'}`} className={`statement-amount num ${row.direction === 'in' ? 'pos' : 'neg'}`}
> >
{row.direction === 'in' ? '+' : '−'} {row.direction === 'in' ? '+' : '−'}
{formatMoney(row.amountKopecks, locale)} {formatMoney(row.amountKopecks, locale)}
</td> </td>
<td data-label={t('imports.rowWarnings')}> <td data-label={t('imports.rowWarnings')} className="statement-warnings">
{row.warnings.length === 0 ? ( {row.warnings.length === 0 ? (
'—' '—'
) : ( ) : (
@@ -274,7 +274,7 @@ export function ImportDetailPage() {
</ul> </ul>
)} )}
</td> </td>
<td data-label={t('imports.decision')}> <td data-label={t('imports.decision')} className="statement-decision">
<select <select
className="input select" className="input select"
aria-label={t('imports.decision')} aria-label={t('imports.decision')}
+7 -7
View File
@@ -623,10 +623,10 @@ export function OperationsPage() {
key={operation.id} key={operation.id}
data-voided={operation.status === 'voided' ? 'true' : undefined} data-voided={operation.status === 'voided' ? 'true' : undefined}
> >
<th scope="row" data-label={t('common.date')}> <th scope="row" data-label={t('common.date')} className="operation-date">
{formatDate(operation.occurredOn, locale)} {formatDate(operation.occurredOn, locale)}
</th> </th>
<td data-label={t('operations.kindLabel')}> <td data-label={t('operations.kindLabel')} className="operation-kind">
<Badge tone={OPERATION_KIND_TONES[operation.kind]}> <Badge tone={OPERATION_KIND_TONES[operation.kind]}>
{t(`operations.kind.${operation.kind}`)} {t(`operations.kind.${operation.kind}`)}
</Badge> </Badge>
@@ -634,22 +634,22 @@ export function OperationsPage() {
<Badge tone="neutral">{t('operations.status.voided')}</Badge> <Badge tone="neutral">{t('operations.status.voided')}</Badge>
) : null} ) : null}
</td> </td>
<td data-label={t('operations.description')}> <td data-label={t('operations.description')} className="operation-description">
{operation.payeeName ?? operation.description ?? '—'} {operation.payeeName ?? operation.description ?? '—'}
</td> </td>
<td data-label={t('operations.account')}> <td data-label={t('operations.account')} className="operation-account">
{accountName(operation.accountId)} {accountName(operation.accountId)}
{operation.kind === 'transfer' {operation.kind === 'transfer'
? ` → ${accountName(operation.counterpartyAccountId)}` ? ` → ${accountName(operation.counterpartyAccountId)}`
: ''} : ''}
</td> </td>
<td data-label={t('operations.category')}> <td data-label={t('operations.category')} className="operation-category">
{operation.categoryName ?? t('operations.noCategory')} {operation.categoryName ?? t('operations.noCategory')}
</td> </td>
<td data-label={t('common.amount')} className={`num ${amount.tone}`}> <td data-label={t('common.amount')} className={`operation-amount num ${amount.tone}`}>
{amount.text} {amount.text}
</td> </td>
<td data-label={t('common.actions')}> <td data-label={t('common.actions')} className="operation-actions">
{operation.status === 'confirmed' ? ( {operation.status === 'confirmed' ? (
<Button variant="subtle" onClick={() => setVoidTarget(operation)}> <Button variant="subtle" onClick={() => setVoidTarget(operation)}>
{t('operations.void')} {t('operations.void')}
+34
View File
@@ -792,6 +792,40 @@ input[type='file']::file-selector-button { min-height: 30px; margin-right: 10px;
.data-table tbody td:nth-child(odd) { justify-content: flex-end; text-align: right; } .data-table tbody td:nth-child(odd) { justify-content: flex-end; text-align: right; }
.data-table tbody td:last-child { grid-column: 1 / -1; justify-content: flex-end; } .data-table tbody td:last-child { grid-column: 1 / -1; justify-content: flex-end; }
.data-table tbody td .inline-actions { width: 100%; } .data-table tbody td .inline-actions { width: 100%; }
.view-operations .data-table tbody tr {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
'date kind'
'description description'
'account category'
'amount actions';
column-gap: 14px;
}
.view-operations .operation-date { grid-area: date; grid-column: auto; }
.view-operations .operation-kind { grid-area: kind; justify-content: flex-end; }
.view-operations .operation-description { grid-area: description; display: block; padding-block: 8px; font-size: 12px; line-height: 1.4; text-align: left; }
.view-operations .operation-account { grid-area: account; justify-content: flex-start; }
.view-operations .operation-category { grid-area: category; justify-content: flex-end; text-align: right; }
.view-operations .operation-amount { grid-area: amount; justify-content: flex-start; text-align: left; }
.view-operations .operation-actions { grid-area: actions; grid-column: auto; justify-content: flex-end; }
.view-operations .operation-description::before { display: block; margin-bottom: 3px; }
.view-imports .statement-date,
.view-imports .statement-amount { grid-column: auto; }
.view-imports .statement-date { grid-area: statement-date; }
.view-imports .statement-amount { grid-area: statement-amount; justify-content: flex-end; }
.view-imports .statement-description { grid-area: statement-description; display: block; padding-block: 8px; font-size: 12px; line-height: 1.4; text-align: left; }
.view-imports .statement-warnings { grid-area: statement-warnings; }
.view-imports .statement-decision { grid-area: statement-decision; grid-column: 1 / -1; }
.view-imports .statement-description::before { display: block; margin-bottom: 3px; }
.view-imports .statement-row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
'statement-date statement-amount'
'statement-description statement-description'
'statement-warnings statement-warnings'
'statement-decision statement-decision';
column-gap: 14px;
}
.analytics-grid { display: block; border: 0; } .analytics-grid { display: block; border: 0; }
.analytics-grid .card { padding: 20px 0; border-width: 0 0 1px; background: transparent; } .analytics-grid .card { padding: 20px 0; border-width: 0 0 1px; background: transparent; }
.hbar-row { grid-template-columns: minmax(70px, 34%) 1fr minmax(64px, auto); } .hbar-row { grid-template-columns: minmax(70px, 34%) 1fr minmax(64px, auto); }