refactor(ui): collapse Tile static class into appTile() helper

Align dashboard-tile primitive with the established helper-function +
partial convention (like tokenSelectForm / multiSelectForm). Keeps the
reusable substance (app-tile.phtml, .app-tile CSS) and makes the
primitive discoverable for modules via core/Support/helpers/ui.php.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 16:23:05 +02:00
parent 0002c07755
commit d9ed4ab5b4
5 changed files with 88 additions and 83 deletions

View File

@@ -191,6 +191,7 @@ docker/ # Dockerfiles, Nginx configs, PHP configs
- **Remove button:** uses the shared `.app-icon-button` primitive + `<i class="bi bi-x-lg">` — never hand-roll a close button.
- **Coexistence:** `multiSelectForm()` (vendor MultiSelect) remains available for small closed lists where the chip-header is acceptable. Pick `tokenSelectForm()` when the selected set can grow beyond ~10 items or typeahead is the expected interaction.
- **Tests:** helper contract lives in `tests/Support/Helpers/TokenSelectFormHelperTest.php`; component lifecycle + entrypoint registration are enforced by `tests/Architecture/FrontendRuntime*ContractTest.php`.
- **Dashboard tile** (icon + count + label link): use `appTile()` from `core/Support/helpers/ui.php` for dashboard / stat-grid tiles that link to a filtered list view. Renders the shared `templates/partials/app-tile.phtml` with `.app-tile` CSS. Options: `href`, `label`, `count`, `icon` (bi-*), `iconBg`, `iconColor`, `class`, `tooltip`, `tooltipPos`. Group tiles inside `<div class="app-tiles">` for the grid layout. Modules may call `appTile()` directly — no wrapper class needed.
### Never Do This

View File

@@ -1,30 +0,0 @@
<?php
namespace MintyPHP\Support;
class Tile
{
public static function render(array $options = []): void
{
$defaults = [
'href' => '#',
'label' => '',
'count' => null,
'icon' => 'bi bi-grid-1x2',
'iconBg' => null,
'iconColor' => null,
'class' => '',
'tooltip' => '',
'tooltipPos' => 'top',
];
$data = array_merge($defaults, $options);
$template = dirname(__DIR__, 2) . '/templates/partials/app-tile.phtml';
if (!is_file($template)) {
return;
}
extract($data, EXTR_SKIP);
require $template;
}
}

View File

@@ -803,3 +803,43 @@ function renderPageStyles(): void
{
renderStylePaths(appPageStylePaths());
}
/**
* Render a dashboard tile (icon + count + label, linking to a target URL).
*
* Options:
* - href (string) Link target. Default: '#'.
* - label (string) Tile label. Default: ''.
* - count (string|null) Numeric or short textual value. Default: null.
* - icon (string) Bootstrap-icon class. Default: 'bi bi-grid-1x2'.
* - iconBg (string|null) CSS color for icon background.
* - iconColor (string|null) CSS color for icon glyph.
* - class (string) Extra CSS classes appended to .app-tile.
* - tooltip (string) Tooltip text (already translated).
* - tooltipPos (string) Tooltip position. Default: 'top'.
*
* @param array<string, mixed> $options
*/
function appTile(array $options = []): void
{
$defaults = [
'href' => '#',
'label' => '',
'count' => null,
'icon' => 'bi bi-grid-1x2',
'iconBg' => null,
'iconColor' => null,
'class' => '',
'tooltip' => '',
'tooltipPos' => 'top',
];
$data = array_merge($defaults, $options);
$template = templatePath('partials/app-tile.phtml');
if (!is_file($template)) {
return;
}
extract($data, EXTR_SKIP);
require $template;
}

View File

@@ -175,7 +175,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="users">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users?active=active',
'label' => t('Active users'),
'count' => (string) $activeUserCount,
@@ -186,7 +186,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
@@ -197,7 +197,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users?login_status=never',
'label' => t('Never logged in'),
'count' => (string) $usersNeverLoggedCount,
@@ -208,7 +208,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users?email_verified=unverified',
'label' => t('Email unverified'),
'count' => (string) $usersUnverifiedCount,
@@ -293,7 +293,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="tenants">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/tenants',
'label' => t('Active tenants'),
'count' => (string) $activeTenantCount,
@@ -304,7 +304,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/tenants',
'label' => t('Inactive tenants'),
'count' => (string) $inactiveTenantCount,
@@ -321,7 +321,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="departments">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/departments?active=active',
'label' => t('Active departments'),
'count' => (string) $activeDepartmentCount,
@@ -332,7 +332,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/departments?active=inactive',
'label' => t('Inactive departments'),
'count' => (string) $inactiveDepartmentCount,
@@ -349,7 +349,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="roles-permissions">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/roles?active=active',
'label' => t('Active roles'),
'count' => (string) $activeRoleCount,
@@ -360,7 +360,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/roles?active=inactive',
'label' => t('Inactive roles'),
'count' => (string) $inactiveRoleCount,
@@ -371,7 +371,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/roles',
'label' => t('Roles without permissions'),
'count' => (string) $rolesWithoutPermissionsCount,
@@ -382,7 +382,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/permissions',
'label' => t('Permissions without roles'),
'count' => (string) $permissionsWithoutRolesCount,
@@ -399,7 +399,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="security">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users?active=inactive',
'label' => t('Inactive users'),
'count' => (string) $inactiveUserCount,
@@ -410,7 +410,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users',
'label' => t('Users without roles'),
'count' => (string) $usersWithoutRolesCount,
@@ -421,7 +421,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users',
'label' => t('Users without tenants'),
'count' => (string) $usersWithoutTenantsCount,
@@ -443,7 +443,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div class="app-tiles">
<?php if ($canViewSystemAudit && $systemAuditStatsAvailable): ?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $systemAuditAll24hHref,
'label' => t('System audit events (24h)'),
'count' => (string) $systemAuditEvents24hCount,
@@ -454,7 +454,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $systemAuditFailed24hHref,
'label' => t('System audit risks (24h)'),
'count' => (string) $systemAuditRisk24hCount,
@@ -468,7 +468,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<?php if ($canViewUserLifecycleAudit && $userLifecycleAuditStatsAvailable): ?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $lifecycleAll7dHref,
'label' => t('Lifecycle runs (7d)'),
'count' => (string) $lifecycleRuns7dCount,
@@ -479,7 +479,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $lifecycleFailed7dHref,
'label' => t('Lifecycle risks (7d)'),
'count' => (string) $lifecycleRisk7dCount,
@@ -490,7 +490,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $lifecycleRestore7dHref,
'label' => t('Restore actions (7d)'),
'count' => (string) $lifecycleRestore7dCount,
@@ -660,7 +660,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="sso">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/tenants',
'label' => t('SSO-enabled tenants'),
'count' => (string) $ssoEnabledTenantCount,
@@ -671,7 +671,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/tenants',
'label' => t('SSO-enforced tenants'),
'count' => (string) $ssoEnforcedTenantCount,
@@ -682,7 +682,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/tenants',
'label' => t('SSO rollout'),
'count' => number_format($ssoEnabledTenantPercent, 1, '.', '') . '%',
@@ -693,7 +693,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users',
'label' => t('Users with Microsoft-only login'),
'count' => (string) $usersMicrosoftOnlyCount,
@@ -704,7 +704,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/users',
'label' => t('Microsoft profile sync gaps'),
'count' => (string) $usersMicrosoftSyncGapsCount,
@@ -722,7 +722,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="custom-fields">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $customFieldsSelectionWithoutOptionsHref,
'label' => t('Selection fields without options'),
'count' => (string) $customFieldsSelectionWithoutOptionsCount,
@@ -733,7 +733,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $customFieldTenantsWithSelectionWithoutOptionsHref,
'label' => t('Tenants with broken selection fields'),
'count' => (string) $customFieldTenantsWithSelectionWithoutOptionsCount,
@@ -744,7 +744,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $customFieldsInactiveWithValuesHref,
'label' => t('Inactive custom fields with values'),
'count' => (string) $customFieldsInactiveWithValuesCount,
@@ -755,7 +755,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => $customFieldsUnusedActiveHref,
'label' => t('Unused active custom fields'),
'count' => (string) $customFieldsUnusedActiveCount,
@@ -815,7 +815,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="api">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/api-audit',
'label' => t('API requests (24h)'),
'count' => (string) $apiRequests24hCount,
@@ -829,7 +829,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
$trendLabel = $apiRequestsPrev24hCount > 0
? (($apiRequestTrendPercent > 0 ? '+' : '') . number_format($apiRequestTrendPercent, 1, '.', '') . '%')
: 'n/a';
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/api-audit',
'label' => t('Request trend vs previous 24h'),
'count' => $trendLabel,
@@ -840,7 +840,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/api-audit',
'label' => t('4xx errors (24h)'),
'count' => (string) $apiErrors4xx24hCount,
@@ -851,7 +851,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/api-audit',
'label' => t('5xx errors (24h)'),
'count' => (string) $apiErrors5xx24hCount,
@@ -862,7 +862,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/api-audit',
'label' => t('P95 response time (ms)'),
'count' => (string) $apiP95DurationMs,
@@ -873,7 +873,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/settings?tab=api',
'label' => t('Tokens expiring in 7 days'),
'count' => (string) $apiTokensExpiring7dCount,
@@ -953,7 +953,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="imports">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/import-audit',
'label' => t('Import runs (7d)'),
'count' => (string) $importRuns7dCount,
@@ -964,7 +964,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/import-audit',
'label' => t('Created rows (7d)'),
'count' => (string) $importRowsCreated7dCount,
@@ -975,7 +975,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/import-audit',
'label' => t('Failed rows (7d)'),
'count' => (string) $importRowsFailed7dCount,
@@ -989,7 +989,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
$importSuccessLabel = $importRuns7dCount > 0
? number_format($importSuccessRate7dPercent, 1, '.', '') . '%'
: 'n/a';
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/import-audit',
'label' => t('Success rate (7d)'),
'count' => $importSuccessLabel,
@@ -1164,7 +1164,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
?>
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/scheduled-jobs',
'label' => t('Enabled jobs'),
'count' => (string) $scheduledEnabledJobsCount,
@@ -1175,7 +1175,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/scheduled-jobs',
'label' => t('Overdue jobs'),
'count' => (string) $scheduledOverdueJobsCount,
@@ -1186,7 +1186,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/scheduled-jobs',
'label' => t('Scheduler runs (24h)'),
'count' => (string) $scheduledRuns24hCount,
@@ -1197,7 +1197,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/scheduled-jobs',
'label' => t('Failed scheduler runs (24h)'),
'count' => (string) $scheduledFailedRuns24hCount,
@@ -1208,7 +1208,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/scheduled-jobs',
'label' => t('Cron runner active'),
'count' => $runnerStatusLabel,
@@ -1227,7 +1227,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<div data-tab-panel="email-security">
<div class="app-tiles">
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/mail-log?status=failed',
'label' => t('Mail failures'),
'count' => (string) $mailLogFailedCount,
@@ -1238,7 +1238,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
]);
?>
<?php
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/mail-log?status=sent',
'label' => t('Sent emails'),
'count' => (string) $mailLogSentCount,
@@ -1250,7 +1250,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
?>
<?php
$lastSentLabel = $mailLogLastSentAt ? dt($mailLogLastSentAt) : t('Never');
MintyPHP\Support\Tile::render([
appTile([
'href' => 'admin/mail-log?status=sent',
'label' => t('Last email sent'),
'count' => $lastSentLabel,

View File

@@ -1428,12 +1428,6 @@ parameters:
count: 1
path: core/Support/SearchConfig.php
-
message: '#^Public method "MintyPHP\\Support\\Tile\:\:render\(\)" is never used$#'
identifier: public.method.unused
count: 1
path: core/Support/Tile.php
-
message: '#^Public constant "MintyPHP\\Module\\AddressBook\\AddressBookAuthorizationPolicy\:\:PERMISSION_KEY" is never used$#'
identifier: public.classConstant.unused