big restructure

This commit is contained in:
2026-02-11 19:28:12 +01:00
parent cd59ccd99b
commit 3eb9cc0ac4
209 changed files with 5101 additions and 2459 deletions

View File

@@ -1,28 +1,33 @@
<?php
/**
* @var int $tenantCount
* @var int $departmentCount
* @var int $roleCount
* @var int $userCount
* @var int $permissionCount
* @var int $activeUserCount
* @var int $inactiveUserCount
* @var array $tenantBreakdown
* @var int $mailLogTotalCount
* @var int $activeTenantCount
* @var int $inactiveTenantCount
* @var int $activeDepartmentCount
* @var int $inactiveDepartmentCount
* @var int $activeRoleCount
* @var int $inactiveRoleCount
* @var int $rolesWithoutPermissionsCount
* @var int $permissionsWithoutRolesCount
* @var int $usersWithoutRolesCount
* @var int $usersWithoutTenantsCount
* @var int $departmentsWithoutTenantsCount
* @var int $mailLogSentCount
* @var int $mailLogFailedCount
* @var int $mailLogQueuedCount
* @var int $mailLogTotal24hCount
* @var int $mailLogSent24hCount
* @var int $mailLogFailed24hCount
* @var int $mailLogQueued24hCount
* @var string $mailLogLastSentAt
* @var array $mailLogRecentFailed
*/
?>
<div class="app-breadcrumb">
<a href="/admin"><?php e(t('Home')); ?></a><i class="bi bi-chevron-right"></i><?php e(t('Statistics')); ?>
</div>
<?php
$breadcrumbs = [
['label' => t('Home'), 'path' => 'admin'],
['label' => t('Statistics')],
];
require templatePath('partials/app-breadcrumb.phtml');
?>
<div class="app-dashboard-titlebar">
<h1><?php e(t('Statistics')); ?></h1>
<div class="app-dashboard-titlebar-actions">
@@ -34,250 +39,370 @@
<div class="app-tabs" data-tabs id="stats-tabs">
<!-- Tab Navigation -->
<div class="app-tabs-nav">
<button data-tab="organization" data-tab-default class="transparent tab-button">
<?php e(t('Organization')); ?>
<button data-tab="users" data-tab-default class="transparent tab-button">
<?php e(t('Users')); ?>
</button>
<button data-tab="users-roles" class="transparent tab-button">
<?php e(t('Users & roles')); ?>
<button data-tab="tenants" class="transparent tab-button">
<?php e(t('Tenants')); ?>
</button>
<button data-tab="departments" class="transparent tab-button">
<?php e(t('Departments')); ?>
</button>
<button data-tab="roles-permissions" class="transparent tab-button">
<?php e(t('Roles & permissions')); ?>
</button>
<button data-tab="security" class="transparent tab-button">
<?php e(t('Security')); ?>
</button>
<?php if (can('mail_log.view')): ?>
<button data-tab="system" class="transparent tab-button">
<?php e(t('System')); ?>
<button data-tab="email-security" class="transparent tab-button">
<?php e(t('Email security')); ?>
</button>
<?php endif; ?>
</div>
<!-- Tab Panel: Organization -->
<div data-tab-panel="organization">
<!-- Tab Panel: Users -->
<div data-tab-panel="users">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users?active=active',
'label' => t('Active users'),
'count' => (string) $activeUserCount,
'icon' => 'bi bi-person-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Active users'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
'icon' => 'bi bi-person-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive users'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users?login_status=never',
'label' => t('Never logged in'),
'count' => (string) $usersNeverLoggedCount,
'icon' => 'bi bi-person-slash',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Never logged in'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users?email_verified=unverified',
'label' => t('Email unverified'),
'count' => (string) $usersUnverifiedCount,
'icon' => 'bi bi-shield-exclamation',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Email unverified'),
]);
?>
</div>
<?php $canViewUsers = can('users.view'); ?>
<div class="app-stats-table">
<div class="app-stats-table-header">
<small><?php e(t('Recent logins')); ?></small>
</div>
<?php if (!empty($recentLogins)): ?>
<table>
<thead>
<tr>
<th><?php e(t('User')); ?></th>
<th><?php e(t('Email')); ?></th>
<th><?php e(t('Last login')); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($recentLogins as $row): ?>
<tr>
<td>
<?php $name = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? '')); ?>
<?php if ($canViewUsers && !empty($row['uuid'])): ?>
<a href="admin/users/edit/<?php e($row['uuid']); ?>"><?php e($name); ?></a>
<?php else: ?>
<?php e($name); ?>
<?php endif; ?>
</td>
<td><?php e($row['email'] ?? ''); ?></td>
<td><?php e(dt($row['last_login_at'] ?? '')); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<small class="muted"><?php e(t('No entries found')); ?></small>
<?php endif; ?>
</div>
<div class="app-stats-table">
<div class="app-stats-table-header">
<small><?php e(t('Never logged in')); ?></small>
</div>
<?php if (!empty($neverLoggedUsers)): ?>
<table>
<thead>
<tr>
<th><?php e(t('User')); ?></th>
<th><?php e(t('Email')); ?></th>
<th><?php e(t('Created')); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($neverLoggedUsers as $row): ?>
<tr>
<td>
<?php $name = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? '')); ?>
<?php if ($canViewUsers && !empty($row['uuid'])): ?>
<a href="admin/users/edit/<?php e($row['uuid']); ?>"><?php e($name); ?></a>
<?php else: ?>
<?php e($name); ?>
<?php endif; ?>
</td>
<td><?php e($row['email'] ?? ''); ?></td>
<td><?php e(dt($row['created'] ?? '')); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<small class="muted"><?php e(t('No entries found')); ?></small>
<?php endif; ?>
</div>
</div>
<!-- Tab Panel: Tenants -->
<div data-tab-panel="tenants">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/tenants',
'label' => t('Tenants'),
'count' => (string) $tenantCount,
'icon' => 'bi bi-building',
'label' => t('Active tenants'),
'count' => (string) $activeTenantCount,
'icon' => 'bi bi-buildings',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Active tenants'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/tenants',
'label' => t('Inactive tenants'),
'count' => (string) $inactiveTenantCount,
'icon' => 'bi bi-building-x',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive tenants'),
]);
?>
</div>
</div>
<!-- Tab Panel: Departments -->
<div data-tab-panel="departments">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/departments?active=active',
'label' => t('Active departments'),
'count' => (string) $activeDepartmentCount,
'icon' => 'bi bi-diagram-3-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Active departments'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/departments?active=inactive',
'label' => t('Inactive departments'),
'count' => (string) $inactiveDepartmentCount,
'icon' => 'bi bi-diagram-3',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive departments'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/departments',
'label' => t('Departments'),
'count' => (string) $departmentCount,
'icon' => 'bi bi-diagram-3-fill',
'label' => t('Departments without tenants'),
'count' => (string) $departmentsWithoutTenantsCount,
'icon' => 'bi bi-diagram-3',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Departments without tenants'),
]);
?>
</div>
</div>
<!-- Tab Panel: Roles & permissions -->
<div data-tab-panel="roles-permissions">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/roles?active=active',
'label' => t('Active roles'),
'count' => (string) $activeRoleCount,
'icon' => 'bi bi-shield-check',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Active roles'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/roles?active=inactive',
'label' => t('Inactive roles'),
'count' => (string) $inactiveRoleCount,
'icon' => 'bi bi-shield-x',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive roles'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/roles',
'label' => t('Roles without permissions'),
'count' => (string) $rolesWithoutPermissionsCount,
'icon' => 'bi bi-shield-exclamation',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Roles without permissions'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/permissions',
'label' => t('Permissions without roles'),
'count' => (string) $permissionsWithoutRolesCount,
'icon' => 'bi bi-key-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Permissions without roles'),
]);
?>
</div>
</div>
<!-- Tab Panel: Security -->
<div data-tab-panel="security">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
'icon' => 'bi bi-person-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Inactive users'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users',
'label' => t('Users'),
'count' => (string) $userCount,
'icon' => 'bi bi-people-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'label' => t('Users without roles'),
'count' => (string) $usersWithoutRolesCount,
'icon' => 'bi bi-person-dash',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
'tooltip' => t('Users without roles'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users',
'label' => t('Users without tenants'),
'count' => (string) $usersWithoutTenantsCount,
'icon' => 'bi bi-person-slash',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Users without tenants'),
]);
?>
</div>
<?php if (!empty($tenantBreakdown)): ?>
<div class="app-stats-table">
<table>
<thead>
<tr>
<th>
<?php e(t('Tenant')); ?>
</th>
<th>
<?php e(t('Departments')); ?>
</th>
<th>
<?php e(t('Assigned users (Active/Inactive)')); ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($tenantBreakdown as $item): ?>
<tr>
<td>
<?php if (can('tenants.view') && !empty($item['uuid'])): ?>
<a href="admin/tenants/edit/<?php e($item['uuid']); ?>">
<?php e($item['description']); ?>
</a>
<?php else: ?>
<?php e($item['description']); ?>
<?php endif; ?>
</td>
<td>
<?php e($item['departments']); ?>
</td>
<td>
<?php e($item['total_users']); ?> <small>(
<?php e($item['active_users']); ?> /
<?php e($item['inactive_users']); ?>)
</small>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<!-- Tab Panel: Users & Roles -->
<div data-tab-panel="users-roles">
<div class="grid">
<div>
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users',
'label' => t('Active users'),
'count' => (string) $activeUserCount,
'icon' => 'bi bi-person-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/users',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
'icon' => 'bi bi-person-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
]);
?>
</div>
</div>
<div>
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/roles',
'label' => t('Roles'),
'count' => (string) $roleCount,
'icon' => 'bi bi-shield-lock-fill',
'iconBg' => '#f3e9ff',
'iconColor' => '#5b2c83',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/permissions',
'label' => t('Permissions'),
'count' => (string) $permissionCount,
'icon' => 'bi bi-key-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
]);
?>
</div>
</div>
</div>
</div>
<!-- Tab Panel: System -->
<!-- Tab Panel: Email security -->
<?php if (can('mail_log.view')): ?>
<div data-tab-panel="system">
<div class="grid">
<div>
<h4><?php e(t('Mail logs')); ?></h4>
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log',
'label' => t('Total emails'),
'count' => (string) $mailLogTotalCount,
'icon' => 'bi bi-envelope-fill',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=sent',
'label' => t('Sent emails'),
'count' => (string) $mailLogSentCount,
'icon' => 'bi bi-envelope-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=failed',
'label' => t('Failed emails'),
'count' => (string) $mailLogFailedCount,
'icon' => 'bi bi-envelope-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log',
'label' => t('Queued emails'),
'count' => (string) $mailLogQueuedCount,
'icon' => 'bi bi-envelope-paper-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
]);
?>
</div>
</div>
<div>
<h4>
<?php e(t('Last 24 hours')); ?>
</h4>
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?created_from=' . urlencode($date24hAgo ?? '') . '&created_to=' . urlencode($dateToday ?? ''),
'label' => t('Total (24h)'),
'count' => (string) $mailLogTotal24hCount,
'icon' => 'bi bi-envelope-fill',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=sent&created_from=' . urlencode($date24hAgo ?? '') . '&created_to=' . urlencode($dateToday ?? ''),
'label' => t('Sent (24h)'),
'count' => (string) $mailLogSent24hCount,
'icon' => 'bi bi-envelope-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=failed&created_from=' . urlencode($date24hAgo ?? '') . '&created_to=' . urlencode($dateToday ?? ''),
'label' => t('Failed (24h)'),
'count' => (string) $mailLogFailed24hCount,
'icon' => 'bi bi-envelope-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=queued&created_from=' . urlencode($date24hAgo ?? '') . '&created_to=' . urlencode($dateToday ?? ''),
'label' => t('Queued (24h)'),
'count' => (string) $mailLogQueued24hCount,
'icon' => 'bi bi-envelope-paper-fill',
'iconBg' => '#fff2d9',
'iconColor' => '#9a5a00',
]);
?>
</div>
</div>
<div data-tab-panel="email-security">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=failed',
'label' => t('Mail failures'),
'count' => (string) $mailLogFailedCount,
'icon' => 'bi bi-envelope-x-fill',
'iconBg' => '#ffe9e9',
'iconColor' => '#a32e2e',
'tooltip' => t('Mail failures'),
]);
?>
<?php
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=sent',
'label' => t('Sent emails'),
'count' => (string) $mailLogSentCount,
'icon' => 'bi bi-envelope-check-fill',
'iconBg' => '#d9f2e6',
'iconColor' => '#1f6a3a',
'tooltip' => t('Sent emails'),
]);
?>
<?php
$lastSentLabel = $mailLogLastSentAt ? dt($mailLogLastSentAt) : t('Never');
MintyPHP\Support\Tile::render([
'href' => 'admin/mail-log?status=sent',
'label' => t('Last email sent'),
'count' => $lastSentLabel,
'icon' => 'bi bi-clock-history',
'iconBg' => '#e9f0ff',
'iconColor' => '#264db3',
'tooltip' => t('Last email sent'),
]);
?>
</div>
<?php if (!empty($mailLogRecentFailed)): ?>
<div class="app-stats-table">
<div class="app-stats-table-header">
<small><?php e(t('Mail failures')); ?></small>
</div>
<table>
<thead>
<tr>
<th><?php e(t('Recipient')); ?></th>
<th><?php e(t('Subject')); ?></th>
<th><?php e(t('Error')); ?></th>
<th><?php e(t('Created')); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($mailLogRecentFailed as $row): ?>
<tr>
<td><?php e($row['to_email'] ?? ''); ?></td>
<td><?php e($row['subject'] ?? ''); ?></td>
<td><?php e($row['error_message'] ?? ''); ?></td>
<td><?php e(dt($row['created_at'] ?? '')); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>