diff --git a/pages/admin/users/export().php b/pages/admin/users/export().php index cb1ceb0..d8cbc59 100644 --- a/pages/admin/users/export().php +++ b/pages/admin/users/export().php @@ -4,10 +4,13 @@ use MintyPHP\Http\Request; use MintyPHP\Http\SessionStoreInterface; use MintyPHP\Router; use MintyPHP\Service\Access\UserAuthorizationPolicy; +use MintyPHP\Service\Export\ExportColumn; use MintyPHP\Support\Guard; -$session = app(SessionStoreInterface::class)->all(); Guard::requireLogin(); +exportRequireGetRequest(); + +$session = app(SessionStoreInterface::class)->all(); $authorizationService = app(\MintyPHP\Service\Access\AuthorizationService::class); $decision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_VIEW, [ 'actor_user_id' => (int) ($session['user']['id'] ?? 0), @@ -21,34 +24,28 @@ if (!$decision->isAllowed()) { Router::redirect('error/forbidden?url=' . urlencode(Request::pathWithQuery())); return; } -$userAccountService = app(\MintyPHP\Service\User\UserAccountService::class); +$request = requestInput(); $currentUserId = (int) ($session['user']['id'] ?? 0); -$search = trim((string) (requestInput()->queryAll()['search'] ?? '')); -$order = (string) (requestInput()->queryAll()['order'] ?? 'id'); -$dir = strtolower((string) (requestInput()->queryAll()['dir'] ?? 'desc')) === 'asc' ? 'asc' : 'desc'; -$active = requestInput()->queryAll()['active'] ?? 'all'; -$createdFrom = trim((string) (requestInput()->queryAll()['created_from'] ?? '')); -$createdTo = trim((string) (requestInput()->queryAll()['created_to'] ?? '')); -$tenant = trim((string) (requestInput()->queryAll()['tenant'] ?? '')); -$roles = requestInput()->queryAll()['roles'] ?? ''; -$departments = requestInput()->queryAll()['departments'] ?? ''; +$search = $request->queryString('search'); +$order = (string) $request->query('order', 'id'); +$dir = strtolower((string) $request->query('dir', 'desc')) === 'asc' ? 'asc' : 'desc'; +$active = (string) $request->query('active', 'all'); +$createdFrom = $request->queryString('created_from'); +$createdTo = $request->queryString('created_to'); +$tenant = $request->queryString('tenant'); +$roles = $request->query('roles', ''); +$departments = $request->query('departments', ''); $allowedOrder = ['id', 'uuid', 'first_name', 'last_name', 'email', 'locale', 'theme', 'created', 'modified', 'active']; if (!in_array($order, $allowedOrder, true)) { $order = 'id'; } -$limit = (int) (requestInput()->queryAll()['limit'] ?? 5000); -if ($limit < 1) { - $limit = 5000; -} -if ($limit > 5000) { - $limit = 5000; -} +$limit = exportCapLimit($request->query('limit'), 5000); -$result = $userAccountService->listPaged([ +$result = app(\MintyPHP\Service\User\UserAccountService::class)->listPaged([ 'limit' => $limit, 'offset' => 0, 'search' => $search, @@ -63,5 +60,48 @@ $result = $userAccountService->listPaged([ 'tenantUserId' => $currentUserId, ]); -$exportRows = $result['rows'] ?? []; -$exportFilename = 'users-' . date('Ymd-His') . '.csv'; +$joinLabels = static function (mixed $value): string { + if (is_array($value)) { + return implode(' | ', array_map('strval', $value)); + } + if (is_string($value)) { + $trimmed = trim($value); + if ($trimmed === '') { + return ''; + } + return str_replace('||', ' | ', $trimmed); + } + return ''; +}; + +$formatTimestamp = static function (mixed $value): string { + if ($value === null || $value === '') { + return ''; + } + return (string) dt($value, 'Y-m-d H:i:s'); +}; + +$columns = [ + new ExportColumn('id', static fn (array $r): string => (string) ($r['id'] ?? '')), + new ExportColumn('uuid', static fn (array $r): string => (string) ($r['uuid'] ?? '')), + new ExportColumn('first_name', static fn (array $r): string => (string) ($r['first_name'] ?? '')), + new ExportColumn('last_name', static fn (array $r): string => (string) ($r['last_name'] ?? '')), + new ExportColumn('email', static fn (array $r): string => (string) ($r['email'] ?? '')), + new ExportColumn('phone', static fn (array $r): string => (string) ($r['phone'] ?? ''), allowSignedNumeric: true), + new ExportColumn('mobile', static fn (array $r): string => (string) ($r['mobile'] ?? ''), allowSignedNumeric: true), + new ExportColumn('short_dial', static fn (array $r): string => (string) ($r['short_dial'] ?? ''), allowSignedNumeric: true), + new ExportColumn('locale', static fn (array $r): string => (string) ($r['locale'] ?? '')), + new ExportColumn('theme', static fn (array $r): string => (string) ($r['theme'] ?? '')), + new ExportColumn('active', static fn (array $r): int => (int) ($r['active'] ?? 0)), + new ExportColumn('tenants', static fn (array $r): string => $joinLabels($r['tenant_labels'] ?? [])), + new ExportColumn('departments', static fn (array $r): string => $joinLabels($r['department_labels'] ?? [])), + new ExportColumn('roles', static fn (array $r): string => $joinLabels($r['role_labels'] ?? [])), + new ExportColumn('created_by', static fn (array $r): string => (string) ($r['created_by'] ?? '')), + new ExportColumn('modified_by', static fn (array $r): string => (string) ($r['modified_by'] ?? '')), + new ExportColumn('created', static fn (array $r): string => $formatTimestamp($r['created'] ?? '')), + new ExportColumn('modified', static fn (array $r): string => $formatTimestamp($r['modified'] ?? '')), +]; + +$filename = 'users-' . date('Ymd-His') . '.csv'; + +exportSendCsv($filename, $result['rows'] ?? [], $columns, exportResolveFlavor($request)); diff --git a/pages/admin/users/export(none).phtml b/pages/admin/users/export(none).phtml deleted file mode 100644 index d143521..0000000 --- a/pages/admin/users/export(none).phtml +++ /dev/null @@ -1,75 +0,0 @@ - - + @@ -119,6 +110,7 @@ $pageConfig = [ 'gridSearch' => $searchConfig, 'filterSchema' => $clientFilterSchema, 'filterChipMeta' => $filterChipMeta, + 'exportUrl' => lurl('admin/users/export'), 'currentUserUuid' => (string) $currentUserUuid, 'canUpdateUsers' => (bool) $canUpdateUsers, 'canUpdateSelf' => (bool) $canUpdateSelf, diff --git a/web/js/pages/admin-users-index.js b/web/js/pages/admin-users-index.js index 6329f0f..783151b 100644 --- a/web/js/pages/admin-users-index.js +++ b/web/js/pages/admin-users-index.js @@ -171,6 +171,7 @@ createListPageModule({ appBase, csrf, labels, + exportUrl: config.exportUrl, buildBulkUrl, bulkDownloadForms: { 'access-pdf': '#users-access-pdf-bulk-form', diff --git a/web/js/pages/app-users-list.js b/web/js/pages/app-users-list.js index ce85106..92e568e 100644 --- a/web/js/pages/app-users-list.js +++ b/web/js/pages/app-users-list.js @@ -1,6 +1,7 @@ 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 { @@ -10,7 +11,7 @@ export function initUsersListPage(options = {}) { labels = {}, selectors = {}, bulkDownloadForms = {}, - exportPath = 'admin/users/export', + exportUrl, listPath = 'admin/users', resetOptions = {}, buildBulkUrl, @@ -20,7 +21,6 @@ export function initUsersListPage(options = {}) { if (!gridConfig) {return;} - const exportSelector = selectors.exportButton || '[data-users-export]'; const resetSelector = selectors.resetButton || '[data-users-reset]'; const bulkSelector = selectors.bulkButtons || '[data-users-bulk]'; @@ -33,19 +33,8 @@ export function initUsersListPage(options = {}) { return postForm(url, body); }; - const exportButton = document.querySelector(exportSelector); - if (exportButton) { - exportButton.addEventListener('click', () => { - const exportUrl = new URL(exportPath, appBase); - const dataUrl = new URL(gridConfig.baseUrl()); - dataUrl.searchParams.forEach((value, key) => exportUrl.searchParams.set(key, value)); - const sortParams = gridConfig.getSortParams ? gridConfig.getSortParams() : null; - if (sortParams && sortParams.order) { - exportUrl.searchParams.set('order', sortParams.order); - exportUrl.searchParams.set('dir', sortParams.dir || 'asc'); - } - window.location.href = exportUrl.toString(); - }); + if (exportUrl) { + initListExport({ gridConfig, exportUrl }); } const resetButton = document.querySelector(resetSelector);