Kyle Pope 023fa86b65 Mobile UI polish: global font scaling, tighter dashboard, cleaner calendar
Scale down all content text on mobile via .mobile-scale CSS class (excludes
navbar/UMBRA title). Hide calendar event times in month view (Google Calendar
style). Restructure CategoryFilterBar so categories display on a separate row
when toggled instead of being hidden behind the search bar. Reduce dashboard
widget density with hidden badges and tighter spacing on small screens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 01:56:53 +08:00

105 lines
3.6 KiB
TypeScript

import { useNavigate } from 'react-router-dom';
import { FolderKanban, TrendingUp, CheckSquare, CloudSun } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
interface StatsWidgetProps {
projectStats: {
total: number;
by_status: Record<string, number>;
};
totalIncompleteTodos: number;
weatherData?: { temp: number; description: string; city?: string } | null;
}
export default function StatsWidget({ projectStats, totalIncompleteTodos, weatherData }: StatsWidgetProps) {
const navigate = useNavigate();
const statCards = [
{
label: 'PROJECTS',
value: projectStats.total,
icon: FolderKanban,
color: 'text-blue-400',
glowBg: 'bg-blue-500/10',
onClick: () => navigate('/projects'),
},
{
label: 'IN PROGRESS',
value: projectStats.by_status['in_progress'] || 0,
icon: TrendingUp,
color: 'text-purple-400',
glowBg: 'bg-purple-500/10',
onClick: () => navigate('/projects', { state: { filter: 'in_progress' } }),
},
{
label: 'OPEN TODOS',
value: totalIncompleteTodos,
icon: CheckSquare,
color: 'text-teal-400',
glowBg: 'bg-teal-500/10',
onClick: () => navigate('/todos'),
},
];
return (
<div className="grid gap-1.5 sm:gap-2.5 grid-cols-2 sm:grid-cols-4">
{statCards.map((stat) => (
<Card
key={stat.label}
className="bg-gradient-to-br from-accent/[0.03] to-transparent cursor-pointer group transition-transform duration-150 hover:scale-[1.01]"
onClick={stat.onClick}
>
<CardContent className="px-3 py-2">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-[10px] font-medium tracking-wider text-muted-foreground">
{stat.label}
</p>
<p className="font-heading text-xl font-bold tabular-nums leading-none">
{stat.value}
</p>
</div>
<div className={`p-1.5 rounded-md ${stat.glowBg}`}>
<stat.icon className={`h-4 w-4 ${stat.color}`} />
</div>
</div>
</CardContent>
</Card>
))}
{/* Weather card */}
<Card className="bg-gradient-to-br from-accent/[0.03] to-transparent">
<CardContent className="px-3 py-2">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-[10px] font-medium tracking-wider text-muted-foreground">
WEATHER
</p>
{weatherData ? (
<div className="flex items-baseline gap-1.5">
<p className="font-heading text-xl font-bold tabular-nums leading-none">
{weatherData.temp}°
</p>
<span className="text-[10px] text-muted-foreground capitalize">
{weatherData.description}
{weatherData.city && (
<span className="text-muted-foreground/50"> · {weatherData.city}</span>
)}
</span>
</div>
) : (
<p className="font-heading text-xl font-bold tabular-nums leading-none text-muted-foreground/50">
</p>
)}
</div>
<div className="p-1.5 rounded-md bg-amber-500/10">
<CloudSun className="h-4 w-4 text-amber-400" />
</div>
</div>
</CardContent>
</Card>
</div>
);
}