Files
breadcrumb-the-shire/web/js/pages/address-book-index.js
2026-03-06 00:44:52 +01:00

210 lines
7.9 KiB
JavaScript

import { initMultiSelect } from '../components/app-multiselect-init.js';
import { initMultiSelectCascade } from '../components/app-multiselect-cascade.js';
import { initStandardListPage } from '../core/app-grid-factory.js';
import { readPageConfig } from '../core/app-page-config.js';
import { warnOnce } from '../core/app-dom.js';
import { getAppBase, escapeHtml, buildBadgeList, gridFilterMultiCsv, gridFilterText } from './app-list-utils.js';
const config = readPageConfig('address-book-index');
if (config) {
const appBase = getAppBase();
const gridSearch = config.gridSearch ?? null;
const filterSchema = config.filterSchema ?? [];
const filterChipMeta = config.filterChipMeta ?? [];
const labels = config.labels ?? {};
const toolbar = document.querySelector('#address-book-drawer-toolbar');
const saveFilterButton = document.querySelector('[data-address-book-save-filter]');
const saveFilterForm = document.querySelector('#address-book-save-filter-form');
const customFilterInputs = Array.from(document.querySelectorAll('[data-address-book-custom-filter]'));
const tenantDepartmentMap = toolbar?.dataset?.tenantDeptMap
? JSON.parse(toolbar.dataset.tenantDeptMap)
: {};
const uuidIndex = 0;
const normalizeCommaSeparated = (value) => {
if (Array.isArray(value)) {return value.filter(Boolean);}
return String(value || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean);
};
const initialsForName = (name) => {
const parts = String(name || '')
.trim()
.split(/\s+/)
.filter(Boolean);
const chars = parts.slice(0, 2).map((value) => value[0] || '').join('').toUpperCase();
return chars || '?';
};
const customFilters = customFilterInputs.map((input) => {
const param = String(input?.dataset?.addressBookCustomFilterParam || '').trim();
if (!param) {return null;}
if (input.dataset.addressBookCustomFilterType === 'multiselect') {
const targetSelector = input.dataset.addressBookCustomFilterInput || '';
return gridFilterMultiCsv(targetSelector, param, { default: [] });
}
return gridFilterText(input, param, { default: '' });
}).filter(Boolean);
const gridOptions = {
gridjs: window.gridjs,
container: '#address-book-grid',
dataUrl: 'address-book/data',
appBase,
columns: [
{ name: 'UUID', hidden: true },
{
name: labels.name || 'Name',
sort: true,
formatter: (cell) => {
const nameValue = typeof cell === 'object' && cell !== null
? (cell.name ?? '')
: cell;
const avatarUuid = typeof cell === 'object' && cell !== null
? (cell.avatar_uuid ?? '')
: '';
const name = escapeHtml(nameValue || '-');
if (!avatarUuid) {
const initials = escapeHtml(initialsForName(nameValue));
return gridjs.html(`<span class="grid-name-cell"><span class="grid-avatar grid-avatar-placeholder">${initials}</span><span>${name}</span></span>`);
}
const src = new URL(`admin/users/avatar-file?uuid=${avatarUuid}&size=64`, appBase).toString();
const full = new URL(`admin/users/avatar-file?uuid=${avatarUuid}&size=256`, appBase).toString();
return gridjs.html(`<span class="grid-name-cell"><a data-fslightbox="address-book-avatars" href="${full}"><img class="grid-avatar" src="${src}" alt="" loading="lazy"></a><span>${name}</span></span>`);
},
},
{ name: labels.email || 'Email', sort: true },
{ name: labels.phone || 'Phone', sort: false },
{ name: labels.mobile || 'Mobile', sort: false },
{ name: labels.shortDial || 'Short dial', sort: false },
{ name: labels.tenants || 'Tenants', sort: false, formatter: (cell) => gridjs.html(buildBadgeList(cell)) },
{ name: labels.departments || 'Departments', sort: false, formatter: (cell) => gridjs.html(buildBadgeList(cell)) },
{ name: labels.roles || 'Roles', sort: false, formatter: (cell) => gridjs.html(buildBadgeList(cell)) },
],
sortColumns: [null, 'display_name', 'email', null, null, null, null, null, null],
paginationLimit: 10,
language: config.gridLang ?? {},
mapData: (data) => data.data.map((row) => [
row.uuid,
{
name: row.display_name,
avatar_uuid: row.has_avatar ? row.uuid : '',
},
row.email,
row.phone,
row.mobile,
row.short_dial,
row.tenants,
row.departments,
row.roles,
]),
search: gridSearch,
filterSchema,
extraFilters: customFilters,
urlSync: true,
rowDataset: (row) => ({
uuid: row?.cells?.[uuidIndex]?.data,
}),
rowDblClick: {
getUrl: (rowData) => {
const uuid = rowData?.cells?.[uuidIndex]?.data;
return uuid ? new URL(`address-book/view/${uuid}`, appBase).toString() : '';
},
},
};
const { gridConfig } = initStandardListPage({
grid: gridOptions,
filters: {
mode: 'drawer',
chipMeta: filterChipMeta,
watchInputs: ['#address-book-search'],
},
});
if (!gridConfig || !gridConfig.grid) {
warnOnce('UI_INIT_FAIL', 'Address book grid init failed', { module: 'address-book-index', component: 'grid' });
}
initMultiSelect().then(() => {
initMultiSelectCascade({
parent: '#address-book-tenant-filter',
child: '#address-book-department-filter',
map: tenantDepartmentMap,
mode: 'disable',
clearInvalid: true,
});
});
const normalizeJoined = (value) => normalizeCommaSeparated(value).join(',');
if (saveFilterButton && saveFilterForm) {
saveFilterButton.addEventListener('click', () => {
if (!gridConfig) {
return;
}
const promptText = saveFilterButton.dataset.saveFilterPrompt || '';
const emptyText = saveFilterButton.dataset.saveFilterEmpty || '';
const enteredName = window.prompt(promptText, '');
if (enteredName === null) {
return;
}
const name = String(enteredName).trim();
if (!name) {
if (emptyText) {
window.alert(emptyText);
}
return;
}
const appliedQuery = new URL(gridConfig.baseUrl()).searchParams;
const state = {
search: String(appliedQuery.get('search') || '').trim(),
tenants: normalizeJoined(appliedQuery.get('tenants') || ''),
departments: normalizeJoined(appliedQuery.get('departments') || ''),
roles: normalizeJoined(appliedQuery.get('roles') || ''),
custom: {},
};
appliedQuery.forEach((value, key) => {
if (!/^(cf_|cfm_|cfd_)/i.test(key)) {
return;
}
const cleanedValue = String(value || '').trim();
if (cleanedValue === '') {
return;
}
state.custom[key] = cleanedValue;
});
const nameInput = saveFilterForm.querySelector('input[name="name"]');
const searchInput = saveFilterForm.querySelector('input[name="search"]');
const tenantsInput = saveFilterForm.querySelector('input[name="tenants"]');
const departmentsInput = saveFilterForm.querySelector('input[name="departments"]');
const rolesInput = saveFilterForm.querySelector('input[name="roles"]');
const extrasContainer = saveFilterForm.querySelector('[data-address-book-save-filter-extra]');
if (!nameInput || !searchInput || !tenantsInput || !departmentsInput || !rolesInput) {
return;
}
nameInput.value = name;
searchInput.value = state.search;
tenantsInput.value = state.tenants;
departmentsInput.value = state.departments;
rolesInput.value = state.roles;
if (extrasContainer) {
extrasContainer.innerHTML = '';
Object.entries(state.custom || {}).forEach(([key, value]) => {
const hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = key;
hidden.value = String(value ?? '');
extrasContainer.appendChild(hidden);
});
}
saveFilterForm.requestSubmit();
});
}
}