feat(core): detail drawer + address book list redesign
Introduces a reusable core detail-drawer primitive that slides in from the right and loads any view via a `*-fragment(none).phtml` endpoint. Bundles the address book list overhaul that is its first consumer. Core additions: - `app-detail-drawer.js` — generic drawer with stepper, focus trap, body scroll-lock, URL-hash deep-linking, session-expiry detection - `app-fragment-init.js` — auto-wires tabs/lookups/confirm/file-upload/ fslightbox inside injected HTML; consumers do not re-initialize components - `app-focus-trap.js` — shared focus-trap + refcounted scroll-lock, used by both filter-drawer and detail-drawer - `getHtml()` in `app-http.js` + `SessionExpiredError`; drawer reloads the page on auth redirect instead of rendering the login form in the panel - `DetailDrawerFragmentContractTest` enforces that every `initDetailDrawer` consumer ships matching `*-fragment($id).php` + `*-fragment(none).phtml` Address book list: - Grid collapses from 9 columns to 4 (identity / context / phone / actions) with a two-line identity cell (avatar + name + email) - Tenant register tabs above the grid using the `app-list-tabs` partial; tenant filter wired via hidden toolbar field so grid.js forwards it on every data fetch - Profile body extracted to a shared partial so the full-page view and the new drawer fragment share the same markup - New i18n keys for the drawer/list labels Also refactors `app-filter-drawer` to reuse the shared focus-trap and scroll-lock instead of maintaining its own copy, and documents the detail-drawer convention in CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,11 +135,29 @@ class AddressBookService
|
||||
);
|
||||
}
|
||||
|
||||
$activeTenant = trim((string) ($query['tenant'] ?? ''));
|
||||
|
||||
$tenantTabs = array_values(array_filter(array_map(
|
||||
static function (array $tenant): array {
|
||||
return [
|
||||
'uuid' => (string) ($tenant['uuid'] ?? ''),
|
||||
'description' => (string) ($tenant['description'] ?? ''),
|
||||
];
|
||||
},
|
||||
$tenants
|
||||
), static fn (array $tab): bool => $tab['uuid'] !== ''));
|
||||
usort($tenantTabs, static fn (array $a, array $b): int => strnatcasecmp(
|
||||
(string) $a['description'],
|
||||
(string) $b['description']
|
||||
));
|
||||
|
||||
return [
|
||||
'activeTenants' => $activeTenants,
|
||||
'activeRoles' => $activeRoles,
|
||||
'activeDepartments' => $activeDepartments,
|
||||
'activeTenant' => $activeTenant,
|
||||
'tenantItems' => $tenantItems,
|
||||
'tenantTabs' => $tenantTabs,
|
||||
'departments' => $departments,
|
||||
'roles' => $roles,
|
||||
'customFieldFilterDefinitions' => $customFieldFilterDefinitions,
|
||||
@@ -191,6 +209,10 @@ class AddressBookService
|
||||
$displayName = (string) ($row['email'] ?? '');
|
||||
}
|
||||
|
||||
$primaryTenantLabel = $tenantList[0] ?? '';
|
||||
$primaryDepartmentLabel = $departmentList[0] ?? '';
|
||||
$extraAssignmentsCount = max(0, count($tenantList) - 1) + max(0, count($departmentList) - 1);
|
||||
|
||||
$rows[] = [
|
||||
'uuid' => $uuid,
|
||||
'display_name' => $displayName,
|
||||
@@ -203,6 +225,9 @@ class AddressBookService
|
||||
'tenants' => $tenantList,
|
||||
'departments' => $departmentList,
|
||||
'roles' => $roleList,
|
||||
'primary_tenant_label' => $primaryTenantLabel,
|
||||
'primary_department_label' => $primaryDepartmentLabel,
|
||||
'extra_assignments_count' => $extraAssignmentsCount,
|
||||
'has_avatar' => $uuid !== '' && $this->avatarService->hasAvatar($uuid),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user