diff --git a/core/Service/User/UserProfileViewService.php b/core/Service/User/UserProfileViewService.php index 60ab37e..8d4bad8 100644 --- a/core/Service/User/UserProfileViewService.php +++ b/core/Service/User/UserProfileViewService.php @@ -124,6 +124,37 @@ class UserProfileViewService ]; } + /** + * Extended profile for admin contexts. Returns the same shape as + * buildProfile() plus `$user['admin_extras']` with operational fields + * that are inappropriate for non-admin views (email verification state, + * last login, active status, created/modified timestamps). + * + * @api Called from pages/admin/users/view-fragment($id).php via app(...). + * + * @return array{status: string, user?: array} + */ + public function buildAdminProfile(int $currentUserId, string $uuid): array + { + $profile = $this->buildProfile($currentUserId, $uuid); + if ($profile['status'] !== 'ok' || !isset($profile['user'])) { + return $profile; + } + + $user = $profile['user']; + $user['admin_extras'] = [ + 'active' => (bool) ($user['active'] ?? false), + 'email_verified_at' => (string) ($user['email_verified_at'] ?? ''), + 'last_login_at' => (string) ($user['last_login_at'] ?? ''), + 'last_login_provider' => (string) ($user['last_login_provider'] ?? ''), + 'created' => (string) ($user['created'] ?? ''), + 'modified' => (string) ($user['modified'] ?? ''), + ]; + $profile['user'] = $user; + + return $profile; + } + /** * @param array $values * @return list diff --git a/i18n/default_de.json b/i18n/default_de.json index 3f6b2e3..0ec6519 100644 --- a/i18n/default_de.json +++ b/i18n/default_de.json @@ -1,6 +1,7 @@ { "Login": "Login", "Login credentials": "Mit Zugangsdaten oder SSO anmelden", + "Login provider": "Login-Anbieter", "Register": "Registrieren", "Password": "Passwort", "Or": "Oder", diff --git a/i18n/default_en.json b/i18n/default_en.json index a477bfa..d35b8a2 100644 --- a/i18n/default_en.json +++ b/i18n/default_en.json @@ -1,6 +1,7 @@ { "Login": "Login", "Login credentials": "Login credentials", + "Login provider": "Login provider", "Register": "Register", "Password": "Password", "Or": "Or", diff --git a/pages/admin/users/view-fragment($id).php b/pages/admin/users/view-fragment($id).php index 724b781..dfd7c98 100644 --- a/pages/admin/users/view-fragment($id).php +++ b/pages/admin/users/view-fragment($id).php @@ -25,7 +25,7 @@ if (!$decision->isAllowed()) { return; } -$profile = app(UserProfileViewService::class)->buildProfile($currentUserId, $uuid); +$profile = app(UserProfileViewService::class)->buildAdminProfile($currentUserId, $uuid); $status = (string) ($profile['status'] ?? ''); if ($status === 'not_found' || $status === 'invalid_uuid') { http_response_code(404); diff --git a/pages/admin/users/view-fragment(none).phtml b/pages/admin/users/view-fragment(none).phtml index 9db0481..559934d 100644 --- a/pages/admin/users/view-fragment(none).phtml +++ b/pages/admin/users/view-fragment(none).phtml @@ -3,4 +3,4 @@ if (!isset($user) || !is_array($user)) { return; } $profileKey = 'admin-users-profile'; -require templatePath('partials/app-user-profile.phtml'); +require templatePath('partials/app-admin-user-profile.phtml'); diff --git a/templates/partials/app-admin-user-profile.phtml b/templates/partials/app-admin-user-profile.phtml new file mode 100644 index 0000000..eb56673 --- /dev/null +++ b/templates/partials/app-admin-user-profile.phtml @@ -0,0 +1,264 @@ +format($format); + } catch (\Exception $e) { + return $input; + } +}; + +$hireDateLabel = $formatDate($hireDate, $localeFormat); +$lastLoginLabel = $formatDate((string) ($adminExtras['last_login_at'] ?? ''), $localeFormatTime); +$emailVerifiedLabel = $formatDate((string) ($adminExtras['email_verified_at'] ?? ''), $localeFormatTime); +$createdLabel = $formatDate((string) ($adminExtras['created'] ?? ''), $localeFormatTime); +$modifiedLabel = $formatDate((string) ($adminExtras['modified'] ?? ''), $localeFormatTime); +$lastLoginProvider = trim((string) ($adminExtras['last_login_provider'] ?? '')); +$isActive = (bool) ($adminExtras['active'] ?? false); + +$initials = ''; +if ($displayName !== '') { + $parts = array_filter(array_map('trim', preg_split('/\s+/', $displayName) ?: [])); + $chars = ''; + foreach ($parts as $part) { + if (function_exists('mb_substr')) { + $chars .= mb_substr($part, 0, 1); + } else { + $chars .= substr($part, 0, 1); + } + } + $initials = strtoupper($chars !== '' ? $chars : '?'); +} + +$hasContact = ($email !== '' || $phone !== '' || $mobile !== '' || $shortDial !== ''); +$hasAddress = ($address !== '' || $postalCode !== '' || $city !== '' || $region !== '' || $country !== ''); +$hasOrganization = (!empty($tenantGroups) || !empty($roleLabels)); +?> + +
+
+
+
+
+
+
+ + + + + + + +
+
+
+

+

+ + + + + + + + +

+
+
+
+
+
+
+
+
+
+ + + + + + +
+ +
+ +
+ +
+ +

+
+ + +
+ +

+
+ +
+
+ +
+ +

+
+ + +
+ +

+
+ +
+ + +
+ +

+ +

+

+

+
+ + +

-

+ +
+ + +
+ + + + + + + + + + + + + + + + + +
+ + + () + +
+ + +
+ +

+
+ +
+ + +
+
+
+ +

+ + + +

+
+
+ +

+
+
+
+
+ +

+
+ +
+ +

+
+ +
+
+ +
+ +

+
+ + +
+ +

+
+ +
+
+ +
+ +
+ +

+
+ + + + +

+ + + +

-

+ +
+
+
+
+
+
diff --git a/web/css/components/app-detail-drawer.css b/web/css/components/app-detail-drawer.css index 601bf87..dcc6d66 100644 --- a/web/css/components/app-detail-drawer.css +++ b/web/css/components/app-detail-drawer.css @@ -126,7 +126,9 @@ border-left: none; border-top: 1px solid var(--app-border); min-height: 0; - padding: calc(var(--app-spacing) * 1.5) 0 0; + /* Match the inline padding of `section > *` (from app-details.css) so the + aside block aligns visually with the main content above it. */ + padding: calc(var(--app-spacing) * 1.5) calc(var(--app-spacing) * 2) 0; margin-top: calc(var(--app-spacing) * 1.5); } }