diff --git a/components/dashboard/course-strategy-sidebar.tsx b/components/dashboard/course-strategy-sidebar.tsx index 21a2c08..e90300f 100644 --- a/components/dashboard/course-strategy-sidebar.tsx +++ b/components/dashboard/course-strategy-sidebar.tsx @@ -1,7 +1,12 @@ "use client"; import * as React from "react"; -import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from "recharts"; +import { PieChart, Pie, Cell } from "recharts"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Slider } from "@/components/ui/slider"; import { CourseData } from "@/actions/get-course-data"; @@ -111,11 +116,27 @@ export function CourseStrategySidebar({ course }: CourseStrategySidebarProps) { { name: "Completed", value: completedWeight, - color: course.color || "#000", + fill: course.color || "hsl(var(--chart-1))", + }, + { + name: "Remaining", + value: remainingWeight, + fill: "hsl(var(--muted))", }, - { name: "Remaining", value: remainingWeight, color: "#e5e7eb" }, // gray-200 ]; + const chartConfig = { + value: { + label: "Weight", + }, + Completed: { + label: "Completed", + }, + Remaining: { + label: "Remaining", + }, + }; + // Calculate total weight for grade weights const totalWeight = React.useMemo(() => { return course.grade_weights.reduce((sum, weight) => { @@ -399,7 +420,7 @@ export function CourseStrategySidebar({ course }: CourseStrategySidebarProps) {
- + - - + {pieData.map((entry, index) => ( + + ))} - `${value.toFixed(1)}%`} - contentStyle={{ borderRadius: "8px" }} + `${value.toFixed(1)}%`} + /> + } /> - +
{completedWeight.toFixed(0)}% diff --git a/components/dashboard/workload-heatmap.tsx b/components/dashboard/workload-heatmap.tsx index fba4956..fcc21f2 100644 --- a/components/dashboard/workload-heatmap.tsx +++ b/components/dashboard/workload-heatmap.tsx @@ -3,17 +3,12 @@ import { DashboardMetrics } from "@/actions/dashboard"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { - BarChart, - Bar, - XAxis, - YAxis, - Tooltip, - ResponsiveContainer, - Cell, -} from "recharts"; + ChartContainer, + ChartTooltip, +} from "@/components/ui/chart"; +import { Bar, BarChart, XAxis, YAxis, Cell } from "recharts"; import { Activity } from "lucide-react"; import { format, parseISO } from "date-fns"; -import { useTheme } from "next-themes"; interface WorkloadHeatmapProps { data: DashboardMetrics["workloadHeatmap"]; @@ -29,9 +24,14 @@ export function WorkloadHeatmap({ data }: WorkloadHeatmapProps) { formattedDate: format(parseISO(d.date), "MMM d"), })); - const isDark = theme === "dark"; - const defaultBarColor = isDark ? "var(--chart-1)" : "var(--chart-1)"; // slate-200 : slate-900 - const warningBarColor = "var(--destructive)"; // red-500 + const defaultBarColor = "hsl(var(--chart-1))"; + const warningBarColor = "hsl(var(--destructive))"; + + const chartConfig = { + taskCount: { + label: "Tasks", + }, + }; return ( @@ -42,62 +42,60 @@ export function WorkloadHeatmap({ data }: WorkloadHeatmapProps) { -
- - - - - { - if (active && payload && payload.length) { - const item = payload[0].payload; - return ( -
-
-
- - {item.formattedDate} - - - {item.taskCount} Tasks - -
+ + + + + { + if (active && payload && payload.length) { + const item = payload[0].payload; + return ( +
+
+
+ + {item.formattedDate} + + + {item.taskCount} Tasks +
- ); +
+ ); + } + return null; + }} + /> + + {chartData.map((entry, index) => ( + 3 ? warningBarColor : defaultBarColor } - return null; - }} - /> - - {chartData.map((entry, index) => ( - 3 ? warningBarColor : defaultBarColor - } - /> - ))} - -
- -
+ /> + ))} + + + ); diff --git a/components/ui/chart.tsx b/components/ui/chart.tsx new file mode 100644 index 0000000..33c0978 --- /dev/null +++ b/components/ui/chart.tsx @@ -0,0 +1,328 @@ +"use client"; + +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; + +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + +
+ + + {children} + +
+
+ ); +}); +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, itemConfig]) => itemConfig.theme || itemConfig.color, + ); + + if (!colorConfig.length) { + return null; + } + + return ( +