-
{reminder.title}
+
{reminder.title}
{format(new Date(reminder.remind_at), 'MMM d, yyyy h:mm a')}
diff --git a/frontend/src/components/dashboard/StatsWidget.tsx b/frontend/src/components/dashboard/StatsWidget.tsx
index 7204bdd..f9aff94 100644
--- a/frontend/src/components/dashboard/StatsWidget.tsx
+++ b/frontend/src/components/dashboard/StatsWidget.tsx
@@ -1,4 +1,4 @@
-import { FolderKanban, Users, MapPin } from 'lucide-react';
+import { FolderKanban, Users, MapPin, TrendingUp } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
interface StatsWidgetProps {
@@ -13,42 +13,52 @@ interface StatsWidgetProps {
export default function StatsWidget({ projectStats, totalPeople, totalLocations }: StatsWidgetProps) {
const statCards = [
{
- label: 'Total Projects',
+ label: 'PROJECTS',
value: projectStats.total,
icon: FolderKanban,
- color: 'text-blue-500',
+ color: 'text-blue-400',
+ glowBg: 'bg-blue-500/10',
},
{
- label: 'In Progress',
+ label: 'IN PROGRESS',
value: projectStats.by_status['in_progress'] || 0,
- icon: FolderKanban,
- color: 'text-purple-500',
+ icon: TrendingUp,
+ color: 'text-purple-400',
+ glowBg: 'bg-purple-500/10',
},
{
- label: 'People',
+ label: 'PEOPLE',
value: totalPeople,
icon: Users,
- color: 'text-green-500',
+ color: 'text-emerald-400',
+ glowBg: 'bg-emerald-500/10',
},
{
- label: 'Locations',
+ label: 'LOCATIONS',
value: totalLocations,
icon: MapPin,
- color: 'text-orange-500',
+ color: 'text-orange-400',
+ glowBg: 'bg-orange-500/10',
},
];
return (
-
+
{statCards.map((stat) => (
-
-
-
-
-
{stat.label}
-
{stat.value}
+
+
+
+
+
+ {stat.label}
+
+
+ {stat.value}
+
+
+
+
-
diff --git a/frontend/src/components/dashboard/TodoWidget.tsx b/frontend/src/components/dashboard/TodoWidget.tsx
index b281cf6..d9123ae 100644
--- a/frontend/src/components/dashboard/TodoWidget.tsx
+++ b/frontend/src/components/dashboard/TodoWidget.tsx
@@ -1,4 +1,4 @@
-import { format, isPast } from 'date-fns';
+import { format, isPast, endOfDay } from 'date-fns';
import { Calendar, CheckCircle2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@@ -17,9 +17,9 @@ interface TodoWidgetProps {
}
const priorityColors: Record
= {
- low: 'bg-green-500/10 text-green-500 border-green-500/20',
- medium: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20',
- high: 'bg-red-500/10 text-red-500 border-red-500/20',
+ low: 'bg-green-500/10 text-green-400 border-green-500/20',
+ medium: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',
+ high: 'bg-red-500/10 text-red-400 border-red-500/20',
};
export default function TodoWidget({ todos }: TodoWidgetProps) {
@@ -27,41 +27,45 @@ export default function TodoWidget({ todos }: TodoWidgetProps) {
-
+
+
+
Upcoming Todos
{todos.length === 0 ? (
-
- No upcoming todos. You're all caught up!
+
+ All caught up.
) : (
-
+
{todos.slice(0, 5).map((todo) => {
- const isOverdue = isPast(new Date(todo.due_date));
+ const isOverdue = isPast(endOfDay(new Date(todo.due_date)));
return (
+
-
{todo.title}
+
{todo.title}
- {format(new Date(todo.due_date), 'MMM d, yyyy')}
- {isOverdue && (Overdue)}
+ {format(new Date(todo.due_date), 'MMM d')}
+ {isOverdue && overdue}
- {todo.category && (
-
{todo.category}
- )}
-
+
{todo.priority}
diff --git a/frontend/src/components/dashboard/UpcomingWidget.tsx b/frontend/src/components/dashboard/UpcomingWidget.tsx
index dd06c84..bbc2d2b 100644
--- a/frontend/src/components/dashboard/UpcomingWidget.tsx
+++ b/frontend/src/components/dashboard/UpcomingWidget.tsx
@@ -1,72 +1,98 @@
import { format } from 'date-fns';
-import { CheckSquare, Calendar, Bell } from 'lucide-react';
+import { CheckSquare, Calendar, Bell, ArrowRight } from 'lucide-react';
import type { UpcomingItem } from '@/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
+import { cn } from '@/lib/utils';
interface UpcomingWidgetProps {
items: UpcomingItem[];
days?: number;
}
-const priorityColors: Record
= {
- low: 'bg-green-500/10 text-green-500 border-green-500/20',
- medium: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20',
- high: 'bg-red-500/10 text-red-500 border-red-500/20',
+const typeConfig: Record = {
+ todo: {
+ icon: CheckSquare,
+ color: 'text-blue-400',
+ borderColor: 'border-l-blue-400',
+ label: 'Todo',
+ },
+ event: {
+ icon: Calendar,
+ color: 'text-purple-400',
+ borderColor: 'border-l-purple-400',
+ label: 'Event',
+ },
+ reminder: {
+ icon: Bell,
+ color: 'text-orange-400',
+ borderColor: 'border-l-orange-400',
+ label: 'Reminder',
+ },
};
export default function UpcomingWidget({ items, days = 7 }: UpcomingWidgetProps) {
- const getIcon = (type: string) => {
- switch (type) {
- case 'todo':
- return ;
- case 'event':
- return ;
- case 'reminder':
- return ;
- default:
- return null;
- }
- };
-
return (
-
+
- Upcoming ({days} days)
+
+
+
+ Upcoming
+
+
{days} days
+
{items.length === 0 ? (
- No upcoming items in the next few days
+ Nothing upcoming
) : (
-
-
- {items.map((item, index) => (
-
- {getIcon(item.type)}
-
-
{item.title}
-
- {item.datetime
- ? format(new Date(item.datetime), 'MMM d, yyyy h:mm a')
- : format(new Date(item.date), 'MMM d, yyyy')}
-
-
-
-
- {item.type}
-
+
+
+ {items.map((item, index) => {
+ const config = typeConfig[item.type] || typeConfig.todo;
+ const Icon = config.icon;
+ return (
+
+
+
+
{item.title}
+
+
+ {item.datetime
+ ? format(new Date(item.datetime), 'MMM d ยท h:mm a')
+ : format(new Date(item.date), 'MMM d')}
+
+
+ {config.label}
+
+
+
{item.priority && (
-
{item.priority}
+
+ {item.priority}
+
)}
-
- ))}
+ );
+ })}
)}
diff --git a/frontend/src/components/dashboard/WeekTimeline.tsx b/frontend/src/components/dashboard/WeekTimeline.tsx
new file mode 100644
index 0000000..ea3d7af
--- /dev/null
+++ b/frontend/src/components/dashboard/WeekTimeline.tsx
@@ -0,0 +1,89 @@
+import { useMemo } from 'react';
+import { format, startOfWeek, addDays, isSameDay, isBefore, startOfDay, formatISO } from 'date-fns';
+import type { UpcomingItem } from '@/types';
+import { cn } from '@/lib/utils';
+
+interface WeekTimelineProps {
+ items: UpcomingItem[];
+}
+
+const typeColors: Record
= {
+ todo: 'bg-blue-400',
+ event: 'bg-purple-400',
+ reminder: 'bg-orange-400',
+};
+
+export default function WeekTimeline({ items }: WeekTimelineProps) {
+ const today = useMemo(() => startOfDay(new Date()), []);
+ const weekStart = useMemo(() => startOfWeek(today, { weekStartsOn: 1 }), [today]);
+
+ const days = useMemo(() => {
+ return Array.from({ length: 7 }, (_, i) => {
+ const date = addDays(weekStart, i);
+ const dayItems = items.filter((item) => {
+ const itemDate = item.datetime ? new Date(item.datetime) : new Date(item.date);
+ return isSameDay(startOfDay(itemDate), date);
+ });
+ return {
+ date,
+ key: format(date, 'yyyy-MM-dd'),
+ dayName: format(date, 'EEE'),
+ dayNum: format(date, 'd'),
+ isToday: isSameDay(date, today),
+ isPast: isBefore(date, today),
+ items: dayItems,
+ };
+ });
+ }, [weekStart, today, items]);
+
+ return (
+
+ {days.map((day) => (
+
+
+ {day.dayName}
+
+
+ {day.dayNum}
+
+
+ {day.items.slice(0, 4).map((item) => (
+
+ ))}
+ {day.items.length > 4 && (
+
+ +{day.items.length - 4}
+
+ )}
+
+
+ ))}
+
+ );
+}
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx
index 15418e6..da1e897 100644
--- a/frontend/src/components/layout/Sidebar.tsx
+++ b/frontend/src/components/layout/Sidebar.tsx
@@ -45,16 +45,16 @@ export default function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
cn(
- 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200',
isActive
- ? 'bg-accent text-accent-foreground'
- : 'text-muted-foreground hover:bg-accent/10 hover:text-accent'
+ ? 'bg-accent/15 text-accent border-l-2 border-accent'
+ : 'text-muted-foreground hover:bg-accent/10 hover:text-accent border-l-2 border-transparent'
);
const sidebarContent = (
<>
- {!collapsed &&
UMBRA
}
+ {!collapsed &&
UMBRA
}