Files
breadcrumb-the-shire/pages/admin/users/index(default).phtml
fs 4d1bd13a3b refactor(ui): move detail drawer chrome to a server-rendered partial
The drawer chrome used to be built in JS via a 50-line innerHTML string
and needed every caller to plumb through a labels object sourced from
per-page PHP config. That diverged from the codebase convention — the
confirm dialog, search dialog and session warning are all PHP partials
mounted once in templates/default.phtml. The drawer now follows the
same pattern: templates/partials/app-detail-drawer.phtml renders the
markup with t() labels, default.phtml mounts it inside the logged-in
shell, and the JS only attaches behavior via the [data-detail-drawer]
selector.

Knock-on cleanup: ensureDrawerElement and resolveLabels disappear from
the JS, all three initDetailDrawer callers (admin/users, address book,
helpdesk debitor) drop their labels parameter, and the matching dead
'drawerClose'/'drawerPrev'/… entries leave the per-page PHP grid configs.
The drawer also gains a clean fallback when the partial is missing
(console warn + null return), so logged-out edges can't crash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:06:06 +02:00

160 lines
6.0 KiB
PHTML

<?php
use MintyPHP\Router;
use MintyPHP\Session;
use MintyPHP\Http\Request;
$toolbarFilterSchema = is_array($toolbarFilterSchema ?? null) ? $toolbarFilterSchema : [];
$searchToolbarFilterSchema = is_array($searchToolbarFilterSchema ?? null) ? $searchToolbarFilterSchema : [];
$drawerToolbarFilterSchema = is_array($drawerToolbarFilterSchema ?? null) ? $drawerToolbarFilterSchema : [];
$toolbarFilterState = is_array($toolbarFilterState ?? null) ? $toolbarFilterState : [];
$toolbarOptionSets = is_array($toolbarOptionSets ?? null) ? $toolbarOptionSets : [];
$clientFilterSchema = is_array($clientFilterSchema ?? null) ? $clientFilterSchema : [];
$filterChipMeta = is_array($filterChipMeta ?? null) ? $filterChipMeta : [];
$searchConfig = is_array($searchConfig ?? null) ? $searchConfig : null;
$activeTenant = (string) ($toolbarFilterState['tenant'] ?? '');
$pageAuth = is_array($viewAuth['page'] ?? null) ? $viewAuth['page'] : [];
$canDeleteUsers = (bool) ($pageAuth['can_delete_users'] ?? false);
$canUpdateUsers = (bool) ($pageAuth['can_update_users'] ?? false);
$canAccessPdf = (bool) ($pageAuth['can_access_pdf'] ?? false);
$canUpdateSelf = (bool) ($pageAuth['can_update_self'] ?? false);
$canCreateUser = (bool) ($pageAuth['can_create_user'] ?? false);
$currentUserUuid = (string) ($_SESSION['user']['uuid'] ?? '');
$showTenantTabs = isset($tenants) && is_array($tenants) && count($tenants) > 1;
?>
<?php
$listTitle = t('Users');
ob_start();
?>
<button type="button" class="secondary outline" data-users-bulk="activate" hidden>
<i class="bi bi-check-circle"></i> <?php e(t('Activate')); ?>
</button>
<button type="button" class="secondary outline" data-users-bulk="deactivate" hidden>
<i class="bi bi-ban"></i> <?php e(t('Deactivate')); ?>
</button>
<?php if ($canUpdateUsers): ?>
<button type="button" class="secondary outline" data-users-bulk="send-access" hidden>
<i class="bi bi-envelope"></i> <?php e(t('Send access')); ?>
</button>
<?php endif; ?>
<?php if ($canAccessPdf): ?>
<button type="button" class="secondary outline" data-users-bulk="access-pdf" hidden>
<i class="bi bi-file-earmark-pdf"></i> <?php e(t('Download access PDFs')); ?>
</button>
<?php endif; ?>
<?php if ($canDeleteUsers): ?>
<button type="button" class="danger outline" data-users-bulk="delete" hidden>
<i class="bi bi-trash3-fill"></i> <?php e(t('Delete')); ?>
</button>
<?php endif; ?>
<?php require templatePath('partials/app-list-export-dropdown.phtml'); ?>
<?php if ($canCreateUser): ?>
<a role="button" class="app-action-success" href="<?php e(requestPathWithReturnTarget('admin/users/create', Request::pathWithQuery())); ?>">
<i class="bi bi-plus"></i> <?php e(t('Create user')); ?>
</a>
<?php endif; ?>
<?php
$listTitleActionsHtml = ob_get_clean();
require templatePath('partials/app-list-titlebar.phtml');
?>
<?php if ($showTenantTabs): ?>
<?php
$listTabsId = 'users-tabs';
$listTabsItems = [
[
'href' => 'admin/users',
'label' => t('All'),
'active' => $activeTenant === '',
],
];
foreach ($tenants ?? [] as $tenant) {
$tenantUuid = (string) ($tenant['uuid'] ?? '');
if ($tenantUuid === '') {
continue;
}
$listTabsItems[] = [
'href' => 'admin/users?tenant=' . rawurlencode($tenantUuid),
'label' => (string) ($tenant['description'] ?? ''),
'active' => $activeTenant === $tenantUuid,
];
}
require templatePath('partials/app-list-tabs.phtml');
?>
<?php endif; ?>
<?php
$filterUiNamespace = 'users';
require templatePath('partials/app-list-filters.phtml');
?>
<div class="app-list-table">
<div id="users-grid"></div>
</div>
<?php if ($canAccessPdf): ?>
<form id="users-access-pdf-bulk-form" method="post" action="admin/users/access-pdf-bulk" hidden>
<?php Session::getCsrfInput(); ?>
<input type="hidden" name="uuids" value="">
</form>
<?php endif; ?>
<?php
$gridLang = json_decode(appBufferValue('grid_lang'), true);
if (!is_array($gridLang)) {
$gridLang = [];
}
$gridCsrf = json_decode(appBufferValue('grid_csrf'), true);
if (!is_array($gridCsrf)) {
$gridCsrf = [];
}
$pageConfig = [
'gridSearch' => $searchConfig,
'filterSchema' => $clientFilterSchema,
'filterChipMeta' => $filterChipMeta,
'exportUrl' => endpointUrl('admin/users/export'),
'currentUserUuid' => (string) $currentUserUuid,
'canUpdateUsers' => (bool) $canUpdateUsers,
'canUpdateSelf' => (bool) $canUpdateSelf,
'csrf' => $gridCsrf,
'gridLang' => $gridLang,
'labels' => [
'user' => t('User'),
'state' => t('State'),
'tenants' => t('Tenants'),
'lastLogin' => t('Last login'),
'selectAll' => t('Select all'),
'active' => t('Active'),
'inactive' => t('Inactive'),
'never' => t('Never'),
'bulkActivateConfirm' => t('Activate users?'),
'bulkDeactivateConfirm' => t('Deactivate users?'),
'bulkDeleteConfirm' => t('Delete users?'),
'bulkSendAccessConfirm' => t('Send access emails to selected users?'),
'bulkAccessPdfConfirm' => t('Generate access PDFs for selected users?'),
'primaryTenant' => t('Primary tenant'),
'bulkMessages' => [
'activate' => [
'success' => t('%d users activated'),
'error' => t('Failed to activate users'),
],
'deactivate' => [
'success' => t('%d users deactivated'),
'error' => t('Failed to deactivate users'),
],
'delete' => [
'success' => t('%d users deleted'),
'error' => t('Failed to delete users'),
],
'send-access' => [
'success' => t('Access emails sent to %d users'),
'partial' => t('Access emails sent to %d users, %f failed'),
'error' => t('Failed to send access emails'),
],
],
],
];
?>
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script src="<?php e(assetVersion('vendor/gridjs/plugins/selection/selection.umd.js')); ?>"></script>
<script type="application/json" id="page-config-admin-users-index"><?php gridJsonForJs($pageConfig); ?></script>
<script type="module" src="<?php e(assetVersion('js/pages/admin-users-index.js')); ?>"></script>