221 lines
13 KiB
HTML
221 lines
13 KiB
HTML
<?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>
|