diff --git a/loko/assets/static/assets/unified_index.js b/loko/assets/static/assets/unified_index.js index 465f66a..55c7c25 100644 --- a/loko/assets/static/assets/unified_index.js +++ b/loko/assets/static/assets/unified_index.js @@ -638,6 +638,79 @@ function applyVisibleColumnsPreference(visibleColumns) { } }); isApplyingSavedColumnVisibility = false; + + // Update checkboxes in dropdown + document.querySelectorAll('.col-toggle-check').forEach(cb => { + cb.checked = wanted.has(cb.dataset.column); + }); +} + +function renderColumnsDropdown() { + const container = document.getElementById('columns-list-container'); + if (!container) return; + container.innerHTML = ''; + + const cols = [ + { field: 'code', label: gettext('Code'), defaultVisible: true }, + { field: 'name', label: gettext('Nom'), defaultVisible: true }, + { field: 'element_type', label: gettext('Type'), defaultVisible: true }, + { field: 'thematic_name', label: gettext('Thématique'), defaultVisible: true }, + { field: 'category', label: gettext('Catégorie d\'assets'), defaultVisible: false }, + { field: 'model', label: gettext('Modèle'), defaultVisible: false }, + { field: 'status', label: gettext('Statut'), defaultVisible: false }, + { field: 'validation_status', label: gettext('Statut de validation'), defaultVisible: true }, + ]; + + const visibleCols = new Set(getVisibleColumnsPreference()); + + cols.forEach(col => { + const isVisible = visibleCols.has(col.field); + const div = document.createElement('div'); + div.className = 'form-check py-1'; + div.innerHTML = ` + + + `; + container.appendChild(div); + }); + + container.addEventListener('change', (e) => { + const cb = e.target.closest('.col-toggle-check'); + if (!cb) return; + const colField = cb.dataset.column; + if (cb.checked) { + $('#unified-table').bootstrapTable('showColumn', colField); + } else { + $('#unified-table').bootstrapTable('hideColumn', colField); + } + schedulePreferencesSave(); + }); + + document.getElementById('cols-select-all')?.addEventListener('click', (e) => { + e.preventDefault(); + cols.forEach(col => { + $('#unified-table').bootstrapTable('showColumn', col.field); + }); + container.querySelectorAll('.col-toggle-check').forEach(cb => { cb.checked = true; }); + schedulePreferencesSave(); + }); + + document.getElementById('cols-reset-default')?.addEventListener('click', (e) => { + e.preventDefault(); + cols.forEach(col => { + if (col.defaultVisible) { + $('#unified-table').bootstrapTable('showColumn', col.field); + } else { + $('#unified-table').bootstrapTable('hideColumn', col.field); + } + }); + container.querySelectorAll('.col-toggle-check').forEach(cb => { + const field = cb.dataset.column; + const def = cols.find(c => c.field === field); + cb.checked = !!def?.defaultVisible; + }); + schedulePreferencesSave(); + }); } // ─── Preferences: save / restore ───────────────────────────────────────────── @@ -834,14 +907,27 @@ $(document).ready(function () { search: false, pagination: true, pageSize: 100, + maintainMetaData: true, clickToSelect: true, ignoreClickToSelectOn: function (element) { return $(element).closest('.zoom-btn, .unified-code-link').length > 0; }, - onCheck: function (row) { row.state = true; selectedIds.add(row.id); updateSelectionStatus(); }, - onUncheck: function (row) { row.state = false; selectedIds.delete(row.id); updateSelectionStatus(); }, - onCheckAll: function (rows) { rows.forEach(r => { r.state = true; selectedIds.add(r.id); }); updateSelectionStatus(); }, - onUncheckAll: function (rows) { rows.forEach(r => { r.state = false; selectedIds.delete(r.id); }); updateSelectionStatus(); }, + onCheck: function (row) { + row.state = true; + selectedIds.add(row.id); + updateSelectionStatus(); + }, + onUncheck: function (row) { + row.state = false; + selectedIds.delete(row.id); + updateSelectionStatus(); + }, + onCheckAll: function (rows) { + selectAllTableRows(); + }, + onUncheckAll: function (rows) { + deselectAllTableRows(); + }, onClickRow: function (row) { if (!row.lon || !row.lat) return; const source = row.is_management_zone ? 'management_zones' : (row.is_location ? 'locations' : 'assets'); @@ -851,6 +937,10 @@ $(document).ready(function () { }, }); + $('#unified-table').on('post-body.bs.table page-change.bs.table', function () { + syncSelectAllCheckbox(); + }); + $(window).on('resize', function () { $('#unified-table').bootstrapTable('resetView', { height: calcHeight() }); }); @@ -1289,28 +1379,21 @@ $(document).ready(function () { // ─── Select all / deselect all ───────────────────────────────────────── - function selectAllTableRows() { - const allRows = $('#unified-table').bootstrapTable('getData') || []; - allRows.forEach(r => { - selectedIds.add(r.id); - r.state = true; - }); - $('#unified-table').bootstrapTable('checkAll'); - updateSelectionStatus(); - } + $(document).on('click', 'input[name="btSelectAll"]', function () { + if (this.checked) { + selectAllTableRows(); + } else { + deselectAllTableRows(); + } + }); - function deselectAllTableRows() { - const allRows = $('#unified-table').bootstrapTable('getData') || []; - allRows.forEach(r => { - r.state = false; - }); - selectedIds.clear(); - $('#unified-table').bootstrapTable('uncheckAll'); - updateSelectionStatus(); - } + document.getElementById('page-size-select')?.addEventListener('change', function () { + const newSize = parseInt(this.value, 10) || 100; + $('#unified-table').bootstrapTable('refreshOptions', { pageSize: newSize }); + }); - document.getElementById('unified-select-all').addEventListener('click', selectAllTableRows); - document.getElementById('unified-deselect-all').addEventListener('click', deselectAllTableRows); + document.getElementById('unified-select-all')?.addEventListener('click', selectAllTableRows); + document.getElementById('unified-deselect-all')?.addEventListener('click', deselectAllTableRows); bindUnifiedAssetsExport(); bindUnifiedBulkActions(); @@ -1349,6 +1432,7 @@ $(document).ready(function () { // ─── Init ────────────────────────────────────────────────────────────── applyInitialPreferences(); + renderColumnsDropdown(); buildCategoryLookup(); renderCategoryFilters(); renderModelFilters(); @@ -1645,7 +1729,7 @@ function getCsrfToken() { } function getSelectedAssetKeysForExport() { - const rows = $('#unified-table').bootstrapTable('getData') || []; + const rows = getAllTableRows(); const keys = []; const seen = new Set(); @@ -1740,7 +1824,7 @@ function bindUnifiedAssetsExport() { } function buildBulkSelectedAssetsPayload() { - const rows = $('#unified-table').bootstrapTable('getData') || []; + const rows = getAllTableRows(); const byId = new Map(rows.filter(r => !r.is_location).map(r => [String(r.id), r])); const payload = []; @@ -2774,14 +2858,9 @@ function loadTableData(features) { $('#unified-spinner').hide(); $('#unified-table').bootstrapTable('load', rows); - // Restore checked state - if (selectedIds.size) { - rows.forEach((row, idx) => { - if (selectedIds.has(row.id)) { - $('#unified-table').bootstrapTable('check', idx); - } - }); - } + updateSelectionStatus(); + const countBadge = document.getElementById('assets-counter-badge'); + if (countBadge) countBadge.textContent = rows.length; } // ─── Map integration ────────────────────────────────────────────────────────── @@ -3093,6 +3172,9 @@ function showEmptyState() { hideSpinner(); document.getElementById('unified-empty-state').style.display = ''; updateAssetsHint(); + const countBadge = document.getElementById('assets-counter-badge'); + if (countBadge) countBadge.textContent = '0'; + syncSelectAllCheckbox(); } function hideEmptyState() { @@ -3129,6 +3211,81 @@ function updateFilterBadge() { badge.classList.toggle('d-none', n === 0); } +function getAllTableRows() { + const $table = $('#unified-table'); + if (!$table.length || typeof $table.bootstrapTable !== 'function') return []; + const options = $table.bootstrapTable('getOptions'); + if (options && Array.isArray(options.data) && options.data.length > 0) { + return options.data; + } + return $table.bootstrapTable('getData', { useCurrentPage: false }) || []; +} + +var isBulkSelectionInProgress = false; + +function selectAllTableRows() { + if (isBulkSelectionInProgress) return; + isBulkSelectionInProgress = true; + try { + const allRows = getAllTableRows(); + allRows.forEach(r => { + selectedIds.add(r.id); + r.state = true; + }); + $('#unified-table tbody tr').addClass('selected'); + $('#unified-table input[name="btSelectItem"]').prop('checked', true); + updateSelectionStatus(); + } finally { + isBulkSelectionInProgress = false; + } +} + +function deselectAllTableRows() { + if (isBulkSelectionInProgress) return; + isBulkSelectionInProgress = true; + try { + const allRows = getAllTableRows(); + allRows.forEach(r => { + r.state = false; + selectedIds.delete(r.id); + }); + $('#unified-table tbody tr').removeClass('selected'); + $('#unified-table input[name="btSelectItem"]').prop('checked', false); + updateSelectionStatus(); + } finally { + isBulkSelectionInProgress = false; + } +} + +function syncSelectAllCheckbox() { + const allRows = getAllTableRows(); + const selectAllCheckboxes = document.querySelectorAll('input[name="btSelectAll"]'); + if (!selectAllCheckboxes.length) return; + + if (allRows.length === 0) { + selectAllCheckboxes.forEach(cb => { + cb.checked = false; + cb.indeterminate = false; + }); + return; + } + + let selectedRowCount = 0; + allRows.forEach(r => { + if (selectedIds.has(r.id)) { + selectedRowCount++; + } + }); + + const isAllSelected = selectedRowCount === allRows.length; + const isSomeSelected = selectedRowCount > 0 && selectedRowCount < allRows.length; + + selectAllCheckboxes.forEach(cb => { + cb.checked = isAllSelected; + cb.indeterminate = isSomeSelected; + }); +} + function updateSelectionStatus() { const el = document.getElementById('unified-selection-status'); if (el) el.textContent = `${selectedIds.size} ${gettext('sélectionné(s)')}`; @@ -3137,10 +3294,11 @@ function updateSelectionStatus() { if (hiddenInput) hiddenInput.value = Array.from(selectedIds).join(','); const hiddenLocationsInput = document.getElementById('preventive-selected-locations-ids'); if (hiddenLocationsInput) hiddenLocationsInput.value = Array.from(selectedIds).join(','); + syncSelectAllCheckbox(); } function buildPreventiveSelectedAssetsPayload() { - const rows = $('#unified-table').bootstrapTable('getData') || []; + const rows = getAllTableRows(); const byId = new Map(rows.filter(r => !r.is_location).map(r => [String(r.id), r])); const payload = []; diff --git a/loko/assets/templates/assets/unified_index.html b/loko/assets/templates/assets/unified_index.html index 595cfd2..df74545 100644 --- a/loko/assets/templates/assets/unified_index.html +++ b/loko/assets/templates/assets/unified_index.html @@ -390,8 +390,8 @@ font-size: 0.72rem; font-weight: 700; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); - top: 2px !important; - right: -6px !important; + top: -5px !important; + right: -7px !important; left: auto !important; transform: none !important; min-width: 1.25rem; @@ -479,7 +479,7 @@ font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.04em !important; - border-bottom: 1.5px solid #e2e8f0 !important; + border-bottom: 2px solid #cbd5e1 !important; padding: 0.45rem 0.65rem !important; vertical-align: middle !important; white-space: nowrap !important; @@ -494,11 +494,11 @@ /* Rows & Cells */ .bootstrap-table.bootstrap5 #unified-table td { - padding: 0.32rem 0.65rem !important; + padding: 0.35rem 0.65rem !important; vertical-align: middle !important; font-size: 0.82rem !important; color: #334155; - border-bottom: 1px solid #f1f5f9 !important; + border-bottom: 1px solid #e2e8f0 !important; line-height: 1.3 !important; } @@ -593,43 +593,157 @@ border-color: #cbd5e1; } - /* Selection toolbar styling */ - #unified-select-all, - #unified-deselect-all { - background-color: #ffffff; - border: 1px solid #e2e8f0; - color: #475569; - border-radius: 6px; - font-size: 0.78rem; - font-weight: 500; - padding: 0.28rem 0.65rem; + /* ─── Controls/Assets Tabs ─────────────────────────────────────────── */ + .controls-tabs { + border-bottom: 2px solid #e2e8f0; + } + .controls-tabs .nav-link { + border: none; + border-bottom: 2px solid transparent; + color: #64748b; + font-weight: 600; + font-size: 0.95rem; + padding: 0.5rem 0.75rem; + margin-bottom: -2px; + background: transparent; transition: all 0.15s ease; } - #unified-select-all:hover, - #unified-deselect-all:hover { - background-color: #f1f5f9; + .controls-tabs .nav-link:hover { color: #0f172a; - border-color: #cbd5e1; + border-bottom-color: #cbd5e1; } + .controls-tabs .nav-link.active { + color: #0f172a; + border-bottom-color: #0f172a; + background: transparent; + } + .controls-tabs .tab-counter-badge { + background-color: #0f172a; + color: #ffffff; + font-size: 0.75rem; + font-weight: 700; + padding: 0.18rem 0.55rem; + border-radius: 9999px; + margin-left: 0.5rem; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + border: 1px solid #0f172a; + } + + /* ─── Assets Toolbar ───────────────────────────────────────────────── */ + .assets-toolbar { + padding: 0.25rem 0; + } + + .filter-trigger-btn, + #columns-dropdown-btn, + #action-dropdown-btn { + border: 1px solid #cbd5e1; + border-radius: 8px; + font-size: 0.82rem; + font-weight: 500; + padding: 0.32rem 0.75rem; + background-color: #ffffff; + color: #334155; + display: inline-flex; + align-items: center; + gap: 0.4rem; + transition: all 0.15s ease; + text-decoration: none; + } + .filter-trigger-btn:hover, + #columns-dropdown-btn:hover, + #action-dropdown-btn:hover { + background-color: #f8fafc; + border-color: #94a3b8; + color: #0f172a; + } + + .btn-new-asset { + background-color: #0f172a; + color: #ffffff !important; + border: 1px solid #0f172a; + border-radius: 8px; + padding: 0.32rem 0.75rem; + font-size: 0.82rem; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 0.4rem; + transition: all 0.15s ease; + text-decoration: none; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); + white-space: nowrap; + } + .btn-new-asset:hover:not(:disabled) { + background-color: #1e293b; + color: #ffffff !important; + border-color: #1e293b; + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(15, 23, 42, 0.12); + } + .btn-new-asset:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .columns-dropdown-menu { + border-radius: 12px; + border: 1px solid #e2e8f0; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.12), 0 8px 10px -6px rgba(0, 0, 0, 0.08); + min-width: 250px; + z-index: 1060; + } + + .columns-checkbox-list { + max-height: 280px; + overflow-y: auto; + } + + .columns-checkbox-list .form-check { + margin-bottom: 0.2rem; + display: flex; + align-items: center; + gap: 0.5rem; + } + + .columns-checkbox-list .form-check-input { + cursor: pointer; + margin-top: 0; + } + + .columns-checkbox-list .form-check-label { + cursor: pointer; + font-size: 0.82rem; + } + + .page-size-selector { + font-size: 0.82rem; + color: #64748b; + } + + .page-size-select { + padding: 0.25rem 0.55rem; + font-size: 0.82rem; + border: 1px solid #cbd5e1; + border-radius: 7px; + background-color: #ffffff; + color: #334155; + cursor: pointer; + font-weight: 500; + } + .page-size-select:focus { + outline: none; + border-color: #2563eb; + } + #unified-selection-status { font-size: 0.8rem; color: #64748b; font-weight: 500; } - #unified-bulk-action-dropdown .dropdown-toggle { - background-color: #ffffff; - border: 1px solid #e2e8f0; - color: #475569; - border-radius: 6px; - font-size: 0.78rem; - font-weight: 500; - padding: 0.28rem 0.75rem; - } - #unified-bulk-action-dropdown .dropdown-toggle:hover { - background-color: #f1f5f9; - color: #0f172a; - border-color: #cbd5e1; - } /* Pagination bar styling */ .bootstrap-table.bootstrap5 .fixed-table-pagination { @@ -1074,21 +1188,21 @@ {# ─── Table column ───────────────────────────────────────────────── #}