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.
|
<a href="/?tab=acasa">Importa un fisier nou</a> daca vrei sa adaugi prezentari.
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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">
|
<div class="tablewrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
@@ -50,7 +55,9 @@
|
|||||||
{% for e in pending %}
|
{% for e in pending %}
|
||||||
{% set top = e.suggestions[0] if e.suggestions else None %}
|
{% set top = e.suggestions[0] if e.suggestions else None %}
|
||||||
{% set preselect = top.cod_prestatie if (top and top.score >= 60) else '' %}
|
{% 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>
|
<td>
|
||||||
<form id="map-rez-{{ loop.index }}" hx-post="/mapari" hx-target="#mapari-section" hx-swap="outerHTML">
|
<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 '' }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}">
|
||||||
@@ -89,6 +96,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -105,6 +115,11 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{# US-005 (5.5): proza explicativa mutata in panoul Ajutor de la "De rezolvat" (o singura data). #}
|
{# 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">
|
<div class="tablewrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
@@ -115,7 +130,8 @@
|
|||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in saved_mappings %}
|
{% 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>
|
<td>
|
||||||
<form id="map-salv-{{ loop.index }}" hx-post="/mapari/salvate" hx-target="#mapari-section" hx-swap="outerHTML">
|
<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 '' }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}">
|
||||||
@@ -160,6 +176,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -179,6 +198,11 @@
|
|||||||
Antetele de fisier recunoscute. Un fisier cu alte coloane = format nou separat.
|
Antetele de fisier recunoscute. Un fisier cu alte coloane = format nou separat.
|
||||||
</p>
|
</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">
|
<div class="tablewrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
@@ -224,6 +248,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,18 @@
|
|||||||
border-radius:6px; cursor:pointer; min-height:36px; white-space:nowrap; }
|
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:hover, .kebab-menu a:hover { background:var(--line); }
|
||||||
.kebab-menu button.danger { color:var(--err); }
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -295,5 +307,68 @@
|
|||||||
window.addEventListener('resize', function() { closeAll(null); });
|
window.addEventListener('resize', function() { closeAll(null); });
|
||||||
})();
|
})();
|
||||||
</script>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user