refactor(web): split AdminView into admin-view module
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a862479ffc
commit
4400a8a58d
9 changed files with 208 additions and 127 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { AdminView } from './AdminView';
|
||||
import { AdminView, AdminViewSkeleton } from './admin-view';
|
||||
|
||||
/**
|
||||
* AdminView - Vue principale d'administration
|
||||
|
|
@ -8,42 +8,45 @@ import { AdminView } from './AdminView';
|
|||
* pour basculer entre les différentes vues admin.
|
||||
*/
|
||||
const meta: Meta<typeof AdminView> = {
|
||||
title: 'Components/Features/Views/AdminView',
|
||||
component: AdminView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Vue conteneur admin avec navigation sidebar vers sous-vues.',
|
||||
},
|
||||
},
|
||||
title: 'Components/Features/Views/AdminView',
|
||||
component: AdminView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'Vue conteneur admin avec navigation sidebar vers sous-vues.',
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
currentSubView: {
|
||||
control: 'select',
|
||||
options: ['dashboard', 'users', 'moderation', 'audit', 'settings'],
|
||||
description: 'Sous-vue à afficher par défaut',
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
currentSubView: {
|
||||
control: 'select',
|
||||
options: ['dashboard', 'users', 'moderation', 'audit', 'settings'],
|
||||
description: 'Sous-vue à afficher par défaut',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-screen p-4">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-layout-page p-4">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Vue dashboard par défaut.
|
||||
*/
|
||||
export const Default: Story = {
|
||||
name: 'Par défaut (Dashboard)',
|
||||
args: {
|
||||
currentSubView: 'dashboard',
|
||||
},
|
||||
name: 'Par défaut (Dashboard)',
|
||||
args: {
|
||||
currentSubView: 'dashboard',
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
render: () => <AdminViewSkeleton />,
|
||||
name: 'Chargement',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,96 +1 @@
|
|||
import React, { useState } from 'react';
|
||||
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>
|
||||
);
|
||||
};
|
||||
export { AdminView } from './admin-view';
|
||||
|
|
|
|||
26
apps/web/src/components/views/admin-view/AdminView.tsx
Normal file
26
apps/web/src/components/views/admin-view/AdminView.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 />;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
4
apps/web/src/components/views/admin-view/index.ts
Normal file
4
apps/web/src/components/views/admin-view/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { AdminView } from './AdminView';
|
||||
export { AdminViewSkeleton } from './AdminViewSkeleton';
|
||||
export { useAdminView } from './useAdminView';
|
||||
export type { AdminViewProps, AdminSubViewId, AdminTabConfig } from './types';
|
||||
18
apps/web/src/components/views/admin-view/types.ts
Normal file
18
apps/web/src/components/views/admin-view/types.ts
Normal 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;
|
||||
}
|
||||
31
apps/web/src/components/views/admin-view/useAdminView.tsx
Normal file
31
apps/web/src/components/views/admin-view/useAdminView.tsx
Normal 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,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue