fix(web): kebab anti-clipping partajat + panou admin redenumit + tabel mapari compact

- "Panou admin" -> "Conturi clienti" (titlu, antet, link meniu hamburger)
- Kebab actiuni mutat in component partajat (base.html) cu position:fixed
  pozitionat din JS: .tablewrap{overflow-x:auto} inducea overflow-y:auto care
  taia dropdown-ul pe ultimul rand (meniul admin nu se vedea). Sters CSS local.
- Mapari salvate: Salveaza/Sterge mutate in kebab (legate prin form=); coloana
  "In coada" doar checkbox (macro autosend_toggle compact, semantica de prezenta
  pastrata); select cod RAR limitat la 240px -> tabelul incape fara scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-23 12:30:56 +00:00
parent 36ec50d667
commit e3f295f912
5 changed files with 91 additions and 32 deletions

View File

@@ -13,16 +13,15 @@
Manual<->Auto peste checkbox, NU doua radio-uri. Zero atingere backend.
- form_id: leaga input-ul de un <form> extern (necesar in celulele de tabel).
- checked: starea STOCATA per mapare (H4) — bifat = Auto. #}
{% macro autosend_toggle(form_id='', checked=True) -%}
{% macro autosend_toggle(form_id='', checked=True, label='') -%}
<label class="autosend-toggle"
title="Auto = pune automat in coada la fisierele viitoare cu aceasta operatie. Manual = tine pentru verificare; nimic nu pleaca la RAR pana confirmi."
style="display:inline-flex; align-items:center; gap:6px; white-space:nowrap; min-height:36px; cursor:pointer; font-size:13px;">
<span class="muted">Manual</span>
title="Bifat = Auto: pune automat in coada la fisierele viitoare cu aceasta operatie. Nebifat = Manual: tine pentru verificare; nimic nu pleaca la RAR pana confirmi."
style="display:inline-flex; align-items:center; justify-content:center; gap:8px; min-height:36px; cursor:pointer;">
{%- if label %}<span class="muted" style="font-size:13px;">{{ label }}</span>{% endif %}
<input type="checkbox" name="auto_send" value="true"
{%- if form_id %} form="{{ form_id }}"{% endif %}
{%- if checked %} checked{% endif %}
aria-label="In coada: Auto (bifat) sau Manual (nebifat), pentru aceasta operatie"
style="width:32px; height:18px; cursor:pointer; accent-color:var(--accent);">
<span><strong>Auto</strong></span>
aria-label="In coada automat (Auto) pentru aceasta operatie"
style="width:18px; height:18px; cursor:pointer; accent-color:var(--accent);">
</label>
{%- endmacro %}

View File

@@ -1,5 +1,10 @@
{% import '_macros.html' as ui %}
<div id="mapari-section">
<style>
/* Selectul de cod RAR e principalul vinovat de latimea tabelelor de mapari. Il limitam ca
tabelul sa incapa in card fara scroll orizontal -> coloana Actiuni (kebab) ramane vizibila. */
#mapari-section td select { width:100%; max-width:240px; min-width:150px; }
</style>
{% if message %}
<div class="flash" style="margin-bottom:12px;">{{ message }}</div>
@@ -139,12 +144,16 @@
<td>
{{ ui.autosend_toggle(form_id="map-salv-" ~ loop.index, checked=m.auto_send) }}
</td>
<td style="white-space:nowrap;">
<button type="submit" form="map-salv-{{ loop.index }}">Salveaza</button>
<button type="submit" form="map-del-{{ loop.index }}"
style="background:var(--card); color:var(--err); border-color:var(--err);">
Sterge
</button>
<td style="text-align:right; white-space:nowrap;">
{# Salveaza/Sterge in meniu contextual (kebab) — randul ramane ingust. Butoanele se
leaga prin form= de cele doua form-uri hx-post definite in prima celula a randului. #}
<details class="kebab">
<summary aria-label="Actiuni pentru {{ m.cod_op_service }}">&#8943;</summary>
<div class="kebab-menu">
<button type="submit" form="map-salv-{{ loop.index }}">Salveaza</button>
<button type="submit" form="map-del-{{ loop.index }}" class="danger">Sterge</button>
</div>
</details>
</td>
</tr>
{% endfor %}

View File

@@ -97,7 +97,7 @@
</select>
</div>
<div class="mapcol">
{{ ui.autosend_toggle(checked=True) }}
{{ ui.autosend_toggle(checked=True, label="In coada automat") }}
</div>
<div class="mapcol">
<button type="submit" style="min-height:44px;">Salveaza</button>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Panou admin — Gateway RAR AUTOPASS{% endblock %}
{% block title %}Conturi clienti — Gateway RAR AUTOPASS{% endblock %}
{% block content %}
{# US-009 (5.5): metadate verbe de ciclu de viata (eticheta, ruta, clasa). #}
@@ -50,8 +50,7 @@
<td class="muted">{{ acct.created_at or "—" }}</td>
<td style="white-space:nowrap;">
<details class="kebab">
<summary class="cardlink" style="list-style:none; cursor:pointer; display:inline-flex;
padding:4px 10px;" aria-label="Actiuni pentru {{ acct.name }}">&#8943;</summary>
<summary aria-label="Actiuni pentru {{ acct.name }}">&#8943;</summary>
<div class="kebab-menu">
{% for v in row_verbs %}
{% set label, action, cls = VERBS[v] %}
@@ -61,7 +60,7 @@
{% 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 }}">
<button type="submit" {% if cls == 'danger' %}style="color:var(--err);"{% endif %}>{{ label }}</button>
<button type="submit"{% if cls == 'danger' %} class="danger"{% endif %}>{{ label }}</button>
</form>
{% endfor %}
</div>
@@ -85,22 +84,11 @@
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 (reuseaza estetica meniului de cont) */
.kebab { position:relative; display:inline-block; }
.kebab > summary::-webkit-details-marker { display:none; }
.kebab-menu { position:absolute; right:0; top:calc(100% + 4px); min-width:140px; z-index:40;
background:var(--card); border:1px solid var(--line); border-radius:8px; padding:6px;
box-shadow:0 8px 24px rgba(0,0,0,.18); display:flex; flex-direction:column; gap:2px; }
.kebab[open] > summary { background:var(--line); }
.kebab-menu form { margin:0; }
.kebab-menu button { display:block; width:100%; text-align:left; background:transparent; border:none;
color:var(--ink); font:inherit; padding:7px 10px; border-radius:6px; cursor:pointer;
min-height:36px; }
.kebab-menu button:hover { background:var(--line); }
/* 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;">Panou admin</h2>
<h2 style="margin:0;">Conturi clienti</h2>
<a href="/" class="cardlink muted">Inapoi la dashboard</a>
</div>

View File

@@ -132,6 +132,24 @@
.cont-menu a:hover, .cont-menu button:hover { background:var(--line); }
.cont-menu hr { border:none; border-top:1px solid var(--line); margin:4px 0; }
.cont-menu form { margin:0; }
/* Kebab partajat (actiuni per-rand in tabele). Meniul e position:fixed si pozitionat de JS:
altfel `.tablewrap { overflow-x:auto }` induce overflow-y:auto si TAIE dropdown-ul pe ultimul
rand (bug 5.5 — meniul nu se vedea). fixed scoate meniul din contextul de clipping al tabelului. */
.kebab { position:relative; display:inline-block; }
.kebab > summary { list-style:none; cursor:pointer; display:inline-flex; align-items:center;
justify-content:center; min-height:32px; min-width:32px; padding:4px 10px;
border-radius:6px; color:var(--ink); }
.kebab > summary::-webkit-details-marker { display:none; }
.kebab > summary:hover, .kebab[open] > summary { background:var(--line); }
.kebab-menu { position:fixed; z-index:1000; min-width:160px; background:var(--card);
border:1px solid var(--line); border-radius:8px; padding:6px;
box-shadow:0 8px 24px rgba(0,0,0,.18); display:flex; flex-direction:column; gap:2px; }
.kebab-menu form { margin:0; }
.kebab-menu button, .kebab-menu a { display:block; width:100%; text-align:left; background:transparent;
border:none; color:var(--ink); text-decoration:none; font:inherit; padding:7px 10px;
border-radius:6px; cursor:pointer; min-height:36px; white-space:nowrap; }
.kebab-menu button:hover, .kebab-menu a:hover { background:var(--line); }
.kebab-menu button.danger { color:var(--err); }
</style>
</head>
<body>
@@ -154,7 +172,7 @@
<a role="menuitem" href="/?tab=cont">Cont</a>
<a role="menuitem" href="/?tab=integrare">Integrare</a>
<a role="menuitem" href="/?tab=nomenclator">Nomenclator</a>
{% if is_admin|default(false) %}<a role="menuitem" href="/admin">Panou admin</a>{% endif %}
{% if is_admin|default(false) %}<a role="menuitem" href="/admin">Conturi clienti</a>{% endif %}
<hr>
<form method="post" action="/logout">
<input type="hidden" name="csrf_token" value="{{ csrf_token|default('') }}">
@@ -232,5 +250,50 @@
});
})();
</script>
<script>
// Kebab partajat (actiuni per-rand). `<details class="kebab">` + `.kebab-menu` position:fixed.
// Pozitionarea se face in JS la deschidere (eveniment `toggle`, captat pe document fiindca nu
// bubble-uie), ancorat sub buton si aliniat la dreapta; flip in sus daca nu incape jos. Delegare
// pe document → supravietuieste swap-urilor HTMX (#mapari-section se re-randeaza la fiecare salvare).
(function() {
function position(d) {
var btn = d.querySelector('summary');
var menu = d.querySelector('.kebab-menu');
if (!btn || !menu) return;
var r = btn.getBoundingClientRect();
menu.style.visibility = 'hidden';
var mw = menu.offsetWidth, mh = menu.offsetHeight;
var left = Math.max(8, r.right - mw);
var top = (r.bottom + mh > window.innerHeight - 8 && r.top - mh - 4 > 8)
? r.top - mh - 4 : r.bottom + 4;
menu.style.left = left + 'px';
menu.style.top = top + 'px';
menu.style.visibility = '';
}
function closeAll(except) {
document.querySelectorAll('details.kebab[open]').forEach(function(d) {
if (d !== except) d.removeAttribute('open');
});
}
// `toggle` nu bubble-uie -> ascultam in faza de capturare pe document.
document.addEventListener('toggle', function(e) {
var d = e.target;
if (!d.classList || !d.classList.contains('kebab')) return;
if (d.open) { closeAll(d); position(d); }
}, true);
document.addEventListener('click', function(e) {
if (!e.target.closest('details.kebab')) closeAll(null);
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeAll(null);
});
// La scroll/resize repozitionam meniul deschis (position:fixed nu urmareste ancora singur).
window.addEventListener('scroll', function() {
var open = document.querySelector('details.kebab[open]');
if (open) position(open);
}, true);
window.addEventListener('resize', function() { closeAll(null); });
})();
</script>
</body>
</html>