forked from admin/family_budget
Compare commits
2 Commits
main
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c98d9c534 | |||
| 2422ca44b0 |
7
CHANGELOG.md
Normal file
7
CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## [0.8.7] - 2026-08-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Completed the frontend CSS class migration to BEM naming and aligned component styles with the design system.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@family-budget/frontend",
|
||||
"version": "0.8.6",
|
||||
"version": "0.8.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -42,22 +42,22 @@ export function AccountsList() {
|
||||
return (
|
||||
<div className="settings-section">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Банк</th>
|
||||
<th>Номер счёта</th>
|
||||
<th>Валюта</th>
|
||||
<th>Алиас</th>
|
||||
<th></th>
|
||||
<thead className="data-table__head">
|
||||
<tr className="data-table__row">
|
||||
<th className="data-table__head-cell">Банк</th>
|
||||
<th className="data-table__head-cell">Номер счёта</th>
|
||||
<th className="data-table__head-cell">Валюта</th>
|
||||
<th className="data-table__head-cell">Алиас</th>
|
||||
<th className="data-table__head-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="data-table__body">
|
||||
{accounts.map((a) => (
|
||||
<tr key={a.id}>
|
||||
<td>{a.bank}</td>
|
||||
<td>{a.accountNumberMasked}</td>
|
||||
<td>{a.currency}</td>
|
||||
<td>
|
||||
<tr className="data-table__row" key={a.id}>
|
||||
<td className="data-table__cell">{a.bank}</td>
|
||||
<td className="data-table__cell">{a.accountNumberMasked}</td>
|
||||
<td className="data-table__cell">{a.currency}</td>
|
||||
<td className="data-table__cell">
|
||||
{editingId === a.id ? (
|
||||
<input
|
||||
type="text"
|
||||
@@ -72,11 +72,11 @@ export function AccountsList() {
|
||||
/>
|
||||
) : (
|
||||
a.alias || (
|
||||
<span className="muted-text">не задан</span>
|
||||
<span className="text text--muted">не задан</span>
|
||||
)
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<td className="data-table__cell">
|
||||
{editingId === a.id ? (
|
||||
<div className="button-group">
|
||||
<button
|
||||
@@ -104,8 +104,8 @@ export function AccountsList() {
|
||||
</tr>
|
||||
))}
|
||||
{accounts.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} className="data-table__cell data-table__cell--center muted-text">
|
||||
<tr className="data-table__row">
|
||||
<td colSpan={5} className="data-table__cell data-table__cell--center text text--muted">
|
||||
Нет счетов. Импортируйте выписку.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -27,17 +27,17 @@ export function CategoriesList() {
|
||||
return (
|
||||
<div className="settings-section">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>Тип</th>
|
||||
<thead className="data-table__head">
|
||||
<tr className="data-table__row">
|
||||
<th className="data-table__head-cell">Категория</th>
|
||||
<th className="data-table__head-cell">Тип</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="data-table__body">
|
||||
{categories.map((c) => (
|
||||
<tr key={c.id}>
|
||||
<td>{c.name}</td>
|
||||
<td>
|
||||
<tr className="data-table__row" key={c.id}>
|
||||
<td className="data-table__cell">{c.name}</td>
|
||||
<td className="data-table__cell">
|
||||
<span className={`badge badge--${c.type}`}>
|
||||
{TYPE_LABELS[c.type] ?? c.type}
|
||||
</span>
|
||||
|
||||
@@ -14,9 +14,14 @@ interface Props {
|
||||
}
|
||||
|
||||
const COLORS = [
|
||||
'#2563eb', '#e85d3f', '#0f9f7f', '#d89b17', '#7c5cdb',
|
||||
'#c8558f', '#1495a3', '#79a92f', '#e06d2f', '#4f6fd7',
|
||||
'#1c9a8a', '#d43d5c', '#277bbd', '#9b59b6', '#2f9f63',
|
||||
'var(--color-chart-1)', 'var(--color-chart-2)',
|
||||
'var(--color-chart-3)', 'var(--color-chart-4)',
|
||||
'var(--color-chart-5)', 'var(--color-chart-6)',
|
||||
'var(--color-chart-7)', 'var(--color-chart-8)',
|
||||
'var(--color-chart-9)', 'var(--color-chart-10)',
|
||||
'var(--color-chart-11)', 'var(--color-chart-12)',
|
||||
'var(--color-chart-13)', 'var(--color-chart-14)',
|
||||
'var(--color-chart-15)',
|
||||
];
|
||||
|
||||
const rubFormatter = new Intl.NumberFormat('ru-RU', {
|
||||
@@ -70,18 +75,18 @@ export function CategoryChart({ data }: Props) {
|
||||
</ResponsiveContainer>
|
||||
|
||||
<table className="category-chart__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>Сумма</th>
|
||||
<th className="category-chart__cell category-chart__cell--center">Операций</th>
|
||||
<th className="category-chart__cell category-chart__cell--center">Доля</th>
|
||||
<thead className="category-chart__head">
|
||||
<tr className="category-chart__row">
|
||||
<th className="category-chart__head-cell">Категория</th>
|
||||
<th className="category-chart__head-cell">Сумма</th>
|
||||
<th className="category-chart__head-cell category-chart__head-cell--center">Операций</th>
|
||||
<th className="category-chart__head-cell category-chart__head-cell--center">Доля</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="category-chart__body">
|
||||
{data.map((item, idx) => (
|
||||
<tr key={item.categoryId}>
|
||||
<td>
|
||||
<tr className="category-chart__row" key={item.categoryId}>
|
||||
<td className="category-chart__cell">
|
||||
<span
|
||||
className="category-chart__dot"
|
||||
style={{
|
||||
@@ -91,7 +96,7 @@ export function CategoryChart({ data }: Props) {
|
||||
/>
|
||||
{item.categoryName}
|
||||
</td>
|
||||
<td>{formatAmount(item.amount)}</td>
|
||||
<td className="category-chart__cell">{formatAmount(item.amount)}</td>
|
||||
<td className="category-chart__cell category-chart__cell--center">{item.txCount}</td>
|
||||
<td className="category-chart__cell category-chart__cell--center">
|
||||
{(item.share * 100).toFixed(1)}%
|
||||
|
||||
@@ -32,14 +32,14 @@ export function ClearHistoryModal({ onClose, onDone }: Props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
className="modal"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__header">
|
||||
<h2>Очистить историю операций</h2>
|
||||
<h2 className="modal__title">Очистить историю операций</h2>
|
||||
<button className="modal__close-button" onClick={onClose} aria-label="Закрыть">
|
||||
×
|
||||
</button>
|
||||
@@ -53,8 +53,8 @@ export function ClearHistoryModal({ onClose, onDone }: Props) {
|
||||
|
||||
{error && <div className="alert alert--error">{error}</div>}
|
||||
|
||||
<div className="field field--checkbox danger-note__check">
|
||||
<label>
|
||||
<div className="field field--checkbox field--flush">
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={check1}
|
||||
@@ -64,8 +64,8 @@ export function ClearHistoryModal({ onClose, onDone }: Props) {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="field field--checkbox danger-note__check">
|
||||
<label>
|
||||
<div className="field field--checkbox field--flush">
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={check2}
|
||||
@@ -79,13 +79,13 @@ export function ClearHistoryModal({ onClose, onDone }: Props) {
|
||||
|
||||
<div className="modal__footer">
|
||||
<button
|
||||
className="button button--danger"
|
||||
className="button button--danger modal__action"
|
||||
onClick={handleConfirm}
|
||||
disabled={!canConfirm || loading}
|
||||
>
|
||||
{loading ? 'Удаление…' : 'Удалить всё'}
|
||||
</button>
|
||||
<button className="button button--secondary" onClick={onClose}>
|
||||
<button className="button button--secondary modal__action" onClick={onClose}>
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -35,36 +35,36 @@ export function DataSection() {
|
||||
return (
|
||||
<div className="data-section">
|
||||
<div className="data-section__block">
|
||||
<h3>История импортов</h3>
|
||||
<h3 className="data-section__title">История импортов</h3>
|
||||
<p className="data-section__description">
|
||||
Список импортов выписок. Можно удалить операции конкретного импорта.
|
||||
</p>
|
||||
{imports.length === 0 ? (
|
||||
<p className="muted-text">Импортов пока нет.</p>
|
||||
<p className="text text--muted">Импортов пока нет.</p>
|
||||
) : (
|
||||
<div className="table-shell">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Счёт</th>
|
||||
<th>Банк</th>
|
||||
<th>Импортировано</th>
|
||||
<th>Дубликаты</th>
|
||||
<th></th>
|
||||
<thead className="data-table__head">
|
||||
<tr className="data-table__row">
|
||||
<th className="data-table__head-cell">Дата</th>
|
||||
<th className="data-table__head-cell">Счёт</th>
|
||||
<th className="data-table__head-cell">Банк</th>
|
||||
<th className="data-table__head-cell">Импортировано</th>
|
||||
<th className="data-table__head-cell">Дубликаты</th>
|
||||
<th className="data-table__head-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="data-table__body">
|
||||
{imports.map((imp) => (
|
||||
<tr key={imp.id}>
|
||||
<td>{formatDate(imp.importedAt)}</td>
|
||||
<td>
|
||||
<tr className="data-table__row" key={imp.id}>
|
||||
<td className="data-table__cell">{formatDate(imp.importedAt)}</td>
|
||||
<td className="data-table__cell">
|
||||
{imp.accountAlias || imp.accountNumberMasked || '—'}
|
||||
</td>
|
||||
<td>{imp.bank}</td>
|
||||
<td>{imp.importedCount}</td>
|
||||
<td>{imp.duplicatesSkipped}</td>
|
||||
<td>
|
||||
<td className="data-table__cell">{imp.bank}</td>
|
||||
<td className="data-table__cell">{imp.importedCount}</td>
|
||||
<td className="data-table__cell">{imp.duplicatesSkipped}</td>
|
||||
<td className="data-table__cell">
|
||||
<button
|
||||
type="button"
|
||||
className="button button--danger button--small"
|
||||
@@ -83,7 +83,7 @@ export function DataSection() {
|
||||
</div>
|
||||
|
||||
<div className="data-section__block">
|
||||
<h3>Очистка данных</h3>
|
||||
<h3 className="data-section__title">Очистка данных</h3>
|
||||
<p className="data-section__description">
|
||||
Очистить историю операций (все транзакции). Счета, категории и
|
||||
правила сохранятся.
|
||||
|
||||
@@ -32,14 +32,14 @@ export function DeleteImportModal({ imp, onClose, onDone }: Props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
className="modal"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__header">
|
||||
<h2>Удалить импорт</h2>
|
||||
<h2 className="modal__title">Удалить импорт</h2>
|
||||
<button className="modal__close-button" onClick={onClose} aria-label="Закрыть">
|
||||
×
|
||||
</button>
|
||||
@@ -59,13 +59,13 @@ export function DeleteImportModal({ imp, onClose, onDone }: Props) {
|
||||
<div className="modal__footer">
|
||||
<button
|
||||
type="button"
|
||||
className="button button--danger"
|
||||
className="button button--danger modal__action"
|
||||
onClick={handleConfirm}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Удаление…' : 'Удалить'}
|
||||
</button>
|
||||
<button type="button" className="button button--secondary" onClick={onClose}>
|
||||
<button type="button" className="button button--secondary modal__action" onClick={onClose}>
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -83,20 +83,20 @@ export function EditTransactionModal({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
className="modal"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__header">
|
||||
<h2>Редактирование операции</h2>
|
||||
<h2 className="modal__title">Редактирование операции</h2>
|
||||
<button className="modal__close-button" onClick={onClose} aria-label="Закрыть">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<form className="form" onSubmit={handleSubmit}>
|
||||
<div className="modal__body">
|
||||
{error && <div className="alert alert--error">{error}</div>}
|
||||
|
||||
@@ -124,7 +124,7 @@ export function EditTransactionModal({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="edit-category">Категория</label>
|
||||
<label className="field__label" htmlFor="edit-category">Категория</label>
|
||||
<select
|
||||
id="edit-category"
|
||||
value={categoryId}
|
||||
@@ -140,7 +140,7 @@ export function EditTransactionModal({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="edit-comment">Комментарий</label>
|
||||
<label className="field__label" htmlFor="edit-comment">Комментарий</label>
|
||||
<textarea
|
||||
id="edit-comment"
|
||||
rows={2}
|
||||
@@ -150,10 +150,10 @@ export function EditTransactionModal({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-divider" />
|
||||
<div className="form__divider" />
|
||||
|
||||
<div className="field field--checkbox">
|
||||
<label>
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={createRule}
|
||||
@@ -166,7 +166,7 @@ export function EditTransactionModal({
|
||||
{createRule && (
|
||||
<>
|
||||
<div className="field">
|
||||
<label htmlFor="edit-pattern">
|
||||
<label className="field__label" htmlFor="edit-pattern">
|
||||
Шаблон (ключевая строка)
|
||||
</label>
|
||||
<input
|
||||
@@ -178,7 +178,7 @@ export function EditTransactionModal({
|
||||
/>
|
||||
</div>
|
||||
<div className="field field--checkbox">
|
||||
<label>
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={requiresConfirmation}
|
||||
@@ -196,14 +196,14 @@ export function EditTransactionModal({
|
||||
<div className="modal__footer">
|
||||
<button
|
||||
type="button"
|
||||
className="button button--secondary"
|
||||
className="button button--secondary modal__action"
|
||||
onClick={onClose}
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="button button--primary"
|
||||
className="button button--primary modal__action"
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? 'Сохранение...' : 'Сохранить'}
|
||||
|
||||
@@ -60,14 +60,14 @@ export function ImportModal({ onClose, onDone }: Props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
className="modal"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
}}
|
||||
>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal__header">
|
||||
<h2>Импорт выписки</h2>
|
||||
<h2 className="modal__title">Импорт выписки</h2>
|
||||
<button className="modal__close-button" onClick={onClose} aria-label="Закрыть">
|
||||
×
|
||||
</button>
|
||||
@@ -78,7 +78,9 @@ export function ImportModal({ onClose, onDone }: Props) {
|
||||
|
||||
{!result && (
|
||||
<div className="import-upload">
|
||||
<p>Выберите файл выписки (PDF или JSON, формат 1.0)</p>
|
||||
<p className="import-upload__description">
|
||||
Выберите файл выписки (PDF или JSON, формат 1.0)
|
||||
</p>
|
||||
<input
|
||||
ref={fileRef}
|
||||
type="file"
|
||||
@@ -95,35 +97,35 @@ export function ImportModal({ onClose, onDone }: Props) {
|
||||
{result && (
|
||||
<div className="import-result">
|
||||
<div className="import-result__icon" aria-hidden="true">✓</div>
|
||||
<h3>Импорт завершён</h3>
|
||||
<h3 className="import-result__title">Импорт завершён</h3>
|
||||
<table className="import-result__stats">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Счёт</td>
|
||||
<td>{result.accountNumberMasked}</td>
|
||||
<tbody className="import-result__stats-body">
|
||||
<tr className="import-result__stat-row">
|
||||
<td className="import-result__stat-cell import-result__stat-cell--label">Счёт</td>
|
||||
<td className="import-result__stat-cell import-result__stat-cell--value">{result.accountNumberMasked}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Новый счёт</td>
|
||||
<td>{result.isNewAccount ? 'Да' : 'Нет'}</td>
|
||||
<tr className="import-result__stat-row">
|
||||
<td className="import-result__stat-cell import-result__stat-cell--label">Новый счёт</td>
|
||||
<td className="import-result__stat-cell import-result__stat-cell--value">{result.isNewAccount ? 'Да' : 'Нет'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Импортировано</td>
|
||||
<td>{result.imported}</td>
|
||||
<tr className="import-result__stat-row">
|
||||
<td className="import-result__stat-cell import-result__stat-cell--label">Импортировано</td>
|
||||
<td className="import-result__stat-cell import-result__stat-cell--value">{result.imported}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Дубликатов пропущено</td>
|
||||
<td>{result.duplicatesSkipped}</td>
|
||||
<tr className="import-result__stat-row">
|
||||
<td className="import-result__stat-cell import-result__stat-cell--label">Дубликатов пропущено</td>
|
||||
<td className="import-result__stat-cell import-result__stat-cell--value">{result.duplicatesSkipped}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Всего в файле</td>
|
||||
<td>{result.totalInFile}</td>
|
||||
<tr className="import-result__stat-row">
|
||||
<td className="import-result__stat-cell import-result__stat-cell--label">Всего в файле</td>
|
||||
<td className="import-result__stat-cell import-result__stat-cell--value">{result.totalInFile}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{result.isNewAccount && !aliasSaved && (
|
||||
<div className="import-result__alias">
|
||||
<label>Алиас для нового счёта</label>
|
||||
<label className="import-result__alias-label">Алиас для нового счёта</label>
|
||||
<div className="import-result__alias-row">
|
||||
<input
|
||||
type="text"
|
||||
@@ -154,12 +156,12 @@ export function ImportModal({ onClose, onDone }: Props) {
|
||||
|
||||
<div className="modal__footer">
|
||||
{result ? (
|
||||
<button className="button button--primary" onClick={onDone}>
|
||||
<button className="button button--primary modal__action" onClick={onDone}>
|
||||
Готово
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="button button--secondary"
|
||||
className="button button--secondary modal__action"
|
||||
onClick={onClose}
|
||||
disabled={loading}
|
||||
>
|
||||
|
||||
@@ -53,7 +53,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg className="sidebar__nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
|
||||
<polyline points="14,2 14,8 20,8" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
@@ -70,7 +70,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg className="sidebar__nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="20" x2="18" y2="10" />
|
||||
<line x1="12" y1="20" x2="12" y2="4" />
|
||||
<line x1="6" y1="20" x2="6" y2="14" />
|
||||
@@ -85,7 +85,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg className="sidebar__nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z" />
|
||||
</svg>
|
||||
|
||||
@@ -55,29 +55,29 @@ export function RulesList() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-section">
|
||||
<div className="rules-list settings-section">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Шаблон</th>
|
||||
<th>Категория</th>
|
||||
<thead className="data-table__head">
|
||||
<tr className="data-table__row">
|
||||
<th className="data-table__head-cell">Шаблон</th>
|
||||
<th className="data-table__head-cell">Категория</th>
|
||||
<th className="data-table__head-cell data-table__head-cell--center">Приоритет</th>
|
||||
<th className="data-table__head-cell data-table__head-cell--center">Подтверждение</th>
|
||||
<th>Создано</th>
|
||||
<th className="data-table__head-cell">Создано</th>
|
||||
<th className="data-table__head-cell data-table__head-cell--center">Активно</th>
|
||||
<th></th>
|
||||
<th className="data-table__head-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="data-table__body">
|
||||
{rules.map((r) => (
|
||||
<tr
|
||||
key={r.id}
|
||||
className={!r.isActive ? 'data-table__row--inactive' : ''}
|
||||
className={`data-table__row ${!r.isActive ? 'data-table__row--inactive' : ''}`}
|
||||
>
|
||||
<td>
|
||||
<td className="data-table__cell">
|
||||
<code>{r.pattern}</code>
|
||||
</td>
|
||||
<td>{r.categoryName}</td>
|
||||
<td className="data-table__cell">{r.categoryName}</td>
|
||||
<td className="data-table__cell data-table__cell--center">{r.priority}</td>
|
||||
<td className="data-table__cell data-table__cell--center">
|
||||
{r.requiresConfirmation ? 'Да' : 'Нет'}
|
||||
@@ -99,7 +99,7 @@ export function RulesList() {
|
||||
{r.isActive ? 'Вкл' : 'Выкл'}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<td className="data-table__cell">
|
||||
<div className="rules-list__actions">
|
||||
{r.isActive && (
|
||||
<button
|
||||
@@ -120,8 +120,8 @@ export function RulesList() {
|
||||
</tr>
|
||||
))}
|
||||
{rules.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={7} className="data-table__cell data-table__cell--center muted-text">
|
||||
<tr className="data-table__row">
|
||||
<td colSpan={7} className="data-table__cell data-table__cell--center text text--muted">
|
||||
Нет правил
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function TimeseriesChart({ data }: Props) {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={chartHeight}>
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e7ded2" vertical={false} />
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="period"
|
||||
tickFormatter={(v: string) => {
|
||||
@@ -47,7 +47,7 @@ export function TimeseriesChart({ data }: Props) {
|
||||
return `${d.getDate()}.${String(d.getMonth() + 1).padStart(2, '0')}`;
|
||||
}}
|
||||
fontSize={12}
|
||||
stroke="#7d7164"
|
||||
stroke="var(--color-text-secondary)"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
@@ -56,33 +56,33 @@ export function TimeseriesChart({ data }: Props) {
|
||||
v >= 1000 ? `${(v / 1000).toFixed(0)}к` : String(v)
|
||||
}
|
||||
fontSize={12}
|
||||
stroke="#7d7164"
|
||||
stroke="var(--color-text-secondary)"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number) => rubFormatter.format(value)}
|
||||
cursor={{ fill: 'rgba(129, 93, 58, 0.08)' }}
|
||||
cursor={{ fill: 'var(--color-chart-cursor)' }}
|
||||
contentStyle={{
|
||||
border: '1px solid #e7ded2',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 16px 36px rgba(54, 42, 30, 0.14)',
|
||||
boxShadow: 'var(--shadow-lg)',
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Bar
|
||||
dataKey="Расходы"
|
||||
fill="#e85d3f"
|
||||
fill="var(--color-danger)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="Доходы"
|
||||
fill="#0f9f7f"
|
||||
fill="var(--color-success)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="Инвестиции"
|
||||
fill="#d89b17"
|
||||
fill="var(--color-warning)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
|
||||
@@ -118,7 +118,7 @@ export function TransactionFilters({
|
||||
<div className="filters">
|
||||
<div className="filters__row">
|
||||
<div className="field field--period">
|
||||
<label>Период</label>
|
||||
<label className="field__label">Период</label>
|
||||
<div className="filters__date-control">
|
||||
{filters.periodMode !== 'custom' && (
|
||||
<button
|
||||
@@ -179,7 +179,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Счёт</label>
|
||||
<label className="field__label">Счёт</label>
|
||||
<select
|
||||
value={filters.accountId}
|
||||
onChange={(e) => set('accountId', e.target.value)}
|
||||
@@ -194,7 +194,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Тип</label>
|
||||
<label className="field__label">Тип</label>
|
||||
<select
|
||||
value={filters.direction}
|
||||
onChange={(e) => set('direction', e.target.value)}
|
||||
@@ -207,7 +207,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Категория</label>
|
||||
<label className="field__label">Категория</label>
|
||||
<select
|
||||
value={filters.categoryId}
|
||||
onChange={(e) => set('categoryId', e.target.value)}
|
||||
@@ -224,7 +224,7 @@ export function TransactionFilters({
|
||||
|
||||
<div className="filters__row">
|
||||
<div className="field field--wide">
|
||||
<label>Поиск</label>
|
||||
<label className="field__label">Поиск</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Поиск по описанию..."
|
||||
@@ -234,7 +234,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Сумма от (₽)</label>
|
||||
<label className="field__label">Сумма от (₽)</label>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="мин"
|
||||
@@ -244,7 +244,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Сумма до (₽)</label>
|
||||
<label className="field__label">Сумма до (₽)</label>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="макс"
|
||||
@@ -254,7 +254,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field field--checkbox">
|
||||
<label>
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filters.onlyUnconfirmed}
|
||||
@@ -265,7 +265,7 @@ export function TransactionFilters({
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Сортировка</label>
|
||||
<label className="field__label">Сортировка</label>
|
||||
<div className="filters__sort">
|
||||
<select
|
||||
value={filters.sortBy}
|
||||
|
||||
@@ -102,25 +102,25 @@ export function TransactionTable({ transactions, loading, onEdit }: Props) {
|
||||
<>
|
||||
<div className="table-shell table-shell--desktop">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Счёт</th>
|
||||
<th>Сумма</th>
|
||||
<th>Описание</th>
|
||||
<th>Категория</th>
|
||||
<thead className="data-table__head">
|
||||
<tr className="data-table__row">
|
||||
<th className="data-table__head-cell">Дата</th>
|
||||
<th className="data-table__head-cell">Счёт</th>
|
||||
<th className="data-table__head-cell">Сумма</th>
|
||||
<th className="data-table__head-cell">Описание</th>
|
||||
<th className="data-table__head-cell">Категория</th>
|
||||
<th className="data-table__head-cell data-table__head-cell--center">Статус</th>
|
||||
<th></th>
|
||||
<th className="data-table__head-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="data-table__body">
|
||||
{transactions.map((tx) => (
|
||||
<tr
|
||||
key={tx.id}
|
||||
className={
|
||||
!tx.isCategoryConfirmed && tx.categoryId
|
||||
? 'data-table__row--unconfirmed'
|
||||
: ''
|
||||
? 'data-table__row data-table__row--unconfirmed'
|
||||
: 'data-table__row'
|
||||
}
|
||||
>
|
||||
<td className="data-table__cell data-table__cell--nowrap">
|
||||
@@ -149,7 +149,7 @@ export function TransactionTable({ transactions, loading, onEdit }: Props) {
|
||||
</td>
|
||||
<td className="data-table__cell data-table__cell--nowrap">
|
||||
{tx.categoryName || (
|
||||
<span className="muted-text">—</span>
|
||||
<span className="text text--muted">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="data-table__cell data-table__cell--center">
|
||||
@@ -162,7 +162,7 @@ export function TransactionTable({ transactions, loading, onEdit }: Props) {
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<td className="data-table__cell">
|
||||
<button
|
||||
className="icon-button"
|
||||
onClick={() => onEdit(tx)}
|
||||
|
||||
@@ -99,7 +99,7 @@ export function AnalyticsPage() {
|
||||
<PeriodSelector period={period} onChange={setPeriod} />
|
||||
<div className="analytics-panel__filters">
|
||||
<div className="field">
|
||||
<label>Счёт</label>
|
||||
<label className="field__label">Счёт</label>
|
||||
<select
|
||||
value={accountId}
|
||||
onChange={(e) => setAccountId(e.target.value)}
|
||||
@@ -113,7 +113,7 @@ export function AnalyticsPage() {
|
||||
</select>
|
||||
</div>
|
||||
<div className="field field--checkbox">
|
||||
<label>
|
||||
<label className="field__label field__label--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlyConfirmed}
|
||||
@@ -132,11 +132,11 @@ export function AnalyticsPage() {
|
||||
{summary && <SummaryCards summary={summary} />}
|
||||
<div className="analytics-grid">
|
||||
<div className="chart-card">
|
||||
<h3>Динамика</h3>
|
||||
<h3 className="chart-card__title">Динамика</h3>
|
||||
<TimeseriesChart data={timeseries} />
|
||||
</div>
|
||||
<div className="chart-card">
|
||||
<h3>По категориям</h3>
|
||||
<h3 className="chart-card__title">По категориям</h3>
|
||||
<CategoryChart data={byCategory} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -210,7 +210,7 @@ export function HistoryPage() {
|
||||
<h1 className="page__title">История операций</h1>
|
||||
</div>
|
||||
<button
|
||||
className="button button--primary"
|
||||
className="button button--primary page__action"
|
||||
onClick={() => setShowImport(true)}
|
||||
>
|
||||
<svg className="button__icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
|
||||
@@ -24,13 +24,13 @@ export function LoginPage() {
|
||||
<div className="login__panel">
|
||||
<div className="login__header">
|
||||
<span className="login__icon">₽</span>
|
||||
<h1>Семейный бюджет</h1>
|
||||
<p>Войдите для продолжения</p>
|
||||
<h1 className="login__title">Семейный бюджет</h1>
|
||||
<p className="login__subtitle">Войдите для продолжения</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="login__form">
|
||||
<form onSubmit={handleSubmit} className="form login__form">
|
||||
{error && <div className="alert alert--error">{error}</div>}
|
||||
<div className="field">
|
||||
<label htmlFor="login">Логин</label>
|
||||
<label className="field__label" htmlFor="login">Логин</label>
|
||||
<input
|
||||
id="login"
|
||||
type="text"
|
||||
@@ -42,7 +42,7 @@ export function LoginPage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="password">Пароль</label>
|
||||
<label className="field__label" htmlFor="password">Пароль</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
|
||||
@@ -31,6 +31,22 @@
|
||||
--color-warning-soft: #fff6da;
|
||||
--color-transfer: #7c5cdb;
|
||||
--color-transfer-soft: #f0ebff;
|
||||
--color-chart-1: var(--color-primary);
|
||||
--color-chart-2: var(--color-danger);
|
||||
--color-chart-3: var(--color-success);
|
||||
--color-chart-4: var(--color-warning);
|
||||
--color-chart-5: var(--color-transfer);
|
||||
--color-chart-6: #c8558f;
|
||||
--color-chart-7: #1495a3;
|
||||
--color-chart-8: #79a92f;
|
||||
--color-chart-9: #e06d2f;
|
||||
--color-chart-10: #4f6fd7;
|
||||
--color-chart-11: #1c9a8a;
|
||||
--color-chart-12: #d43d5c;
|
||||
--color-chart-13: #277bbd;
|
||||
--color-chart-14: #9b59b6;
|
||||
--color-chart-15: #2f9f63;
|
||||
--color-chart-cursor: rgba(129, 93, 58, 0.08);
|
||||
--color-sidebar: #fff9f0;
|
||||
--radius-sm: 4px;
|
||||
--radius: 8px;
|
||||
@@ -182,7 +198,7 @@ button {
|
||||
box-shadow var(--transition);
|
||||
}
|
||||
|
||||
.sidebar__nav-link svg {
|
||||
.sidebar__nav-icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@@ -315,7 +331,7 @@ button {
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.muted-text {
|
||||
.text--muted {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
@@ -344,13 +360,13 @@ button {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.field label {
|
||||
.field__label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.field--checkbox label {
|
||||
.field__label--checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -594,7 +610,7 @@ input[type="checkbox"] {
|
||||
color: #b8321f;
|
||||
}
|
||||
|
||||
.form-divider {
|
||||
.form__divider {
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
margin: 4px 0;
|
||||
@@ -644,12 +660,12 @@ input[type="checkbox"] {
|
||||
box-shadow: 0 18px 34px rgba(232, 93, 63, 0.22);
|
||||
}
|
||||
|
||||
.login__header h1 {
|
||||
.login__title {
|
||||
font-size: 24px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.login__header p {
|
||||
.login__subtitle {
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -836,7 +852,7 @@ input[type="checkbox"] {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chart-card h3 {
|
||||
.chart-card__title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
@@ -853,19 +869,20 @@ input[type="checkbox"] {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.category-chart__table th,
|
||||
.category-chart__table td {
|
||||
.category-chart__head-cell,
|
||||
.category-chart__cell {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 7px 9px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.category-chart__table th {
|
||||
.category-chart__head-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.category-chart__head-cell--center,
|
||||
.category-chart__cell--center {
|
||||
text-align: center !important;
|
||||
}
|
||||
@@ -897,15 +914,14 @@ input[type="checkbox"] {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
.data-table__head-cell,
|
||||
.data-table__cell {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 11px 14px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table__head-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 12px;
|
||||
@@ -915,15 +931,15 @@ input[type="checkbox"] {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-table tbody tr {
|
||||
.data-table__row {
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
.data-table__body .data-table__row:hover {
|
||||
background: rgba(247, 242, 235, 0.72);
|
||||
}
|
||||
|
||||
.data-table tbody tr:last-child td {
|
||||
.data-table__body .data-table__row:last-child .data-table__cell {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -1128,7 +1144,7 @@ input[type="checkbox"] {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.data-section__block h3 {
|
||||
.data-section__title {
|
||||
margin-bottom: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
@@ -1177,7 +1193,7 @@ input[type="checkbox"] {
|
||||
Modals and import
|
||||
================================================================ */
|
||||
|
||||
.modal-backdrop {
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
@@ -1188,7 +1204,7 @@ input[type="checkbox"] {
|
||||
background: rgba(35, 27, 21, 0.44);
|
||||
}
|
||||
|
||||
.modal {
|
||||
.modal__dialog {
|
||||
width: min(100%, 540px);
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
@@ -1206,7 +1222,7 @@ input[type="checkbox"] {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.modal__header h2 {
|
||||
.modal__title {
|
||||
font-size: 18px;
|
||||
font-weight: 850;
|
||||
}
|
||||
@@ -1281,7 +1297,7 @@ input[type="checkbox"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.import-upload p {
|
||||
.import-upload__description {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -1314,7 +1330,7 @@ input[type="checkbox"] {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.import-result h3 {
|
||||
.import-result__title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 17px;
|
||||
font-weight: 850;
|
||||
@@ -1326,16 +1342,16 @@ input[type="checkbox"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.import-result__stats td {
|
||||
.import-result__stat-cell {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.import-result__stats td:first-child {
|
||||
.import-result__stat-cell--label {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.import-result__stats td:last-child {
|
||||
.import-result__stat-cell--value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 850;
|
||||
text-align: right;
|
||||
@@ -1346,7 +1362,7 @@ input[type="checkbox"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.import-result__alias label {
|
||||
.import-result__alias-label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--color-text-secondary);
|
||||
@@ -1374,7 +1390,7 @@ input[type="checkbox"] {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.danger-note__check {
|
||||
.field--flush {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -1436,7 +1452,7 @@ input[type="checkbox"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page__header .button {
|
||||
.page__action {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1504,12 +1520,12 @@ input[type="checkbox"] {
|
||||
padding: 28px 22px;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
.modal {
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
.modal__dialog {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
@@ -1527,7 +1543,7 @@ input[type="checkbox"] {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.modal__footer .button {
|
||||
.modal__action {
|
||||
flex: 1 1 150px;
|
||||
}
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -64,7 +64,7 @@
|
||||
},
|
||||
"frontend": {
|
||||
"name": "@family-budget/frontend",
|
||||
"version": "0.8.6",
|
||||
"version": "0.8.7",
|
||||
"dependencies": {
|
||||
"@family-budget/shared": "*",
|
||||
"react": "^19.0.0",
|
||||
|
||||
Reference in New Issue
Block a user