forked from fa/breadcrumb-the-shire
Replace the inline export implementation with the new generic primitive
(core/Service/Export + helpers/export + app-list-export.js). No more
hand-rolled fputcsv loop, header setup, or escape closure duplicated
here — one implementation, tested once, reused everywhere.
- export().php: uses exportRequireGetRequest, exportCapLimit,
exportResolveFlavor, ExportColumn[], exportSendCsv. Phone/mobile/
short_dial keep allowSignedNumeric=true so +49 numbers render
untouched.
- export(none).phtml: deleted — headers + fputcsv now live in the
core helper.
- index(default).phtml: inline <details class="dropdown"> "Export CSV"
replaced by the reusable app-list-export-dropdown partial, exposing
CSV and Excel flavors. exportUrl added to pageConfig.
- app-users-list.js: hand-rolled export-button binding removed in
favor of initListExport({ gridConfig, exportUrl }).
- admin-users-index.js: forwards config.exportUrl into
initUsersListPage.
Net effect: ~100 lines of duplicated export plumbing removed from the
users page; users gain the Excel flavor for free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
4.7 KiB
JavaScript
146 lines
4.7 KiB
JavaScript
import { confirmDialog } from '../core/app-confirm-dialog.js';
|
|
import { warnOnce } from '../core/app-dom.js';
|
|
import { postForm } from '../core/app-http.js';
|
|
import { initListExport } from '../core/app-list-export.js';
|
|
|
|
export function initUsersListPage(options = {}) {
|
|
const {
|
|
gridConfig,
|
|
appBase,
|
|
csrf,
|
|
labels = {},
|
|
selectors = {},
|
|
bulkDownloadForms = {},
|
|
exportUrl,
|
|
listPath = 'admin/users',
|
|
resetOptions = {},
|
|
buildBulkUrl,
|
|
showFlash,
|
|
withLoading
|
|
} = options;
|
|
|
|
if (!gridConfig) {return;}
|
|
|
|
const resetSelector = selectors.resetButton || '[data-users-reset]';
|
|
const bulkSelector = selectors.bulkButtons || '[data-users-bulk]';
|
|
|
|
const resolveBulkUrl = buildBulkUrl
|
|
? buildBulkUrl
|
|
: (action) => new URL(`admin/users/bulk/${action}`, appBase).toString();
|
|
|
|
const postAction = async (url, data = {}) => {
|
|
const body = new URLSearchParams({ ...data, [csrf.key]: csrf.token });
|
|
return postForm(url, body);
|
|
};
|
|
|
|
if (exportUrl) {
|
|
initListExport({ gridConfig, exportUrl });
|
|
}
|
|
|
|
const resetButton = document.querySelector(resetSelector);
|
|
if (resetButton) {
|
|
resetButton.addEventListener('click', () => {
|
|
if (typeof gridConfig.resetFilters === 'function') {
|
|
gridConfig.resetFilters(resetOptions);
|
|
} else {
|
|
gridConfig.updateGrid(resetOptions);
|
|
}
|
|
document.dispatchEvent(new CustomEvent('app:filters-updated'));
|
|
});
|
|
}
|
|
|
|
const bulkButtons = Array.from(document.querySelectorAll(bulkSelector));
|
|
if (bulkButtons.length) {
|
|
bulkButtons.forEach((button) => {
|
|
button.addEventListener('click', async () => {
|
|
const action = button.getAttribute('data-users-bulk') || '';
|
|
const ids = gridConfig.selection?.getSelectedIds?.() || [];
|
|
const validActions = ['activate', 'deactivate', 'delete', 'send-access', 'access-pdf'];
|
|
if (!ids.length || !validActions.includes(action)) {
|
|
if (action && !validActions.includes(action)) {
|
|
warnOnce('UI_ACTION_UNSUPPORTED', `Unsupported users bulk action: ${action}`, {
|
|
module: 'users-list',
|
|
component: 'bulk-action',
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
const confirmTexts = {
|
|
'activate': labels.bulkActivateConfirm,
|
|
'deactivate': labels.bulkDeactivateConfirm,
|
|
'delete': labels.bulkDeleteConfirm,
|
|
'send-access': labels.bulkSendAccessConfirm,
|
|
'access-pdf': labels.bulkAccessPdfConfirm
|
|
};
|
|
const confirmText = confirmTexts[action];
|
|
if (confirmText) {
|
|
const approved = await confirmDialog.confirm({
|
|
message: confirmText,
|
|
actionKind: action,
|
|
});
|
|
if (!approved) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const downloadFormSelector = bulkDownloadForms?.[action];
|
|
if (downloadFormSelector) {
|
|
const downloadForm = document.querySelector(downloadFormSelector);
|
|
if (!downloadForm) {
|
|
window.location.href = new URL(listPath, appBase).toString();
|
|
return;
|
|
}
|
|
const uuidsInput = downloadForm.querySelector('input[name="uuids"]');
|
|
if (uuidsInput) {
|
|
uuidsInput.value = ids.join(',');
|
|
}
|
|
downloadForm.submit();
|
|
return;
|
|
}
|
|
|
|
const executeBulk = async () => {
|
|
const url = resolveBulkUrl(action);
|
|
try {
|
|
const data = await postAction(url, { uuids: ids.join(',') });
|
|
if (!data || data.ok !== true) {
|
|
throw new Error('Bulk action failed');
|
|
}
|
|
gridConfig.selection?.clear?.();
|
|
gridConfig.grid?.forceRender();
|
|
|
|
if (showFlash && labels.bulkMessages) {
|
|
const msg = labels.bulkMessages[action];
|
|
if (msg) {
|
|
const count = data.count ?? 0;
|
|
const failed = data.failed ?? 0;
|
|
let text = msg.success.replace('%d', count);
|
|
if (failed > 0 && msg.partial) {
|
|
text = msg.partial.replace('%d', count).replace('%f', failed);
|
|
}
|
|
showFlash('success', text);
|
|
}
|
|
}
|
|
} catch {
|
|
if (showFlash && labels.bulkMessages) {
|
|
const msg = labels.bulkMessages[action];
|
|
if (msg?.error) {
|
|
showFlash('error', msg.error);
|
|
} else {
|
|
window.location.href = new URL(listPath, appBase).toString();
|
|
}
|
|
} else {
|
|
window.location.href = new URL(listPath, appBase).toString();
|
|
}
|
|
}
|
|
};
|
|
|
|
if (withLoading) {
|
|
await withLoading(executeBulk);
|
|
} else {
|
|
await executeBulk();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|