5.12 (livrat): editare in modal a randurilor de preview, cont obligatoriu inainte de import, formular editare extras (_form_editare, _editare_preview_modal), plus suita de teste aferenta (preview edit/compact, mapare op, form editare, signup, admin panel). Design + planificare: - docs/design.md: sistem de design (tokeni, breakpoints, scara control, componente, a11y). - docs/prd/prd-5.12-* si prd-5.13-* (5.13 cu raport /autoplan: CEO+Design+Eng, audit trail). Curatare: sterse PNG-urile de test/mockup temporare din radacina. Nota: implementarea CSS 5.13 (responsive compact + sistem butoane) NU e inca facuta — planul revizuit cere refactorul testelor fragile din test_web_responsive.py INAINTE de CSS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
6.6 KiB
HTML
147 lines
6.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Conturi clienti — Gateway RAR AUTOPASS{% endblock %}
|
|
{% block content %}
|
|
|
|
{# Metadate verbe de ciclu de viata (eticheta, ruta, clasa). #}
|
|
{% set VERBS = {
|
|
'activate': ('Activeaza', '/admin/activate', ''),
|
|
'block': ('Blocheaza', '/admin/block', ''),
|
|
'archive': ('Arhiveaza', '/admin/archive', ''),
|
|
'delete': ('Sterge', '/admin/delete', 'danger')
|
|
} %}
|
|
|
|
{% macro lifecycle_block(title, rows, block_id, bulk_verbs, row_verbs) %}
|
|
<div class="card">
|
|
<h3 style="margin-top:0;">{{ title }} ({{ rows|length }})</h3>
|
|
{% if rows %}
|
|
{# Bara bulk: form propriu (id=bulk-<block>); checkbox-urile randurilor se leaga prin atributul
|
|
HTML5 form= (fara form-uri imbricate). Ascunsa pana exista o selectie (JS). #}
|
|
<form id="bulk-{{ block_id }}" method="post" class="bulk-form" data-block="{{ block_id }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<div class="bulk-bar" hidden>
|
|
<span class="bulk-count muted" style="font-size:13px;">0 selectate</span>
|
|
{% for v in bulk_verbs %}
|
|
{% set label, action, cls = VERBS[v] %}
|
|
<button type="submit" formaction="{{ action }}"
|
|
{% if v == 'delete' %}onclick="return confirm('Stergi conturile selectate? (stergere soft, datele se purjeaza)');"{% endif %}
|
|
style="{% if cls == 'danger' %}background:var(--card); color:var(--err); border-color:var(--err);{% endif %}">{{ label }}</button>
|
|
{% endfor %}
|
|
</div>
|
|
</form>
|
|
|
|
<div class="tablewrap">
|
|
<table>
|
|
<thead><tr>
|
|
<th style="width:28px;"><input type="checkbox" class="master-check" data-block="{{ block_id }}"
|
|
aria-label="Selecteaza tot"></th>
|
|
<th>ID</th><th>Companie</th><th>CUI</th><th>Email</th><th>Stare</th><th>Inregistrat</th><th>Actiuni</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for acct in rows %}
|
|
<tr>
|
|
<td><input type="checkbox" name="account_id" value="{{ acct.id }}" form="bulk-{{ block_id }}"
|
|
class="row-check" data-block="{{ block_id }}"
|
|
aria-label="Selecteaza contul {{ acct.name }}"></td>
|
|
<td class="muted">{{ acct.id }}</td>
|
|
<td>{{ acct.name }}</td>
|
|
<td class="muted">{{ acct.cui or "—" }}</td>
|
|
<td>{{ acct.email or "—" }}</td>
|
|
<td><span class="pill">{{ acct.status }}</span></td>
|
|
<td class="muted">{{ acct.created_at or "—" }}</td>
|
|
<td style="white-space:nowrap;">
|
|
<details class="kebab">
|
|
<summary aria-label="Actiuni pentru {{ acct.name }}">⋯</summary>
|
|
<div class="kebab-menu">
|
|
{% for v in row_verbs %}
|
|
{% set label, action, cls = VERBS[v] %}
|
|
{# Confirm fara nume interpolat: un apostrof in numele firmei (free-form) ar rupe
|
|
string-ul JS din atributul inline (entitatea ' e decodata inainte de parse). #}
|
|
<form method="post" action="{{ action }}"
|
|
{% if v == 'delete' %}onsubmit="return confirm('Stergi acest cont? (stergere soft)');"{% endif %}>
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="account_id" value="{{ acct.id }}">
|
|
{% if v == 'activate' and not acct.is_complete %}
|
|
<button type="submit"{% if cls == 'danger' %} class="danger"{% endif %}
|
|
disabled
|
|
title="Completeaza datele firmei (companie + email + CUI) inainte de activare">{{ label }}</button>
|
|
{% else %}
|
|
<button type="submit"{% if cls == 'danger' %} class="danger"{% endif %}>{{ label }}</button>
|
|
{% endif %}
|
|
</form>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="empty">Niciun cont.</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
<style>
|
|
/* Bara de actiuni bulk — ascunsa pana exista selectie. `[hidden]` trebuie sa invinga
|
|
display-ul, deci stilul sta in CSS (NU inline cu display:flex, care ar invinge [hidden]). */
|
|
.bulk-bar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:10px;
|
|
padding:8px 10px; border:1px solid var(--line); border-radius:8px;
|
|
background:color-mix(in srgb, var(--accent) 8%, var(--card)); }
|
|
.bulk-bar[hidden] { display:none; }
|
|
/* Kebab per-rand: stiluri partajate in base.html (position:fixed, anti-clipping tablewrap). */
|
|
</style>
|
|
|
|
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px;">
|
|
<h2 style="margin:0;">Conturi clienti</h2>
|
|
<a href="/" class="cardlink muted">Inapoi la dashboard</a>
|
|
</div>
|
|
|
|
{% if error %}
|
|
<div class="banner" style="margin-bottom:16px;padding:10px 14px;">{{ error }}</div>
|
|
{% endif %}
|
|
|
|
{{ lifecycle_block("Conturi in asteptare", pending, "pending",
|
|
['activate', 'block', 'archive', 'delete'],
|
|
['activate', 'block', 'archive', 'delete']) }}
|
|
|
|
{{ lifecycle_block("Conturi active", active, "active",
|
|
['block', 'archive', 'delete'],
|
|
['block', 'archive', 'delete']) }}
|
|
|
|
{# Conturi suspendate (blocate/arhivate): reactivare sau stergere. Stare reala in pill. #}
|
|
{{ lifecycle_block("Conturi blocate / arhivate", suspended, "suspended",
|
|
['activate', 'delete'],
|
|
['activate', 'delete']) }}
|
|
|
|
<script>
|
|
(function() {
|
|
// Selectie + bara bulk, scoped pe fiecare bloc (pending/active) prin data-block.
|
|
document.querySelectorAll('.master-check').forEach(function(master) {
|
|
var block = master.getAttribute('data-block');
|
|
var rows = Array.prototype.slice.call(
|
|
document.querySelectorAll('.row-check[data-block="' + block + '"]'));
|
|
var form = document.getElementById('bulk-' + block);
|
|
var bar = form ? form.querySelector('.bulk-bar') : null;
|
|
var count = form ? form.querySelector('.bulk-count') : null;
|
|
|
|
function refresh() {
|
|
var n = rows.filter(function(r) { return r.checked; }).length;
|
|
if (bar) bar.hidden = (n === 0);
|
|
if (count) count.textContent = n + ' selectate';
|
|
master.checked = (n > 0 && n === rows.length);
|
|
master.indeterminate = (n > 0 && n < rows.length);
|
|
}
|
|
master.addEventListener('change', function() {
|
|
rows.forEach(function(r) { r.checked = master.checked; });
|
|
refresh();
|
|
});
|
|
rows.forEach(function(r) { r.addEventListener('change', refresh); });
|
|
refresh();
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
{% endblock %}
|