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 @@
-
-" data-tooltip-pos="top">
-
-
-
+
@@ -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);