refactor: dashboard design critique — eliminate redundancy, differentiate sections

- Remove GreetingStatStrip (duplicated PerformanceCards data)
- Strip left-border accent from stat cards (AI slop pattern)
- Redesign KnowledgeBaseCards: icon grid → compact row list with icon badges
- Redesign TeamSummary: distinct inline-row layout, no longer identical twin
- Differentiate hover: stat cards use subtle border-hover, sessions keep springy lift
- Add loading skeletons to PerformanceCards, KnowledgeBaseCards, TeamSummary
- Add error state to PerformanceCards
- Extract timeAgo() to shared lib/timeAgo.ts (replaced 4 duplicates)
- Fix Skeleton bg-brand-border (undefined CSS var) → border-default
- Fix double text-xs text-[0.5625rem] class conflicts across dashboard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-29 17:06:30 -04:00
parent 677c8f88ea
commit 912075cd43
11 changed files with 105 additions and 85 deletions

View File

@@ -4,6 +4,7 @@ import { CheckCircle, Clock, TrendingUp, Timer } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { sidebarApi } from '@/api'
import { cn } from '@/lib/utils'
import { Skeleton } from '@/components/ui/Skeleton'
interface StatCard {
label: string
@@ -19,6 +20,8 @@ export function PerformanceCards() {
const [resolved, setResolved] = useState(0)
const [active, setActive] = useState(0)
const [totalMinutes, setTotalMinutes] = useState(0)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
useEffect(() => {
sidebarApi.getStats()
@@ -27,9 +30,31 @@ export function PerformanceCards() {
setActive(stats.active_count)
setTotalMinutes(stats.total_session_minutes_today)
})
.catch(() => {})
.catch(() => setError(true))
.finally(() => setLoading(false))
}, [])
if (loading) {
return (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="card-flat p-4 space-y-2">
<Skeleton className="h-3 w-2/3" />
<Skeleton className="h-7 w-1/2" />
</div>
))}
</div>
)
}
if (error) {
return (
<div className="card-flat p-4 text-center">
<p className="text-sm text-muted-foreground">Unable to load performance data</p>
</div>
)
}
const avgMttr = resolved > 0 ? Math.round(totalMinutes / resolved) : 0
const cards: StatCard[] = [
@@ -70,14 +95,13 @@ export function PerformanceCards() {
<button
key={card.label}
onClick={() => navigate(card.href)}
className="card-interactive p-4 text-left fade-in"
className="card-flat p-4 text-left fade-in hover:border-[var(--color-border-hover)] transition-colors cursor-pointer"
style={{
animationDelay: `${400 + i * 60}ms`,
borderLeft: `3px solid ${card.iconColor}`,
}}
>
<div className="flex items-center justify-between mb-2">
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-sans text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
{card.label}
</p>
<card.icon size={14} style={{ color: card.iconColor }} />