feat(admin): add core users widget and reorganize dashboard widget registration

This commit is contained in:
2026-04-22 13:27:04 -04:00
parent 692f639cb5
commit 12f66a2115
10 changed files with 90 additions and 63 deletions
@@ -1,6 +1,7 @@
'use client';
import { getClientWidgets } from '../../dashboard/clientRegistry.js';
import '../../dashboard/widgets/index.client.js';
import '../../../dashboard.client.js';
// Évalué après tous les imports : les auto-registrations sont complètes
@@ -20,7 +21,7 @@ export default function DashboardPage({ stats }) {
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-4 sm:gap-6">
{sortedWidgets.map(({ id, Component }) => (
<Component
key={id}
@@ -10,6 +10,7 @@
export { collectAllDashboardData } from './registry.js';
// Side effect : initialise le registre, puis déclenche toutes les auto-registrations
// Side effect : initialise le registre, enregistre les widgets core, puis les features
import './registry.js';
import './widgets/index.server.js';
import '../../dashboard.server.js';
@@ -0,0 +1,10 @@
'use client';
/**
* Widgets core — Câblage client
*
* Ce fichier est le SEUL à modifier pour ajouter un widget core côté client.
* Les widgets core sont auto-enregistrés au démarrage de l'admin, sans module externe.
*/
import './users.widget.js';
@@ -0,0 +1,8 @@
/**
* Widgets core — Câblage serveur
*
* Ce fichier est le SEUL à modifier pour ajouter un widget core côté serveur.
* Les widgets core sont auto-enregistrés au démarrage de l'admin, sans module externe.
*/
import './users.server.js';
@@ -0,0 +1,30 @@
/**
* Widget core — Contribution serveur : nombre d'utilisateurs
*
* Auto-enregistré via admin/dashboard/widgets/index.server.js.
* Pas besoin de modifier features/dashboard.server.js pour ce widget.
*/
import { registerServerWidget } from '../registry.js';
import { query } from '@zen/core/database';
import { fail } from '@zen/core/shared/logger';
async function getUsersDashboardData() {
try {
const result = await query(`
SELECT
COUNT(*) AS total,
COUNT(CASE WHEN created_at >= NOW() - INTERVAL '30 days' THEN 1 END) AS new_this_month
FROM zen_auth_users
`);
return {
totalUsers: parseInt(result.rows[0].total) || 0,
newThisMonth: parseInt(result.rows[0].new_this_month) || 0,
};
} catch (error) {
fail(`Users dashboard data error: ${error.message}`);
return { totalUsers: 0, newThisMonth: 0 };
}
}
registerServerWidget('users', getUsersDashboardData);
@@ -0,0 +1,36 @@
'use client';
/**
* Widget core — Composant client : nombre d'utilisateurs
*
* Auto-enregistré via admin/dashboard/widgets/index.client.js.
* Pas besoin de modifier features/dashboard.client.js pour ce widget.
*
* Props du composant :
* data — { totalUsers: number, newThisMonth: number } retourné par getUsersDashboardData(), ou null
* loading — true tant que les données serveur ne sont pas disponibles
*/
import { registerClientWidget } from '../clientRegistry.js';
import { StatCard } from '@zen/core/shared/components';
import { UserMultiple02Icon } from '@zen/core/shared/icons';
function UsersDashboardWidget({ data, loading }) {
const newThisMonth = data?.newThisMonth ?? 0;
return (
<StatCard
title="Nombre d'utilisateurs"
value={loading ? '-' : String(data?.totalUsers ?? 0)}
change={!loading && newThisMonth > 0 ? `+${newThisMonth} ce mois` : undefined}
changeType="increase"
icon={UserMultiple02Icon}
color="text-purple-400"
bgColor="bg-purple-500/10"
loading={loading}
/>
);
}
registerClientWidget('users', UsersDashboardWidget, 10);
export default UsersDashboardWidget;
-22
View File
@@ -1,22 +0,0 @@
/**
* Auth Feature — Contribution serveur au tableau de bord
*
* Ce module s'auto-enregistre auprès du registre admin en side effect.
* Dépendance correcte : auth → admin (feature → core).
*/
import { registerServerWidget } from '../admin/dashboard/registry.js';
import { query } from '@zen/core/database';
import { fail } from '@zen/core/shared/logger';
async function getDashboardData() {
try {
const result = await query(`SELECT COUNT(*) as count FROM zen_auth_users`);
return { totalUsers: parseInt(result.rows[0].count) || 0 };
} catch (error) {
fail(`Auth dashboard data error: ${error.message}`);
return { totalUsers: 0 };
}
}
registerServerWidget('auth', getDashboardData);
-33
View File
@@ -1,33 +0,0 @@
'use client';
/**
* Auth Feature — Widget client pour le tableau de bord
*
* Ce module s'auto-enregistre auprès du registre admin en side effect.
* Dépendance correcte : auth → admin (feature → core).
*
* Props du composant :
* data — { totalUsers: number } retourné par getDashboardData(), ou null
* loading — true tant que les données serveur ne sont pas disponibles
*/
import { registerClientWidget } from '../admin/dashboard/clientRegistry.js';
import { StatCard } from '@zen/core/shared/components';
import { UserMultiple02Icon } from '@zen/core/shared/icons';
function AuthDashboardWidget({ data, loading }) {
return (
<StatCard
title="Nombre d'utilisateurs"
value={loading ? '-' : String(data?.totalUsers ?? 0)}
icon={UserMultiple02Icon}
color="text-purple-400"
bgColor="bg-purple-500/10"
loading={loading}
/>
);
}
registerClientWidget('auth', AuthDashboardWidget, 10);
export default AuthDashboardWidget;
+1 -3
View File
@@ -1,7 +1,7 @@
'use client';
/**
* Câblage client des widgets du tableau de bord
* Câblage client des widgets du tableau de bord — Features externes
*
* Ce fichier est le SEUL à modifier pour ajouter le widget client
* d'une nouvelle feature au tableau de bord.
@@ -12,5 +12,3 @@
* Exemple pour une feature 'blog' :
* import './blog/dashboard.widget.js';
*/
import './auth/dashboard.widget.js';
+1 -3
View File
@@ -1,5 +1,5 @@
/**
* Câblage serveur des widgets du tableau de bord
* Câblage serveur des widgets du tableau de bord — Features externes
*
* Ce fichier est le SEUL à modifier pour ajouter la contribution serveur
* d'une nouvelle feature au tableau de bord.
@@ -10,5 +10,3 @@
* Exemple pour une feature 'blog' :
* import './blog/dashboard.server.js';
*/
import './auth/dashboard.server.js';