refactor: remove module system integration from admin and CLI

Removes all module-related logic from the admin dashboard, CLI database
initialization, and AdminPages component:

- Drop `initModules` call from `db init` CLI command and simplify the
  completion message to only reflect core feature tables
- Remove `getModuleDashboardStats` and module page routing from admin
  stats actions and update usage documentation accordingly
- Simplify `AdminPagesClient` to remove module page loading, lazy
  components, and module-specific props (`moduleStats`, `modulePageInfo`,
  `routeInfo`, `enabledModules`)
This commit is contained in:
2026-04-14 19:26:48 -04:00
parent 242ea69664
commit 3131df2b71
9 changed files with 415 additions and 239 deletions
+9 -70
View File
@@ -1,85 +1,24 @@
'use client';
/**
* Admin Pages Component
*
* This component handles both core admin pages and module pages.
* Module pages are loaded dynamically on the client where hooks work properly.
*/
import { Suspense } from 'react';
import DashboardPage from './pages/DashboardPage.js';
import UsersPage from './pages/UsersPage.js';
import UserEditPage from './pages/UserEditPage.js';
import ProfilePage from './pages/ProfilePage.js';
import { getModulePageLoader } from '../../../modules/modules.pages.js';
// Loading component for suspense
function PageLoading() {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
</div>
);
}
export default function AdminPagesClient({ params, user, dashboardStats = null }) {
const parts = params?.admin || [];
const page = parts[0] || 'dashboard';
export default function AdminPagesClient({
params,
user,
dashboardStats = null,
moduleStats = {},
modulePageInfo = null,
routeInfo = null,
enabledModules = {}
}) {
// If this is a module page, render it with lazy loading
if (modulePageInfo && routeInfo) {
const LazyComponent = getModulePageLoader(modulePageInfo.module, modulePageInfo.path);
if (LazyComponent) {
// Build props for the page
const pageProps = { user };
if (routeInfo.action === 'edit' && routeInfo.id) {
// Add ID props for edit pages (modules may use different prop names)
pageProps.id = routeInfo.id;
pageProps.invoiceId = routeInfo.id;
pageProps.clientId = routeInfo.id;
pageProps.itemId = routeInfo.id;
pageProps.categoryId = routeInfo.id;
pageProps.transactionId = routeInfo.id;
pageProps.recurrenceId = routeInfo.id;
pageProps.templateId = routeInfo.id;
pageProps.postId = routeInfo.id;
}
return (
<Suspense fallback={<PageLoading />}>
<LazyComponent {...pageProps} />
</Suspense>
);
}
if (page === 'users' && parts[1] === 'edit' && parts[2]) {
return <UserEditPage userId={parts[2]} user={user} />;
}
// Determine core page from routeInfo or params
let currentPage = 'dashboard';
if (routeInfo?.path) {
const parts = routeInfo.path.split('/').filter(Boolean);
currentPage = parts[1] || 'dashboard'; // /admin/[page]
} else if (params?.admin) {
currentPage = params.admin[0] || 'dashboard';
}
// Core page components mapping (non-module pages)
const usersPageComponent = routeInfo?.action === 'edit' && routeInfo?.id
? () => <UserEditPage userId={routeInfo.id} user={user} enabledModules={enabledModules} />
: () => <UsersPage user={user} />;
const corePages = {
dashboard: () => <DashboardPage user={user} stats={dashboardStats} moduleStats={moduleStats} enabledModules={enabledModules} />,
users: usersPageComponent,
dashboard: () => <DashboardPage user={user} stats={dashboardStats} />,
users: () => <UsersPage user={user} />,
profile: () => <ProfilePage user={user} />,
};
// Render the appropriate core page or default to dashboard
const CorePageComponent = corePages[currentPage];
return CorePageComponent ? <CorePageComponent /> : <DashboardPage user={user} stats={dashboardStats} moduleStats={moduleStats} enabledModules={enabledModules} />;
const CorePageComponent = corePages[page];
return CorePageComponent ? <CorePageComponent /> : <DashboardPage user={user} stats={dashboardStats} />;
}
@@ -1,33 +1,11 @@
'use client';
/**
* Admin Dashboard Page
* Displays core stats and dynamically loads module dashboard widgets
*/
import { Suspense } from 'react';
import { StatCard } from '../../../../shared/components';
import { UserMultiple02Icon } from '../../../../shared/Icons.js';
import { getModuleDashboardWidgets } from '../../../../modules/modules.pages.js';
/**
* Loading placeholder for widgets
*/
function WidgetLoading() {
return (
<div className="animate-pulse bg-neutral-200 dark:bg-neutral-800 rounded-lg h-32"></div>
);
}
export default function DashboardPage({ user, stats, moduleStats = {}, enabledModules = {} }) {
export default function DashboardPage({ user, stats }) {
const loading = !stats;
// Get only enabled module dashboard widgets
const allModuleWidgets = getModuleDashboardWidgets();
const moduleWidgets = Object.fromEntries(
Object.entries(allModuleWidgets).filter(([moduleName]) => enabledModules[moduleName])
);
return (
<div className="flex flex-col gap-4 sm:gap-6 lg:gap-8">
<div className="flex items-center justify-between">
@@ -40,16 +18,6 @@ export default function DashboardPage({ user, stats, moduleStats = {}, enabledMo
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{/* Module dashboard widgets (dynamically loaded) */}
{Object.entries(moduleWidgets).map(([moduleName, widgets]) => (
widgets.map((Widget, index) => (
<Suspense key={`${moduleName}-widget-${index}`} fallback={<WidgetLoading />}>
<Widget stats={moduleStats[moduleName]} />
</Suspense>
))
))}
{/* Core stats - always shown */}
<StatCard
title="Nombre d'utilisateurs"
value={loading ? '-' : String(stats?.totalUsers || 0)}
@@ -9,20 +9,17 @@ import { useToast } from '@zen/core/toast';
* User Edit Page Component
* Page for editing an existing user (admin only)
*/
const UserEditPage = ({ userId, user, enabledModules = {} }) => {
const UserEditPage = ({ userId, user }) => {
const router = useRouter();
const toast = useToast();
const clientsModuleActive = Boolean(enabledModules?.clients);
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [clients, setClients] = useState([]);
const [formData, setFormData] = useState({
name: '',
role: 'user',
email_verified: 'false',
client_id: ''
});
const [errors, setErrors] = useState({});
@@ -40,15 +37,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => {
loadUser();
}, [userId]);
useEffect(() => {
if (clientsModuleActive) {
fetch('/zen/api/admin/clients?limit=500', { credentials: 'include' })
.then(res => res.json())
.then(data => data.clients ? setClients(data.clients) : setClients([]))
.catch(() => setClients([]));
}
}, [clientsModuleActive]);
const loadUser = async () => {
try {
setLoading(true);
@@ -64,7 +52,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => {
name: data.user.name || '',
role: data.user.role || 'user',
email_verified: data.user.email_verified ? 'true' : 'false',
client_id: data.linkedClient ? String(data.linkedClient.id) : ''
}));
} else {
toast.error(data.message || 'Utilisateur introuvable');
@@ -107,7 +94,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => {
name: formData.name.trim(),
role: formData.role,
email_verified: formData.email_verified === 'true',
...(clientsModuleActive && { client_id: formData.client_id ? parseInt(formData.client_id, 10) : null })
})
});
const data = await response.json();
@@ -209,21 +195,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => {
onChange={(value) => handleInputChange('email_verified', value)}
options={emailVerifiedOptions}
/>
{clientsModuleActive && (
<Select
label="Client associé"
value={formData.client_id}
onChange={(value) => handleInputChange('client_id', value)}
options={[
{ value: '', label: 'Aucun' },
...clients.map(c => ({
value: String(c.id),
label: [c.client_number, c.company_name || [c.first_name, c.last_name].filter(Boolean).join(' ') || c.email].filter(Boolean).join(' ')
}))
]}
/>
)}
</div>
</div>
</Card>