feat(web): cautare + paginare client-side pe tabelele din Mapari
Maparile pot creste la sute de randuri. Enhancer reutilizabil (data-dt) in base.html filtreaza si pagineaza DOM-ul deja randat, fara cereri server; re-init la full load si dupa swap-urile HTMX. Aplicat pe cele 3 tabele (De rezolvat / operatii salvate / formate coloane). Randurile cu <select> expun haystack explicit prin data-dt-row (cod_op + cod_rar + denumire): altfel optiunile selectului ar pune tot nomenclatorul in textContent si orice cautare ar potrivi orice rand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,11 @@
|
||||
<a href="/?tab=acasa">Importa un fisier nou</a> daca vrei sa adaugi prezentari.
|
||||
</div>
|
||||
{% else %}
|
||||
<div data-dt="10">
|
||||
<div class="dt-tools">
|
||||
<input type="search" data-dt-search class="dt-search"
|
||||
placeholder="Cauta operatie sau cod..." aria-label="Cauta in operatiile de rezolvat">
|
||||
</div>
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
@@ -50,7 +55,9 @@
|
||||
{% for e in pending %}
|
||||
{% set top = e.suggestions[0] if e.suggestions else None %}
|
||||
{% set preselect = top.cod_prestatie if (top and top.score >= 60) else '' %}
|
||||
<tr>
|
||||
{# data-dt-row = haystack de cautare (randul contine un <select> cu tot nomenclatorul). #}
|
||||
<tr data-dt-row="{{ e.cod_op_service }} {{ e.denumire or '' }}
|
||||
{%- for s in e.suggestions[:3] %} {{ s.cod_prestatie }}{% endfor %}">
|
||||
<td>
|
||||
<form id="map-rez-{{ loop.index }}" hx-post="/mapari" hx-target="#mapari-section" hx-swap="outerHTML">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}">
|
||||
@@ -89,6 +96,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="dt-empty" data-dt-empty style="display:none;">Nicio operatie nu se potriveste cautarii.</div>
|
||||
<div class="dt-pager" data-dt-pager></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -105,6 +115,11 @@
|
||||
{% else %}
|
||||
{# US-005 (5.5): proza explicativa mutata in panoul Ajutor de la "De rezolvat" (o singura data). #}
|
||||
|
||||
<div data-dt="10">
|
||||
<div class="dt-tools">
|
||||
<input type="search" data-dt-search class="dt-search"
|
||||
placeholder="Cauta operatie sau cod RAR..." aria-label="Cauta in maparile salvate">
|
||||
</div>
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
@@ -115,7 +130,8 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for m in saved_mappings %}
|
||||
<tr>
|
||||
{# data-dt-row = haystack de cautare (randul contine un <select> cu tot nomenclatorul). #}
|
||||
<tr data-dt-row="{{ m.cod_op_service }} {{ m.cod_prestatie }} {{ m.nume_prestatie or '' }}">
|
||||
<td>
|
||||
<form id="map-salv-{{ loop.index }}" hx-post="/mapari/salvate" hx-target="#mapari-section" hx-swap="outerHTML">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}">
|
||||
@@ -160,6 +176,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="dt-empty" data-dt-empty style="display:none;">Nicio mapare nu se potriveste cautarii.</div>
|
||||
<div class="dt-pager" data-dt-pager></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -179,6 +198,11 @@
|
||||
Antetele de fisier recunoscute. Un fisier cu alte coloane = format nou separat.
|
||||
</p>
|
||||
|
||||
<div data-dt="10">
|
||||
<div class="dt-tools">
|
||||
<input type="search" data-dt-search class="dt-search"
|
||||
placeholder="Cauta coloana sau camp..." aria-label="Cauta in formatele de coloane">
|
||||
</div>
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
@@ -224,6 +248,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="dt-empty" data-dt-empty style="display:none;">Niciun format nu se potriveste cautarii.</div>
|
||||
<div class="dt-pager" data-dt-pager></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -150,6 +150,18 @@
|
||||
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); }
|
||||
/* Tabel cu cautare + paginare client-side (data-dt). Maparile pot creste la sute de randuri;
|
||||
filtram/paginez DOM-ul deja randat, fara cereri suplimentare. Vezi scriptul din base.html. */
|
||||
input[type=search] { font:inherit; background:var(--bg); color:var(--ink); border:1px solid var(--line);
|
||||
border-radius:6px; padding:6px 10px; width:100%; }
|
||||
.dt-tools { display:flex; align-items:center; gap:8px; margin:0 0 10px; }
|
||||
.dt-search { flex:1 1 auto; max-width:320px; }
|
||||
.dt-empty { color:var(--muted); padding:16px; text-align:center; font-size:13px; }
|
||||
.dt-pager { display:flex; align-items:center; justify-content:flex-end; gap:10px;
|
||||
margin-top:10px; font-size:13px; color:var(--muted); }
|
||||
.dt-pager button { background:transparent; color:var(--ink); border:1px solid var(--line);
|
||||
padding:5px 12px; min-height:32px; }
|
||||
.dt-pager button:disabled { opacity:.45; cursor:default; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -295,5 +307,68 @@
|
||||
window.addEventListener('resize', function() { closeAll(null); });
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
// Cautare + paginare client-side pentru tabele mari (data-dt="<page_size>"). Filtreaza si
|
||||
// pagineaza DOM-ul deja randat (fara cereri server) — potrivit pentru maparile care pot creste
|
||||
// la sute de randuri. Re-init la full load SI dupa swap-urile HTMX (tab Mapari, salvare/stergere).
|
||||
// Markup: <div data-dt="10"> [input data-dt-search] <table> [div data-dt-empty] [div data-dt-pager] </div>
|
||||
(function() {
|
||||
function enhance(scope) {
|
||||
(scope || document).querySelectorAll('[data-dt]').forEach(function(wrap) {
|
||||
if (wrap.__dt) return; // idempotent (afterSettle poate re-scana)
|
||||
wrap.__dt = true;
|
||||
var table = wrap.querySelector('table');
|
||||
var tbody = table && table.tBodies[0];
|
||||
if (!tbody) return;
|
||||
var size = parseInt(wrap.getAttribute('data-dt'), 10) || 10;
|
||||
var search = wrap.querySelector('[data-dt-search]');
|
||||
var pager = wrap.querySelector('[data-dt-pager]');
|
||||
var empty = wrap.querySelector('[data-dt-empty]');
|
||||
var rows = Array.prototype.slice.call(tbody.rows);
|
||||
var page = 1;
|
||||
// Haystack-ul randului: atributul data-dt-row daca exista, altfel textContent. Necesar
|
||||
// cand randul contine un <select> (optiunile lui ar pune tot nomenclatorul in textContent
|
||||
// -> orice cautare ar potrivi orice rand). Vezi data-dt-row in _mapari.html.
|
||||
function haystack(r) {
|
||||
var h = r.getAttribute('data-dt-row');
|
||||
return (h !== null ? h : r.textContent).toLowerCase();
|
||||
}
|
||||
function matched() {
|
||||
var q = (search && search.value || '').trim().toLowerCase();
|
||||
if (!q) return rows;
|
||||
return rows.filter(function(r) { return haystack(r).indexOf(q) !== -1; });
|
||||
}
|
||||
function draw() {
|
||||
var fr = matched();
|
||||
var pages = Math.max(1, Math.ceil(fr.length / size));
|
||||
if (page > pages) page = pages;
|
||||
if (page < 1) page = 1;
|
||||
var start = (page - 1) * size;
|
||||
rows.forEach(function(r) { r.style.display = 'none'; });
|
||||
fr.slice(start, start + size).forEach(function(r) { r.style.display = ''; });
|
||||
if (empty) empty.style.display = fr.length ? 'none' : '';
|
||||
if (!pager) return;
|
||||
if (fr.length <= size && page === 1) { pager.style.display = 'none'; pager.innerHTML = ''; return; }
|
||||
pager.style.display = '';
|
||||
pager.innerHTML = '';
|
||||
var info = document.createElement('span');
|
||||
info.textContent = (fr.length ? start + 1 : 0) + '–' +
|
||||
Math.min(start + size, fr.length) + ' din ' + fr.length;
|
||||
var prev = document.createElement('button');
|
||||
prev.type = 'button'; prev.textContent = 'Inapoi'; prev.disabled = page <= 1;
|
||||
prev.addEventListener('click', function() { page--; draw(); });
|
||||
var next = document.createElement('button');
|
||||
next.type = 'button'; next.textContent = 'Inainte'; next.disabled = page >= pages;
|
||||
next.addEventListener('click', function() { page++; draw(); });
|
||||
pager.appendChild(info); pager.appendChild(prev); pager.appendChild(next);
|
||||
}
|
||||
if (search) search.addEventListener('input', function() { page = 1; draw(); });
|
||||
draw();
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() { enhance(document); });
|
||||
document.body.addEventListener('htmx:afterSettle', function(e) { enhance(e.target); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user