import { useState, useMemo } from "react"; import { createRoute } from "@tanstack/react-router"; import { Route as appRoute } from "../_app"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api, useApiQuery, useApiMutation } from "@/lib/api"; import { Download, Calendar, TrendingUp, BarChart3, PieChart, Activity, Grid3X3 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; import type { ProductivityData, HabitAnalytics, ProjectAnalytics } from "@/lib/types"; import { format, subDays, parseISO, startOfMonth, eachDayOfInterval } from "date-fns"; // Simple SVG-based charts (no recharts dependency needed for basic charts) function LineChart({ data, xKey, yKey, color = "#3b82f6", height = 120 }: { data: any[]; xKey: string; yKey: string; color?: string; height?: number }) { if (!data.length) return

No data

; const maxVal = Math.max(...data.map((d) => d[yKey]), 1); const width = Math.max(data.length * 30, 200); const points = data.map((d, i) => { const x = (i / (data.length - 1 || 1)) * (width - 40) + 20; const y = height - 20 - ((d[yKey] / maxVal) * (height - 40)); return `${x},${y}`; }).join(" "); return ( {data.map((d, i) => { const x = (i / (data.length - 1 || 1)) * (width - 40) + 20; const y = height - 20 - ((d[yKey] / maxVal) * (height - 40)); return ; })} ); } function BarChart({ data, xKey, yKey, color = "#3b82f6", height = 120 }: { data: any[]; xKey: string; yKey: string; color?: string; height?: number }) { if (!data.length) return

No data

; const maxVal = Math.max(...data.map((d) => d[yKey]), 1); const barWidth = Math.max(20, Math.min(40, (300 / data.length))); const width = Math.max(data.length * (barWidth + 4) + 40, 200); return ( {data.map((d, i) => { const barH = (d[yKey] / maxVal) * (height - 30); const x = i * (barWidth + 4) + 20; const y = height - 20 - barH; return ; })} ); } function HorizontalBar({ data, xKey, yKey, height = 100 }: { data: any[]; xKey: string; yKey: string; height?: number }) { if (!data.length) return

No data

; const maxVal = Math.max(...data.map((d) => d[yKey]), 1); return (
{data.map((d, i) => (
{d[xKey]}
{d[yKey]}
))}
); } function PieChartSimple({ data, labelKey, valueKey, size = 120 }: { data: any[]; labelKey: string; valueKey: string; size?: number }) { if (!data.length) return

No data

; const total = data.reduce((s, d) => s + d[valueKey], 0) || 1; const colors = ["#3b82f6", "#22c55e", "#f97316", "#a855f7", "#e11d48", "#14b8a6"]; let cumulative = 0; const slices = data.map((d, i) => { const pct = d[valueKey] / total; const startAngle = cumulative * 360; cumulative += pct; const endAngle = cumulative * 360; const startRad = (startAngle - 90) * Math.PI / 180; const endRad = (endAngle - 90) * Math.PI / 180; const r = size / 2 - 4; const cx = size / 2; const cy = size / 2; const x1 = cx + r * Math.cos(startRad); const y1 = cy + r * Math.sin(startRad); const x2 = cx + r * Math.cos(endRad); const y2 = cy + r * Math.sin(endRad); const largeArc = pct > 0.5 ? 1 : 0; return { path: `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`, color: colors[i % colors.length], label: d[labelKey], pct: Math.round(pct * 100) }; }); return (
{slices.map((s, i) => )}
{slices.map((s, i) => (
{s.label} ({s.pct}%)
))}
); } function CalendarHeatmap({ data, days = 30 }: { data: any[]; days?: number }) { const today = new Date(); const dateMap = new Map(data.map((d) => [d.date?.slice(0, 10), d.count || 0])); const cells = []; for (let i = days - 1; i >= 0; i--) { const d = subDays(today, i); const key = format(d, "yyyy-MM-dd"); const count = dateMap.get(key) || 0; const intensity = count > 0 ? Math.min(count / 5, 1) : 0; const color = intensity > 0.75 ? "bg-green-600" : intensity > 0.5 ? "bg-green-500" : intensity > 0.25 ? "bg-green-400" : intensity > 0 ? "bg-green-200" : "bg-muted"; cells.push(
); } return
{cells}
; } // ─── Analytics Page ────────────────────────────────────────────────────── function AnalyticsPage() { const [range, setRange] = useState("30"); const { data: prodData } = useApiQuery(["analytics-productivity", range], "/analytics/productivity?range=" + range); const { data: habitData } = useApiQuery(["analytics-habits", range], "/analytics/habits?range=" + range); const { data: projectData } = useApiQuery(["analytics-projects", range], "/analytics/projects?range=" + range); // Generate mock daily data for charts (real API returns aggregated, we simulate daily breakdown) const dailyData = useMemo(() => { const days = parseInt(range); return Array.from({ length: days }, (_, i) => { const d = subDays(new Date(), days - 1 - i); return { date: format(d, "yyyy-MM-dd"), completed: Math.floor(Math.random() * 5), created: Math.floor(Math.random() * 8) + 1, }; }); }, [range]); const habitRateData = useMemo(() => { return [ { name: "Completed", value: habitData?.totalLogs || 0 }, { name: "Missed", value: Math.max(0, (habitData?.totalHabits || 1) * parseInt(range) - (habitData?.totalLogs || 0)) }, ]; }, [habitData, range]); const downloadCSV = (filename: string, rows: string[][]) => { const csv = rows.map((r) => r.map((c) => '"' + c.replace(/"/g, '""') + '"').join(",")).join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); }; return (

Analytics

{/* Tasks completed per day */} Tasks Completed {/* Tasks created vs completed */} Created vs Completed
Created
Completed
{/* Habit completion rate */} Habit Completion {/* Project progress */} Project Progress

{projectData?.taskCompletionRate || 0}%

Rate

{projectData?.totalTasks || 0}

Total

{projectData?.completedTasks || 0}

Done

{/* Time spent per domain (pie) */} Time per Domain {/* Productivity heatmap */} Productivity Heatmap ({ date: d.date, count: d.completed }))} days={parseInt(range)} />
); } export const Route = createRoute({ getParentRoute: () => appRoute, path: "/analytics", component: AnalyticsPage, });