Bicycle/App/view/Transactions/index.html
2026-08-15 19:40:29 +03:00

221 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @description Transactions/index.html — список последних 50 транзакций + модалка ручного
* ввода (создание/правка), см. App/Controller/TransactionsController.php,
* App/media/js/transactions.js.
*
* @var array $user
* @var object[] $transactions
* @var object[] $accounts Активные счета пользователя (для селектов счёта списания/зачисления)
* @var object[] $categories Активные статьи пользователя (для селекта категории, income/expense)
*/
use System\Classes\CSRF;
$month_names_gen = [
1 => 'января', 2 => 'февраля', 3 => 'марта', 4 => 'апреля', 5 => 'мая', 6 => 'июня',
7 => 'июля', 8 => 'августа', 9 => 'сентября', 10 => 'октября', 11 => 'ноября', 12 => 'декабря',
];
// В дизайне доход подсвечен зелёным, а расход — обычным текстом (не красным); тут же приводим
// зелёный к общему токену --positive-text вместо отдельного литерала oklch(...).
$type_colors = [
'income' => 'var(--positive-text)',
'expense' => 'var(--ink)',
'transfer' => 'var(--muted)',
];
$type_signs = [
'income' => '+',
'expense' => '−',
'transfer' => '',
];
$this->setScript('transactions.js');
?>
<svg width="0" height="0" style="position:absolute">
<defs>
<symbol id="ic-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/>
<line x1="5" y1="12" x2="19" y2="12"/>
</symbol>
<symbol id="ic-dots" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="1.6"/>
<circle cx="12" cy="12" r="1.6"/>
<circle cx="12" cy="19" r="1.6"/>
</symbol>
<symbol id="ic-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9"/>
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/>
</symbol>
<symbol id="ic-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
</symbol>
<symbol id="ic-swap" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 7h11l-3-3"/>
<path d="M17 17H6l3 3"/>
</symbol>
<symbol id="ic-arrow-down-circle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M12 8v8"/>
<path d="M9 13l3 3 3-3"/>
</symbol>
<symbol id="ic-arrow-up-circle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M12 16V8"/>
<path d="M9 11l3-3 3 3"/>
</symbol>
</defs>
</svg>
<div class="section-label" style="margin-top:0">
<span>Транзакции</span>
<div style="display:flex;gap:0.5rem">
<button type="button" class="btn-add js-tx-add" title="Добавить операцию"><svg class="ico"><use href="#ic-plus"/></svg></button>
</div>
</div>
<?php if (empty($transactions)): ?>
<div class="empty-state">Нет операций — нажмите «+», чтобы добавить первую</div>
<?php else: ?>
<div class="card elev-md pm-wrap" style="padding:6px 0">
<?php foreach ($transactions as $tx): ?>
<?php
$d = new DateTime($tx->date);
$dateLabel = (int)$d->format('j') . ' ' . $month_names_gen[(int)$d->format('n')];
$amount = (float)$tx->summa * ((float)($tx->rate_to_rub ?? 1));
$color = $type_colors[$tx->type] ?? 'var(--muted)';
$sign = $type_signs[$tx->type] ?? '';
$title = match (true) {
$tx->type === 'transfer' => htmlspecialchars(($tx->account_f_title ?? '') . ' → ' . ($tx->account_in_title ?? ''), ENT_QUOTES, 'UTF-8'),
default => htmlspecialchars($tx->category_title ?? '—', ENT_QUOTES, 'UTF-8'),
};
$account = $tx->type === 'income' ? $tx->account_in_title : $tx->account_f_title;
?>
<div class="stat-row"
data-id="<?= (int)$tx->id ?>"
data-type="<?= htmlspecialchars($tx->type, ENT_QUOTES, 'UTF-8') ?>"
data-date="<?= htmlspecialchars($tx->date, ENT_QUOTES, 'UTF-8') ?>"
data-summa="<?= htmlspecialchars((string)$tx->summa, ENT_QUOTES, 'UTF-8') ?>"
data-categorie-id="<?= (int)($tx->categorie_id ?? 0) ?>"
data-account-f-id="<?= (int)($tx->account_f_id ?? 0) ?>"
data-account-in-id="<?= (int)($tx->account_in_id ?? 0) ?>">
<div style="width:auto;flex:1">
<div style="font-size:13px;font-weight:600"><?= $title ?></div>
<div class="card-meta" style="font-size:11px;margin-top:2px">
<?= htmlspecialchars($dateLabel, ENT_QUOTES, 'UTF-8') ?>
<?php if ($account): ?> · <?= htmlspecialchars($account, ENT_QUOTES, 'UTF-8') ?><?php endif; ?>
</div>
</div>
<div class="num" style="width:auto;flex-shrink:0;font-size:14px;font-weight:600;color:<?= $color ?>">
<?= $sign . number_format($amount, 0, ',', ' ') ?> ₽
</div>
<div class="room-menu" style="flex-shrink:0;margin-left:6px">
<button type="button" class="room-menu-btn">
<svg class="ico"><use href="#ic-dots"/></svg>
</button>
<div class="room-menu-list">
<a href="#" class="js-tx-edit"><svg class="ico ico-sm"><use href="#ic-pencil"/></svg>Редактировать</a>
<a href="#" class="danger js-tx-delete"><svg class="ico ico-sm"><use href="#ic-trash"/></svg>Удалить</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="modal fade" id="tx-modal" tabindex="-1" data-bs-theme="dark">
<div class="modal-dialog modal-dialog-scrollable" style="max-width:480px">
<div class="modal-content" style="background:var(--surface);color:var(--ink);border-radius:var(--radius-lg)">
<div class="modal-header" style="border-color:var(--surface-3);align-items:flex-start">
<div style="display:flex;gap:12px;align-items:flex-start">
<span class="dialog-icon"><svg class="ico" style="width:1.3em;height:1.3em"><use href="#ic-swap"/></svg></span>
<div>
<h5 class="modal-title" id="tx-modal-title">Новая операция</h5>
<div class="dialog-sub">Доход, расход или перевод между счетами</div>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<form id="tx-form" method="post">
<?= CSRF::field() ?>
<input type="hidden" name="id">
<div class="modal-body">
<div id="tx-form-error" class="alert-error" style="display:none"></div>
<div class="dialog-section-label" style="margin-top:0">Тип операции</div>
<div class="pm-tiles-grid" id="tx-type-tiles" style="grid-template-columns:1fr 1fr 1fr">
<div class="pm-tile selected" data-type="expense">
<span class="pm-check">✓</span>
<span class="pm-tile-icon" style="background:color-mix(in srgb,var(--red) 20%,transparent);color:var(--red)"><svg class="ico" style="width:1.1em;height:1.1em"><use href="#ic-arrow-up-circle"/></svg></span>
<div><div class="pm-tile-title">Расход</div></div>
</div>
<div class="pm-tile" data-type="income">
<span class="pm-check">✓</span>
<span class="pm-tile-icon" style="background:color-mix(in srgb,var(--positive) 20%,transparent);color:var(--positive)"><svg class="ico" style="width:1.1em;height:1.1em"><use href="#ic-arrow-down-circle"/></svg></span>
<div><div class="pm-tile-title">Доход</div></div>
</div>
<div class="pm-tile" data-type="transfer">
<span class="pm-check">✓</span>
<span class="pm-tile-icon" style="background:color-mix(in srgb,var(--blue) 20%,transparent);color:var(--blue)"><svg class="ico" style="width:1.1em;height:1.1em"><use href="#ic-swap"/></svg></span>
<div><div class="pm-tile-title">Перевод</div></div>
</div>
</div>
<input type="hidden" name="type" id="tx-type-hidden" value="expense">
<div class="field" id="tx-category-field" style="margin-top:10px">
<label>Статья</label>
<select class="input" name="categorie_id" id="tx-category">
<option value="">— выберите статью —</option>
<?php foreach ($categories as $c): ?>
<option value="<?= (int)$c->id ?>" data-type="<?= htmlspecialchars($c->type, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($c->title, ENT_QUOTES, 'UTF-8') ?></option>
<?php endforeach; ?>
</select>
</div>
<div style="display:flex;gap:10px;margin-top:10px">
<div class="field" style="flex:1;margin:0">
<label>Дата</label>
<input class="input" type="date" name="date" required>
</div>
<div class="field" style="flex:1;margin:0">
<label>Сумма</label>
<input class="input" type="number" step="0.01" min="0.01" name="summa" required>
</div>
</div>
<div class="field" id="tx-account-f-field" style="margin-top:10px">
<label>Счёт списания</label>
<select class="input" name="account_f_id" id="tx-account-f">
<option value="">— выберите счёт —</option>
<?php foreach ($accounts as $a): ?>
<option value="<?= (int)$a->id ?>"><?= htmlspecialchars($a->title, ENT_QUOTES, 'UTF-8') ?> (<?= htmlspecialchars($a->currency, ENT_QUOTES, 'UTF-8') ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="field" id="tx-account-in-field" style="margin-top:10px">
<label>Счёт зачисления</label>
<select class="input" name="account_in_id" id="tx-account-in">
<option value="">— выберите счёт —</option>
<?php foreach ($accounts as $a): ?>
<option value="<?= (int)$a->id ?>"><?= htmlspecialchars($a->title, ENT_QUOTES, 'UTF-8') ?> (<?= htmlspecialchars($a->currency, ENT_QUOTES, 'UTF-8') ?>)</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="modal-footer" style="border-color:var(--surface-3)">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
</div>