Statuses import, an icon picker, folder-scoped CSV import, and site name
- Statuses can now be bulk-imported/updated via CSV (Name, Color columns; matches and updates existing statuses by name). New internal/statuses/csv.go, tested. - Icon fields (folder create/rename, item type form) are now a searchable dropdown of the 50 most common Bootstrap Icons instead of a free-text box, via a small reusable .icon-picker widget in app.js/app.css. - CSV asset import is now folder-scoped when triggered from the tree: right-click a folder (or its background) > Import CSV... imports every row directly into that folder, so the CSV needs no Folder column at all, and the option no longer appears at the top level. The original global, Folder-path-aware import (with auto-create-missing-folders) moved to a new admin-only Settings > Import / Export page, alongside the new Statuses import and format examples for both. assets.Import gained ImportOptions.TargetFolderID to drive the folder-scoped mode. - Site Name (Settings > General) now actually renders in the navbar brand and <title>, via a per-App "siteName" template func bound in loadTemplates. The brand is plain text now, not a link to /inventory.
This commit is contained in:
+119
-7
@@ -240,6 +240,7 @@
|
||||
items.push({ divider: true });
|
||||
items.push({ label: 'Rename', icon: 'bi-pencil', action: function () { openRenameFolder(id); } });
|
||||
items.push({ divider: true });
|
||||
items.push({ label: 'Import CSV…', icon: 'bi-upload', action: function () { openImportModal(id); } });
|
||||
}
|
||||
items.push({ label: 'Export CSV', icon: 'bi-download', action: function () { window.location = '/export/assets.csv?folder_id=' + id + '&recursive=1'; } });
|
||||
if (isAdmin) {
|
||||
@@ -261,6 +262,9 @@
|
||||
return items;
|
||||
}
|
||||
|
||||
// Import is intentionally folder-scoped only (see openImportModal) - it
|
||||
// never appears at the top level, since a global admin import with
|
||||
// folder-path resolution lives under Settings > Import / Export instead.
|
||||
function treeBackgroundMenuItems() {
|
||||
var items = [];
|
||||
if (isAdmin) {
|
||||
@@ -271,9 +275,6 @@
|
||||
items.push({ label: 'Expand All', icon: 'bi-arrows-expand', action: expandAllHandler });
|
||||
items.push({ label: 'Collapse All', icon: 'bi-arrows-collapse', action: collapseAllHandler });
|
||||
items.push({ divider: true });
|
||||
if (isAdmin) {
|
||||
items.push({ label: 'Import CSV…', icon: 'bi-upload', action: function () { if (importModal) importModal.show(); } });
|
||||
}
|
||||
items.push({ label: 'Export CSV', icon: 'bi-download', action: function () { window.location = '/export/assets.csv'; } });
|
||||
return items;
|
||||
}
|
||||
@@ -283,10 +284,13 @@
|
||||
if (isAdmin) {
|
||||
items.push({ label: folderId ? 'New Subfolder' : 'New Folder', icon: 'bi-folder-plus', action: function () { openFolderModal({ parentId: folderId }); } });
|
||||
items.push({ label: 'Add Asset', icon: 'bi-plus-lg', action: function () { loadAssetForm({ folderId: folderId }); } });
|
||||
items.push({ divider: true });
|
||||
items.push({ label: 'Import CSV…', icon: 'bi-upload', action: function () { if (importModal) importModal.show(); } });
|
||||
if (folderId) {
|
||||
items.push({ divider: true });
|
||||
items.push({ label: 'Import CSV…', icon: 'bi-upload', action: function () { openImportModal(folderId); } });
|
||||
}
|
||||
}
|
||||
var exportUrl = folderId ? ('/export/assets.csv?folder_id=' + folderId + '&recursive=1') : '/export/assets.csv';
|
||||
items.push({ divider: true });
|
||||
items.push({ label: 'Export CSV', icon: 'bi-download', action: function () { window.location = exportUrl; } });
|
||||
return items;
|
||||
}
|
||||
@@ -344,6 +348,105 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Icon picker: a searchable dropdown of common Bootstrap Icons standing
|
||||
// in for a free-text icon-class input (used by the folder modal and the
|
||||
// item type form).
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
var COMMON_ICONS = [
|
||||
'bi-folder-fill', 'bi-folder', 'bi-archive', 'bi-box-seam', 'bi-boxes',
|
||||
'bi-laptop', 'bi-pc-display', 'bi-pc', 'bi-display', 'bi-tv', 'bi-projector',
|
||||
'bi-phone', 'bi-telephone', 'bi-tablet', 'bi-smartwatch',
|
||||
'bi-camera-video', 'bi-camera', 'bi-printer', 'bi-keyboard', 'bi-mouse',
|
||||
'bi-hdd', 'bi-hdd-rack', 'bi-hdd-stack', 'bi-server', 'bi-cpu', 'bi-memory',
|
||||
'bi-disc', 'bi-usb-drive', 'bi-usb-plug', 'bi-ethernet', 'bi-router', 'bi-wifi',
|
||||
'bi-shield-lock', 'bi-shield-check', 'bi-lock', 'bi-unlock', 'bi-plug',
|
||||
'bi-battery', 'bi-battery-charging', 'bi-gear', 'bi-gear-fill', 'bi-tools',
|
||||
'bi-wrench', 'bi-tag', 'bi-tags', 'bi-bookmark', 'bi-house-door-fill',
|
||||
'bi-building', 'bi-person', 'bi-people',
|
||||
];
|
||||
|
||||
function setIconPickerValue(input, val) {
|
||||
input.value = val || '';
|
||||
var container = input.closest('.icon-picker');
|
||||
if (!container) return;
|
||||
var preview = container.querySelector('.icon-picker-preview');
|
||||
var label = container.querySelector('.icon-picker-label');
|
||||
if (val) {
|
||||
preview.className = 'bi ' + val + ' icon-picker-preview';
|
||||
label.textContent = val;
|
||||
label.classList.remove('text-body-secondary');
|
||||
} else {
|
||||
preview.className = 'bi icon-picker-preview';
|
||||
label.textContent = 'Choose icon…';
|
||||
label.classList.add('text-body-secondary');
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllIconPickers() {
|
||||
document.querySelectorAll('.icon-picker-menu.show').forEach(function (m) { m.classList.remove('show'); });
|
||||
}
|
||||
|
||||
function initIconPickers() {
|
||||
document.querySelectorAll('[data-icon-picker]').forEach(function (container) {
|
||||
if (container.dataset.iconPickerInit) return;
|
||||
container.dataset.iconPickerInit = '1';
|
||||
|
||||
var input = container.querySelector('input[type="hidden"]');
|
||||
var toggle = container.querySelector('.icon-picker-toggle');
|
||||
var menu = container.querySelector('.icon-picker-menu');
|
||||
var search = container.querySelector('.icon-picker-search');
|
||||
var grid = container.querySelector('.icon-picker-grid');
|
||||
var clearBtn = container.querySelector('.icon-picker-clear');
|
||||
|
||||
function renderGrid(filter) {
|
||||
filter = (filter || '').toLowerCase();
|
||||
var list = COMMON_ICONS.filter(function (ic) { return !filter || ic.indexOf(filter) !== -1; });
|
||||
grid.innerHTML = list.map(function (ic) {
|
||||
return '<button type="button" class="icon-picker-item" data-icon="' + ic + '" title="' + ic + '"><i class="bi ' + ic + '"></i></button>';
|
||||
}).join('') || '<div class="text-body-secondary small p-2">No matches</div>';
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = menu.classList.contains('show');
|
||||
closeAllIconPickers();
|
||||
if (!isOpen) {
|
||||
menu.classList.add('show');
|
||||
search.value = '';
|
||||
renderGrid('');
|
||||
search.focus();
|
||||
}
|
||||
});
|
||||
|
||||
search.addEventListener('input', function () { renderGrid(search.value.trim()); });
|
||||
|
||||
grid.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.icon-picker-item');
|
||||
if (!btn) return;
|
||||
setIconPickerValue(input, btn.dataset.icon);
|
||||
menu.classList.remove('show');
|
||||
});
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', function () {
|
||||
setIconPickerValue(input, '');
|
||||
menu.classList.remove('show');
|
||||
});
|
||||
}
|
||||
|
||||
renderGrid('');
|
||||
setIconPickerValue(input, input.value);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('.icon-picker')) closeAllIconPickers();
|
||||
});
|
||||
|
||||
initIconPickers();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Folder create / rename modal
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -357,7 +460,7 @@
|
||||
document.getElementById('folderFormParentId').value = (opts && opts.parentId) || '';
|
||||
document.getElementById('folderFormName').value = '';
|
||||
document.getElementById('folderFormDescription').value = '';
|
||||
document.getElementById('folderFormIcon').value = '';
|
||||
setIconPickerValue(document.getElementById('folderFormIcon'), '');
|
||||
document.getElementById('folderFormSortOrder').value = '0';
|
||||
document.getElementById('folderModalTitle').textContent = 'New Folder';
|
||||
document.getElementById('folderFormError').classList.add('d-none');
|
||||
@@ -372,7 +475,7 @@
|
||||
document.getElementById('folderFormParentId').value = '';
|
||||
document.getElementById('folderFormName').value = f.Name;
|
||||
document.getElementById('folderFormDescription').value = f.Description;
|
||||
document.getElementById('folderFormIcon').value = f.Icon;
|
||||
setIconPickerValue(document.getElementById('folderFormIcon'), f.Icon);
|
||||
document.getElementById('folderFormSortOrder').value = f.SortOrder;
|
||||
document.getElementById('folderModalTitle').textContent = 'Rename Folder';
|
||||
document.getElementById('folderFormError').classList.add('d-none');
|
||||
@@ -658,6 +761,15 @@
|
||||
var importModalEl = document.getElementById('importModal');
|
||||
var importModal = (importModalEl && window.bootstrap) ? new bootstrap.Modal(importModalEl) : null;
|
||||
|
||||
function openImportModal(folderId) {
|
||||
if (!importModal) return;
|
||||
document.getElementById('importFolderId').value = folderId || '';
|
||||
document.getElementById('importResult').innerHTML = '';
|
||||
var fileInput = document.getElementById('importFile');
|
||||
if (fileInput) fileInput.value = '';
|
||||
importModal.show();
|
||||
}
|
||||
|
||||
var importFormEl = document.getElementById('importForm');
|
||||
if (importFormEl) {
|
||||
importFormEl.addEventListener('submit', async function (e) {
|
||||
|
||||
Reference in New Issue
Block a user