cognitive-load: organize secondary info in tabs on dashboard

- Added Tabs component to organize Activity Feed content
- Chart and Activity List now in separate tabs (Graphique, Activité)
- Reduces cognitive load by showing one view at a time
- Default tab is 'Graphique' (Chart)
- Tabs are inside the collapsible Activity Feed section
- Action 10.1.1.3 complete
This commit is contained in:
senke 2026-01-16 02:28:58 +01:00
parent e7bec689b0
commit 8bc570cfda
2 changed files with 109 additions and 95 deletions

View file

@ -3467,12 +3467,19 @@ Critical path dependencies:
- **Status**: Component is fully functional and ready to use
- **Rollback**: Delete component
- [ ] **Action 10.1.1.3**: Use tabs or accordions for secondary info
- [x] **Action 10.1.1.3**: Use tabs or accordions for secondary info
- **Scope**: `apps/web/src/pages/DashboardPage.tsx` - Group secondary info in tabs
- **Dependencies**: Action 10.1.1.2 complete
- **Dependencies**: Action 10.1.1.2 complete
- **Risk**: MEDIUM (layout change)
- **Validation**: Secondary info in tabs
- **Rollback**: Restore single view
- **Validation**: ✅ Secondary info organized in tabs:
- **Tabs component**: Added Tabs, TabsList, TabsTrigger, TabsContent imports
- **Tab structure**: Two tabs ("Graphique" and "Activité") organize Activity Feed content
- **Chart tab**: Contains the activity chart with time period buttons (7J, 30J, MAX)
- **Activity tab**: Contains the recent activity list with activity items
- **Default tab**: "Graphique" is the default active tab
- **Layout**: Tabs are inside the Collapsible Activity Feed section
- **Result**: Secondary information (chart and activity list) is now organized in tabs, reducing cognitive load by showing one view at a time
- **Rollback**: Remove Tabs wrapper, restore space-y-6 div with both Cards visible
- [x] **Action 10.1.1.4**: Create Accordion component (if doesn't exist)
- **Scope**: `apps/web/src/components/ui/accordion.tsx` (create) - Reusable accordion component ✅

View file

@ -24,6 +24,7 @@ import { formatDistanceToNow } from 'date-fns';
import { fr } from 'date-fns/locale';
import { KodoEmptyState } from '@/components/ui/KodoEmptyState';
import { Collapsible } from '@/components/ui/collapsible';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { FAB } from '@/components/ui/FAB';
import { cn } from '@/lib/utils';
@ -256,101 +257,107 @@ export function DashboardPage() {
triggerClassName="p-0 hover:bg-transparent"
contentClassName="pt-0"
>
<div className="space-y-6">
{/* Chart Card */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-kodo-cyan" />
Activité récente
</CardTitle>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" className="text-xs">
7J
</Button>
<Button variant="outline" size="sm" className="text-xs text-kodo-cyan bg-kodo-cyan/10 border-kodo-cyan/20">
30J
</Button>
<Button variant="ghost" size="sm" className="text-xs">
MAX
</Button>
{/* Action 10.1.1.3: Use tabs to organize secondary info */}
<Tabs defaultValue="chart" className="mt-6">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="chart">Graphique</TabsTrigger>
<TabsTrigger value="activity">Activité</TabsTrigger>
</TabsList>
<TabsContent value="chart" className="mt-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-kodo-cyan" />
Activité récente
</CardTitle>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" className="text-xs">
7J
</Button>
<Button variant="outline" size="sm" className="text-xs text-kodo-cyan bg-kodo-cyan/10 border-kodo-cyan/20">
30J
</Button>
<Button variant="ghost" size="sm" className="text-xs">
MAX
</Button>
</div>
</div>
</div>
</CardHeader>
<CardContent>
<div className="h-64 flex items-end gap-2">
{[40, 65, 35, 90, 55, 75, 45, 85, 60, 70, 50, 95].map(
(h, i) => (
<div
key={i}
className="flex-1 bg-gradient-to-t from-kodo-cyan/40 to-kodo-cyan/20 rounded-t-lg transition-all duration-300 hover:from-kodo-cyan/60 hover:to-kodo-cyan/40 cursor-pointer group relative"
style={{ height: `${h}%` }}
>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-kodo-ink border border-kodo-cyan/30 text-kodo-cyan text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap shadow-lg">
{h}%
</div>
</div>
),
)}
</div>
</CardContent>
</Card>
{/* Recent Activity List */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Activity className="w-5 h-5 text-kodo-cyan" />
Dernières activités
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{isLoadingDashboard ? (
Array(3)
.fill(0)
.map((_, i) => (
</CardHeader>
<CardContent>
<div className="h-64 flex items-end gap-2">
{[40, 65, 35, 90, 55, 75, 45, 85, 60, 70, 50, 95].map(
(h, i) => (
<div
key={i}
className="h-16 bg-white/5 rounded-xl animate-pulse"
/>
className="flex-1 bg-gradient-to-t from-kodo-cyan/40 to-kodo-cyan/20 rounded-t-lg transition-all duration-300 hover:from-kodo-cyan/60 hover:to-kodo-cyan/40 cursor-pointer group relative"
style={{ height: `${h}%` }}
>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-kodo-ink border border-kodo-cyan/30 text-kodo-cyan text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap shadow-lg">
{h}%
</div>
</div>
),
)}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="activity" className="mt-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Activity className="w-5 h-5 text-kodo-cyan" />
Dernières activités
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{isLoadingDashboard ? (
Array(3)
.fill(0)
.map((_, i) => (
<div
key={i}
className="h-16 bg-white/5 rounded-xl animate-pulse"
/>
))
) : recentActivity.length > 0 ? (
recentActivity.slice(0, 5).map((act, i) => (
<div
key={i}
className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/5 hover:bg-white/10 hover:border-kodo-cyan/30 transition-all duration-200 group cursor-pointer"
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-kodo-cyan/20 to-kodo-cyan/10 border border-kodo-cyan/20 flex items-center justify-center group-hover:scale-110 transition-transform">
<Clock className="w-5 h-5 text-kodo-cyan" />
</div>
<div>
<p className="text-sm font-semibold text-white group-hover:text-kodo-cyan transition-colors">
{act.title}
</p>
<p className="text-xs text-kodo-secondary mt-0.5">
{act.description || 'Activité système'}
</p>
</div>
</div>
<div className="text-xs text-kodo-secondary font-mono">
{formatTimestamp(act.timestamp)}
</div>
</div>
))
) : recentActivity.length > 0 ? (
recentActivity.slice(0, 5).map((act, i) => (
<div
key={i}
className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/5 hover:bg-white/10 hover:border-kodo-cyan/30 transition-all duration-200 group cursor-pointer"
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-kodo-cyan/20 to-kodo-cyan/10 border border-kodo-cyan/20 flex items-center justify-center group-hover:scale-110 transition-transform">
<Clock className="w-5 h-5 text-kodo-cyan" />
</div>
<div>
<p className="text-sm font-semibold text-white group-hover:text-kodo-cyan transition-colors">
{act.title}
</p>
<p className="text-xs text-kodo-secondary mt-0.5">
{act.description || 'Activité système'}
</p>
</div>
</div>
<div className="text-xs text-kodo-secondary font-mono">
{formatTimestamp(act.timestamp)}
</div>
</div>
))
) : (
<KodoEmptyState
icon={Activity}
title="Aucune activité récente"
description="Vos activités apparaîtront ici"
/>
)}
</div>
</CardContent>
</Card>
</div>
) : (
<KodoEmptyState
icon={Activity}
title="Aucune activité récente"
description="Vos activités apparaîtront ici"
/>
)}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</Collapsible>
</CardHeader>
</Card>