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:
parent
e7bec689b0
commit
8bc570cfda
2 changed files with 109 additions and 95 deletions
|
|
@ -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 ✅
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue