PageSourceSearch

https://napoparte-cmms.hf.space/assets/asset_hist_pagination.js?m=1789806719.0

js napoparte-cmms.hf.space collected 2026-10-03 17:09:48 UTC 9,938 bytes, 220 lines download raw bytes

1/**
2 * asset_hist_pagination.js v3
3 * Client-side pagination + search + filter cho asset detail modal history tabs.
4 *
5 * Vì dbc.Input/dcc.Input không nhận data-* attrs, JS tự parse section từ element id:
6 *   id "asset-hist-search-inc"          → section = "inc"
7 *   id "asset-hist-filter-year-inc"     → section = "inc", filterKey = "year"
8 *   id "asset-hist-filter-type-maint"   → section = "maint", filterKey = "type"
9 *   id "asset-hist-first-parts" (btn)   → section = "parts"
10 *
11 * html.Select dropdowns vẫn dùng data-section/data-filterkey (html.Select hỗ trợ).
12 *
13 * State lưu trên pager div data attrs:
14 *   data-page, data-pagesize, data-total, data-cols, data-center, data-filters
15 */
16(function () {
17    'use strict';
18
19    var PREFIX = 'asset-hist-';
20
21    /* ── Parse section từ element id ──────────────────────────────────── */
22    function sectionFromId(id, part) {
23        // id = "asset-hist-{part}-{section}"
24        // part examples: "search", "first", "prev", "next", "last", "page", "count"
25        var pfx = PREFIX + part + '-';
26        return id && id.startsWith(pfx) ? id.slice(pfx.length) : null;
27    }
28
29    // For filter dropdowns: id = "asset-hist-filter-{filterKey}-{section}"
30    // section may itself contain "-" only if section_id has "-", which we avoid.
31    // filterKey is always the second-to-last segment, section is the last.
32    function parseFilterId(id) {
33        var pfx = PREFIX + 'filter-';
34        if (!id || !id.startsWith(pfx)) return null;
35        var rest = id.slice(pfx.length); // "year-inc" or "type-maint"
36        var dash = rest.lastIndexOf('-');
37        if (dash < 0) return null;
38        return { filterKey: rest.slice(0, dash), section: rest.slice(dash + 1) };
39    }
40
41    /* ── Helpers ────────────────────────────────────────────────────── */
42    function escHtml(s) {
43        return String(s === null || s === undefined ? '' : s)
44            .replace(/&/g,'&amp;').replace(/</g,'&lt;')
45            .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
46    }
47
48    function getAllRows(section) {
49        var div = document.getElementById(PREFIX + 'data-' + section);
50        if (!div) return [];
51        try { return JSON.parse(div.textContent || '[]'); } catch(e) { return []; }
52    }
53
54    function getPager(section) {
55        return document.getElementById(PREFIX + 'pager-' + section);
56    }
57
58    function readFilters(section) {
59        var p = getPager(section);
60        if (!p) return {};
61        try { return JSON.parse(p.dataset.filters || '{}'); } catch(e) { return {}; }
62    }
63
64    function readSearch(section) {
65        var el = document.getElementById(PREFIX + 'search-' + section);
66        return el ? (el.value || '') : '';
67    }
68
69    /* ── Apply search + filter ──────────────────────────────────────── */
70    function applyFilters(rows, searchStr, filters) {
71        var q = (searchStr || '').trim().toLowerCase();
72        var hasFilter = Object.keys(filters).some(function(k){ return !!filters[k]; });
73        if (!q && !hasFilter) return rows;
74        return rows.filter(function(r) {
75            if (hasFilter) {
76                for (var k in filters) {
77                    if (!filters[k]) continue;
78                    var v = String(r[k] !== null && r[k] !== undefined ? r[k] : '').trim();
79                    if (v !== filters[k]) return false;
80                }
81            }
82            if (q) {
83                var found = false;
84                for (var key in r) {
85                    if (String(r[key] !== null && r[key] !== undefined ? r[key] : '').toLowerCase().indexOf(q) !== -1) {
86                        found = true; break;
87                    }
88                }
89                if (!found) return false;
90            }
91            return true;
92        });
93    }
94
95    /* ── Render one page ────────────────────────────────────────────── */
96    function renderPage(section, filteredRows, curPage) {
97        var pager   = getPager(section);
98        var tbody   = document.getElementById(PREFIX + 'tbody-' + section);
99        var pageLbl = document.getElementById(PREFIX + 'page-'  + section);
100        var cntLbl  = document.getElementById(PREFIX + 'count-' + section);
101        if (!pager || !tbody) return;
102
103        var pageSize   = parseInt(pager.dataset.pagesize, 10) || 10;
104        var cols       = JSON.parse(pager.dataset.cols   || '[]');
105        var centers    = JSON.parse(pager.dataset.center || '[]');
106        var total      = filteredRows.length;
107        var totalPages = Math.max(1, Math.ceil(total / pageSize));
108
109        curPage = Math.max(0, Math.min(curPage, totalPages - 1));
110        pager.dataset.page  = curPage;
111        pager.dataset.total = total;
112
113        var start = curPage * pageSize;
114        var slice = filteredRows.slice(start, start + pageSize);
115        var html  = '';
116        if (!slice.length) {
117            var nodataMsg = (pager && pager.dataset.nodata) ? pager.dataset.nodata : 'Không có dữ liệu phù hợp';
118            html = '<tr><td colspan="' + cols.length +
119                   '" class="text-center text-muted small py-3">' + escHtml(nodataMsg) + '</td></tr>';
120        } else {
121            slice.forEach(function(r, i) {
122                var cls = i % 2 === 0 ? 'asset-hist-odd' : 'asset-hist-even';
123                html += '<tr class="asset-hist-row ' + cls + '">';
124                cols.forEach(function(key, j) {
125                    var center = centers[j] ? ' text-center' : '';
126                    var val = (r[key] !== null && r[key] !== undefined) ? String(r[key]) : '';
127                    html += '<td class="asset-hist-td' + center + '">' + escHtml(val) + '</td>';
128                });
129                html += '</tr>';
130            });
131        }
132        tbody.innerHTML = html;
133
134        if (pageLbl) pageLbl.textContent = (curPage + 1) + ' / ' + totalPages;
135        var rowsTpl = (pager && pager.dataset.rowslabel) ? pager.dataset.rowslabel : '{n} dòng';
136        if (cntLbl)  cntLbl.textContent  = rowsTpl.replace('{n}', total);
137
138        ['first','prev'].forEach(function(a) {
139            var b = document.getElementById(PREFIX + a + '-' + section);
140            if (b) b.disabled = (curPage <= 0);
141        });
142        ['next','last'].forEach(function(a) {
143            var b = document.getElementById(PREFIX + a + '-' + section);
144            if (b) b.disabled = (curPage >= totalPages - 1);
145        });
146    }
147
148    function refresh(section, newPage) {
149        var pager = getPager(section);
150        if (!pager) return;
151        var curPage = (newPage !== undefined) ? newPage : parseInt(pager.dataset.page, 10) || 0;
152        var filtered = applyFilters(getAllRows(section), readSearch(section), readFilters(section));
153        renderPage(section, filtered, curPage);
154        // (filtered rows không cần sync lên Python store - export callback dùng DB fallback)
155    }
156
157    /* ── Click: pagination buttons ──────────────────────────────────── */
158    document.addEventListener('click', function(e) {
159        var btn = e.target.closest('button[id^="' + PREFIX + '"]');
160        if (!btn) return;
161        var id = btn.id;
162        var section = null, action = null;
163        ['first','prev','next','last'].forEach(function(a) {
164            var s = sectionFromId(id, a);
165            if (s) { section = s; action = a; }
166        });
167        if (!section) return;
168
169        var pager = getPager(section);
170        if (!pager) return;
171        var curPage    = parseInt(pager.dataset.page, 10) || 0;
172        var total      = parseInt(pager.dataset.total, 10) || 0;
173        var pageSize   = parseInt(pager.dataset.pagesize, 10) || 10;
174        var totalPages = Math.max(1, Math.ceil(total / pageSize));
175
176        if      (action === 'first') curPage = 0;
177        else if (action === 'prev')  curPage = Math.max(0, curPage - 1);
178        else if (action === 'next')  curPage = Math.min(totalPages - 1, curPage + 1);
179        else if (action === 'last')  curPage = totalPages - 1;
180
181        refresh(section, curPage);
182    });
183
184    /* ── Change: filter dropdowns (html.Select — support data-* natively) */
185    document.addEventListener('change', function(e) {
186        var el = e.target;
187        // html.Select: có data-action="filter"
188        if (el.dataset && el.dataset.action === 'filter' && el.dataset.section && el.dataset.filterkey) {
189            var pager = getPager(el.dataset.section);
190            if (!pager) return;
191            var filters = readFilters(el.dataset.section);
192            filters[el.dataset.filterkey] = el.value || '';
193            pager.dataset.filters = JSON.stringify(filters);
194            refresh(el.dataset.section, 0);
195            return;
196        }
197        // Fallback: parse from id (asset-hist-filter-{key}-{section})
198        var parsed = el.id ? parseFilterId(el.id) : null;
199        if (parsed) {
200            var pager2 = getPager(parsed.section);
201            if (!pager2) return;
202            var filters2 = readFilters(parsed.section);
203            filters2[parsed.filterKey] = el.value || '';
204            pager2.dataset.filters = JSON.stringify(filters2);
205            refresh(parsed.section, 0);
206        }
207    });
208
209    /* ── Input/change: search box (dbc.Input — id only, no data-*) ──── */
210    function onSearch(e) {
211        var el = e.target;
212        var id = el.id || '';
213        var section = sectionFromId(id, 'search');
214        if (!section) return;
215        refresh(section, 0);
216    }
217    document.addEventListener('input',  onSearch);
218    document.addEventListener('change', onSearch);
219
220})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.