refactor(web): split AdminView into admin-view module

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-06 22:31:24 +01:00
parent a862479ffc
commit 4400a8a58d
9 changed files with 208 additions and 127 deletions

View file

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { AdminView } from './AdminView'; import { AdminView, AdminViewSkeleton } from './admin-view';
/** /**
* AdminView - Vue principale d'administration * AdminView - Vue principale d'administration
@ -8,42 +8,45 @@ import { AdminView } from './AdminView';
* pour basculer entre les différentes vues admin. * pour basculer entre les différentes vues admin.
*/ */
const meta: Meta<typeof AdminView> = { const meta: Meta<typeof AdminView> = {
title: 'Components/Features/Views/AdminView', title: 'Components/Features/Views/AdminView',
component: AdminView, component: AdminView,
parameters: { parameters: {
layout: 'fullscreen', layout: 'fullscreen',
docs: { docs: {
description: { description: {
component: 'Vue conteneur admin avec navigation sidebar vers sous-vues.', component:
}, 'Vue conteneur admin avec navigation sidebar vers sous-vues.',
}, },
}, },
tags: ['autodocs'], },
argTypes: { tags: ['autodocs'],
currentSubView: { argTypes: {
control: 'select', currentSubView: {
options: ['dashboard', 'users', 'moderation', 'audit', 'settings'], control: 'select',
description: 'Sous-vue à afficher par défaut', options: ['dashboard', 'users', 'moderation', 'audit', 'settings'],
}, description: 'Sous-vue à afficher par défaut',
}, },
decorators: [ },
(Story) => ( decorators: [
<div className="bg-kodo-background min-h-screen p-4"> (Story) => (
<Story /> <div className="bg-kodo-background min-h-layout-page p-4">
</div> <Story />
), </div>
], ),
],
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
/**
* Vue dashboard par défaut.
*/
export const Default: Story = { export const Default: Story = {
name: 'Par défaut (Dashboard)', name: 'Par défaut (Dashboard)',
args: { args: {
currentSubView: 'dashboard', currentSubView: 'dashboard',
}, },
};
export const Loading: Story = {
render: () => <AdminViewSkeleton />,
name: 'Chargement',
}; };

View file

@ -1,96 +1 @@
import React, { useState } from 'react'; export { AdminView } from './admin-view';
import { AdminDashboardView } from '../admin/AdminDashboardView';
import { AdminUsersView } from '../admin/AdminUsersView';
import { AdminModerationView } from '../admin/AdminModerationView';
import { AdminAuditLogsView } from '../admin/AdminAuditLogsView';
import { AdminSettingsView } from '../admin/AdminSettingsView';
import { LayoutDashboard, Users, ShieldAlert, Settings, History } from 'lucide-react';
interface AdminViewProps {
currentSubView?: string; // 'dashboard' | 'users' | 'moderation' | 'audit' | 'settings'
}
export const AdminView: React.FC<AdminViewProps> = ({
currentSubView = 'dashboard',
}) => {
// Local state for internal navigation if not driven by props, but props is better for deep linking simulation
const [activeTab, setActiveTab] = useState(currentSubView);
const renderContent = () => {
switch (activeTab) {
case 'users':
return <AdminUsersView />;
case 'moderation':
return <AdminModerationView />;
case 'audit':
return <AdminAuditLogsView />;
case 'settings':
return <AdminSettingsView />;
case 'dashboard':
default:
return <AdminDashboardView />;
}
};
return (
<div className="flex flex-col lg:flex-row gap-8 animate-fadeIn h-[calc(100vh-140px)]">
{/* Admin Sidebar */}
<div className="w-full lg:w-64 flex-shrink-0">
<div className="bg-kodo-red/10 border border-kodo-red/30 p-4 rounded-xl mb-6 flex items-center gap-4">
<ShieldAlert className="w-6 h-6 text-kodo-red" />
<div>
<h3 className="font-bold text-white text-sm">Admin Area</h3>
<p className="text-[10px] text-kodo-content-dim">Restricted Access</p>
</div>
</div>
<div className="space-y-1">
{[
{
id: 'dashboard',
label: 'Dashboard',
icon: <LayoutDashboard className="w-4 h-4" />,
},
{
id: 'users',
label: 'Users',
icon: <Users className="w-4 h-4" />,
},
{
id: 'moderation',
label: 'Moderation',
icon: <ShieldAlert className="w-4 h-4" />,
},
{
id: 'audit',
label: 'Audit Logs',
icon: <History className="w-4 h-4" />,
},
{
id: 'settings',
label: 'Settings',
icon: <Settings className="w-4 h-4" />,
},
].map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`w-full flex items-center gap-4 px-4 py-4 rounded-lg text-sm font-medium transition-all ${activeTab === item.id
? 'bg-white/10 text-white border-l-2 border-kodo-red'
: 'text-kodo-content-dim hover:text-white hover:bg-white/5 border-l-2 border-transparent'
}`}
>
{item.icon}
{item.label}
</button>
))}
</div>
</div>
{/* Content Area */}
<div className="flex-1 overflow-y-auto pr-2 pb-10 custom-scrollbar">
{renderContent()}
</div>
</div>
);
};

View file

@ -0,0 +1,26 @@
import React from 'react';
import { AdminViewSidebar } from './AdminViewSidebar';
import { AdminViewContent } from './AdminViewContent';
import { useAdminView } from './useAdminView';
import type { AdminViewProps, AdminSubViewId } from './types';
const DEFAULT_SUB_VIEW: AdminSubViewId = 'dashboard';
export function AdminView({
currentSubView = DEFAULT_SUB_VIEW,
}: AdminViewProps = {}) {
const { activeTab, setActiveTab, tabs } = useAdminView(currentSubView);
return (
<div className="flex flex-col lg:flex-row gap-8 animate-fadeIn min-h-layout-main">
<AdminViewSidebar
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
<div className="flex-1 overflow-y-auto pr-2 pb-10 custom-scrollbar">
<AdminViewContent activeTab={activeTab} />
</div>
</div>
);
}

View file

@ -0,0 +1,27 @@
import React from 'react';
import { AdminDashboardView } from '@/components/admin/AdminDashboardView';
import { AdminUsersView } from '@/components/admin/AdminUsersView';
import { AdminModerationView } from '@/components/admin/AdminModerationView';
import { AdminAuditLogsView } from '@/components/admin/AdminAuditLogsView';
import { AdminSettingsView } from '@/components/admin/AdminSettingsView';
import type { AdminSubViewId } from './types';
interface AdminViewContentProps {
activeTab: AdminSubViewId;
}
export function AdminViewContent({ activeTab }: AdminViewContentProps) {
switch (activeTab) {
case 'users':
return <AdminUsersView />;
case 'moderation':
return <AdminModerationView />;
case 'audit':
return <AdminAuditLogsView />;
case 'settings':
return <AdminSettingsView />;
case 'dashboard':
default:
return <AdminDashboardView />;
}
}

View file

@ -0,0 +1,45 @@
import React from 'react';
import { ShieldAlert } from 'lucide-react';
import type { AdminSubViewId, AdminTabConfig } from './types';
interface AdminViewSidebarProps {
tabs: AdminTabConfig[];
activeTab: AdminSubViewId;
onTabChange: (id: AdminSubViewId) => void;
}
export function AdminViewSidebar({
tabs,
activeTab,
onTabChange,
}: AdminViewSidebarProps) {
return (
<div className="w-full lg:w-64 flex-shrink-0">
<div className="bg-kodo-red/10 border border-kodo-red/30 p-4 rounded-xl mb-6 flex items-center gap-4">
<ShieldAlert className="w-6 h-6 text-kodo-red" />
<div>
<h3 className="font-bold text-white text-sm">Admin Area</h3>
<p className="text-xs text-kodo-content-dim">Restricted Access</p>
</div>
</div>
<div className="space-y-1">
{tabs.map((item) => (
<button
key={item.id}
type="button"
onClick={() => onTabChange(item.id)}
className={`w-full flex items-center gap-4 px-4 py-4 rounded-lg text-sm font-medium transition-all ${
activeTab === item.id
? 'bg-white/10 text-white border-l-2 border-kodo-red'
: 'text-kodo-content-dim hover:text-white hover:bg-white/5 border-l-2 border-transparent'
}`}
>
{item.icon}
{item.label}
</button>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,22 @@
import React from 'react';
import { Skeleton } from '@/components/ui/skeleton';
export function AdminViewSkeleton() {
return (
<div className="flex flex-col lg:flex-row gap-8 animate-fadeIn min-h-layout-main">
<div className="w-full lg:w-64 flex-shrink-0">
<Skeleton className="h-20 w-full rounded-xl mb-6" />
<div className="space-y-1">
{[1, 2, 3, 4, 5].map((i) => (
<Skeleton key={i} className="h-12 w-full rounded-lg" />
))}
</div>
</div>
<div className="flex-1 space-y-4">
<Skeleton className="h-10 w-48" />
<Skeleton className="h-40 w-full rounded-xl" />
<Skeleton className="h-40 w-full rounded-xl" />
</div>
</div>
);
}

View file

@ -0,0 +1,4 @@
export { AdminView } from './AdminView';
export { AdminViewSkeleton } from './AdminViewSkeleton';
export { useAdminView } from './useAdminView';
export type { AdminViewProps, AdminSubViewId, AdminTabConfig } from './types';

View file

@ -0,0 +1,18 @@
import type { ReactNode } from 'react';
export type AdminSubViewId =
| 'dashboard'
| 'users'
| 'moderation'
| 'audit'
| 'settings';
export interface AdminViewProps {
currentSubView?: AdminSubViewId;
}
export interface AdminTabConfig {
id: AdminSubViewId;
label: string;
icon: ReactNode;
}

View file

@ -0,0 +1,31 @@
import { useState, useEffect } from 'react';
import {
LayoutDashboard,
Users,
ShieldAlert,
Settings,
History,
} from 'lucide-react';
import type { AdminSubViewId, AdminTabConfig } from './types';
const ADMIN_TABS: AdminTabConfig[] = [
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard className="w-4 h-4" /> },
{ id: 'users', label: 'Users', icon: <Users className="w-4 h-4" /> },
{ id: 'moderation', label: 'Moderation', icon: <ShieldAlert className="w-4 h-4" /> },
{ id: 'audit', label: 'Audit Logs', icon: <History className="w-4 h-4" /> },
{ id: 'settings', label: 'Settings', icon: <Settings className="w-4 h-4" /> },
];
export function useAdminView(initialSubView: AdminSubViewId = 'dashboard') {
const [activeTab, setActiveTab] = useState<AdminSubViewId>(initialSubView);
useEffect(() => {
setActiveTab(initialSubView);
}, [initialSubView]);
return {
activeTab,
setActiveTab,
tabs: ADMIN_TABS,
};
}