From 058b684f9b47c96c1f85087ddeff491d6ed268be Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Sat, 18 Oct 2025 19:12:56 -0400 Subject: [PATCH 01/54] added category attribute to requirements, seeded mock data --- packages/server/convex/_generated/api.d.ts | 4 + packages/server/convex/http.ts | 6 + packages/server/convex/mockData.ts | 189 +++++++++++++++++++++ packages/server/convex/requirements.ts | 2 + packages/server/convex/schemas/programs.ts | 3 + packages/server/convex/seed.ts | 100 +++++++++++ 6 files changed, 304 insertions(+) create mode 100644 packages/server/convex/mockData.ts create mode 100644 packages/server/convex/seed.ts diff --git a/packages/server/convex/_generated/api.d.ts b/packages/server/convex/_generated/api.d.ts index 22832dc4..36e57d45 100644 --- a/packages/server/convex/_generated/api.d.ts +++ b/packages/server/convex/_generated/api.d.ts @@ -13,6 +13,7 @@ import type * as courseOfferings from "../courseOfferings.js"; import type * as courses from "../courses.js"; import type * as helpers_auth from "../helpers/auth.js"; import type * as http from "../http.js"; +import type * as mockData from "../mockData.js"; import type * as prerequisites from "../prerequisites.js"; import type * as programs from "../programs.js"; import type * as requirements from "../requirements.js"; @@ -21,6 +22,7 @@ import type * as schemas_courseOfferings from "../schemas/courseOfferings.js"; import type * as schemas_courses from "../schemas/courses.js"; import type * as schemas_programs from "../schemas/programs.js"; import type * as schemas_students from "../schemas/students.js"; +import type * as seed from "../seed.js"; import type * as students from "../students.js"; import type * as userCourseOfferings from "../userCourseOfferings.js"; import type * as userCourses from "../userCourses.js"; @@ -45,6 +47,7 @@ declare const fullApi: ApiFromModules<{ courses: typeof courses; "helpers/auth": typeof helpers_auth; http: typeof http; + mockData: typeof mockData; prerequisites: typeof prerequisites; programs: typeof programs; requirements: typeof requirements; @@ -53,6 +56,7 @@ declare const fullApi: ApiFromModules<{ "schemas/courses": typeof schemas_courses; "schemas/programs": typeof schemas_programs; "schemas/students": typeof schemas_students; + seed: typeof seed; students: typeof students; userCourseOfferings: typeof userCourseOfferings; userCourses: typeof userCourses; diff --git a/packages/server/convex/http.ts b/packages/server/convex/http.ts index 2c0b731f..dbd20764 100644 --- a/packages/server/convex/http.ts +++ b/packages/server/convex/http.ts @@ -57,16 +57,19 @@ export const ZUpsertProgramWithRequirements = z.object({ z.object({ isMajor: z.boolean(), type: z.literal("required"), + category: z.string(), courses: z.array(z.string()), }), z.object({ isMajor: z.boolean(), type: z.literal("alternative"), + category: z.string(), courses: z.array(z.string()), }), z.object({ isMajor: z.boolean(), type: z.literal("options"), + category: z.string(), courses: z.array(z.string()), courseLevels: z.array( z.object({ @@ -89,6 +92,7 @@ export const ZUpsertRequirements = z.array( ), isMajor: z.boolean(), type: z.literal("required"), + category: z.string(), courses: z.array(z.string()), }), z.object({ @@ -98,6 +102,7 @@ export const ZUpsertRequirements = z.array( ), isMajor: z.boolean(), type: z.literal("alternative"), + category: z.string(), courses: z.array(z.string()), }), z.object({ @@ -107,6 +112,7 @@ export const ZUpsertRequirements = z.array( ), isMajor: z.boolean(), type: z.literal("options"), + category: z.string(), courses: z.array(z.string()), courseLevels: z.array( z.object({ diff --git a/packages/server/convex/mockData.ts b/packages/server/convex/mockData.ts new file mode 100644 index 00000000..d3784d13 --- /dev/null +++ b/packages/server/convex/mockData.ts @@ -0,0 +1,189 @@ +/** + * Mock data for NYU CS curriculum + * This file contains sample program requirements categorized similar to cs.nyu.edu + */ + +export const mockProgramsData = { + program: { + name: "Computer Science (BA)", + level: "undergraduate" as const, + programUrl: "https://cs.nyu.edu/home/undergrad/major_programs.html", + }, +}; + +/** + * Categories for course requirements (similar to NYU CS curriculum): + * - Computer Science: All CS courses including core, theory, systems, and electives + * - Mathematics: Pure math courses + * - Natural Science: Physics, Chemistry, Biology + * - General Education: Non-CS/Math/Science requirements + */ + +export const mockRequirementsData = [ + // Computer Science Requirements + { + type: "required" as const, + isMajor: true, + category: "Computer Science", + courses: [ + "CSCI-UA 101", // Intro to Computer Science + "CSCI-UA 102", // Data Structures + "CSCI-UA 201", // Computer Systems Organization + "CSCI-UA 310", // Basic Algorithms + "CSCI-UA 202", // Operating Systems + "CSCI-UA 480", // Parallel Computing (Special Topics) + ], + }, + + // Mathematics Requirements + { + type: "required" as const, + isMajor: true, + category: "Mathematics", + courses: [ + "MATH-UA 120", // Discrete Mathematics + "MATH-UA 121", // Calculus I + "MATH-UA 122", // Calculus II + ], + }, + + // Math Alternative (students can choose one) + { + type: "alternative" as const, + isMajor: true, + category: "Mathematics", + courses: [ + "MATH-UA 140", // Linear Algebra + "MATH-UA 325", // Analysis + ], + }, + + // Advanced CS Electives (options - choose courses to fulfill credit requirements) + { + type: "options" as const, + isMajor: true, + category: "Computer Science", + courses: [ + "CSCI-UA 467", // Intro to Machine Learning + "CSCI-UA 473", // Fundamentals of AI + "CSCI-UA 479", // Data Management & Analysis + "CSCI-UA 421", // Computer Graphics + "CSCI-UA 453", // Theory of Computation + "CSCI-UA 480-009", // Computer Security + "CSCI-UA 480-010", // Agile Software Development + ], + courseLevels: [ + { + program: "CSCI-UA", + level: 300, + }, + { + program: "CSCI-UA", + level: 400, + }, + ], + creditsRequired: 12, // 12 credits of electives + }, + + // Science Requirements + { + type: "required" as const, + isMajor: false, + category: "Natural Science", + courses: [ + "PHYS-UA 11", // General Physics I + "CHEM-UA 125", // General Chemistry I + ], + }, + + // Science Lab Alternative + { + type: "alternative" as const, + isMajor: false, + category: "Natural Science", + courses: [ + "PHYS-UA 91", // Physics Lab + "CHEM-UA 127", // Chemistry Lab + "BIOL-UA 11", // Biology Lab + ], + }, + + // General Education - Writing Requirements + { + type: "required" as const, + isMajor: false, + category: "General Education", + courses: [ + "EXPOS-UA 1", // Writing the Essay + ], + }, + + // General Education - Humanities Electives + { + type: "options" as const, + isMajor: false, + category: "General Education", + courses: [ + "PHIL-UA 101", // Introduction to Philosophy + "HIST-UA 101", // World History + "ENGL-UA 201", // Literature Survey + ], + courseLevels: [ + { + program: "PHIL-UA", + level: 100, + }, + { + program: "HIST-UA", + level: 100, + }, + { + program: "ENGL-UA", + level: 100, + }, + ], + creditsRequired: 6, // 6 credits of humanities + }, +]; + +/** + * Additional sample programs for testing charts + */ +export const additionalMockPrograms = [ + { + name: "Computer Science (BS)", + level: "undergraduate" as const, + programUrl: "https://cs.nyu.edu/home/undergrad/major_programs.html", + }, + { + name: "Mathematics (BA)", + level: "undergraduate" as const, + programUrl: "https://math.nyu.edu/undergraduate/major.html", + }, + { + name: "Data Science (MS)", + level: "graduate" as const, + programUrl: "https://cds.nyu.edu/academics/ms-in-data-science/", + }, + { + name: "Economics Minor", + level: "undergraduate" as const, + programUrl: "https://as.nyu.edu/economics/undergrad/minors.html", + }, + { + name: "Business Studies Minor", + level: "undergraduate" as const, + programUrl: "https://www.stern.nyu.edu/programs-admissions/undergraduate/academics/minors", + }, +]; + +/** + * Course categories breakdown for charts: + * This can be used to visualize distribution of requirements + */ +export const categoryStats = { + "Computer Science": 10, // 6 required + 4 electives (average) + "Mathematics": 4, // 3 required + 1 alternative + "Natural Science": 3, + "General Education": 3, +}; diff --git a/packages/server/convex/requirements.ts b/packages/server/convex/requirements.ts index e79c1cf2..2b305d1c 100644 --- a/packages/server/convex/requirements.ts +++ b/packages/server/convex/requirements.ts @@ -14,6 +14,7 @@ export const createRequirementsInternal = internalMutation({ programId: newReq.programId, isMajor: newReq.isMajor, type: newReq.type, + category: newReq.category, courses: newReq.courses, courseLevels: newReq.courseLevels, creditsRequired: newReq.creditsRequired, @@ -23,6 +24,7 @@ export const createRequirementsInternal = internalMutation({ programId: newReq.programId, isMajor: newReq.isMajor, type: newReq.type, + category: newReq.category, courses: newReq.courses, }); }), diff --git a/packages/server/convex/schemas/programs.ts b/packages/server/convex/schemas/programs.ts index f2c56cd2..f9ba4fa9 100644 --- a/packages/server/convex/schemas/programs.ts +++ b/packages/server/convex/schemas/programs.ts @@ -11,18 +11,21 @@ const requirements = v.union( programId: v.id("programs"), isMajor: v.boolean(), type: v.literal("required"), + category: v.string(), // e.g., "Computer Science", "Mathematics", "Natural Science", "General Education" courses: v.array(v.string()), // course code }), v.object({ programId: v.id("programs"), isMajor: v.boolean(), type: v.literal("alternative"), + category: v.string(), // e.g., "Computer Science", "Mathematics", "Natural Science", "General Education" courses: v.array(v.string()), // course code }), v.object({ programId: v.id("programs"), isMajor: v.boolean(), type: v.literal("options"), + category: v.string(), // e.g., "Computer Science", "Mathematics", "Natural Science", "General Education" courses: v.array(v.string()), // course code courseLevels: v.array( v.object({ diff --git a/packages/server/convex/seed.ts b/packages/server/convex/seed.ts new file mode 100644 index 00000000..e3477eab --- /dev/null +++ b/packages/server/convex/seed.ts @@ -0,0 +1,100 @@ +import { internalMutation } from "./_generated/server"; +import { internal } from "./_generated/api"; +import { + mockProgramsData, + mockRequirementsData, + additionalMockPrograms, +} from "./mockData"; + +/** + * Seed the database with mock NYU CS curriculum data + * Call this mutation from the Convex dashboard to populate your database + */ +export const seedMockData = internalMutation({ + args: {}, + handler: async (ctx) => { + console.log("Starting to seed mock data..."); + + // Insert the main Computer Science (BA) program + const programId = await ctx.runMutation( + internal.programs.upsertProgramInternal, + mockProgramsData.program, + ); + + console.log("Created program:", programId); + + // Insert requirements for the CS program + if (mockRequirementsData.length > 0) { + // First delete any existing requirements + await ctx.runMutation( + internal.requirements.deleteRequirementsByProgramInternal, + { programId }, + ); + + // Then create new requirements + await ctx.runMutation(internal.requirements.createRequirementsInternal, { + requirements: mockRequirementsData.map((req) => ({ + ...req, + programId, + })), + }); + + console.log(`Created ${mockRequirementsData.length} requirements`); + } + + // Insert additional programs + const additionalProgramIds = []; + for (const program of additionalMockPrograms) { + const id = await ctx.runMutation( + internal.programs.upsertProgramInternal, + program, + ); + additionalProgramIds.push(id); + } + + console.log( + `Created ${additionalMockPrograms.length} additional programs:`, + additionalProgramIds, + ); + + return { + success: true, + programId, + additionalProgramIds, + requirementsCount: mockRequirementsData.length, + }; + }, +}); + +/** + * Clear all programs and requirements from the database + * Use this to reset your database before re-seeding + */ +export const clearAllData = internalMutation({ + args: {}, + handler: async (ctx) => { + console.log("Clearing all programs and requirements..."); + + // Delete all requirements first (due to foreign key relationship) + const allRequirements = await ctx.db.query("requirements").collect(); + for (const req of allRequirements) { + await ctx.db.delete(req._id); + } + + // Delete all programs + const allPrograms = await ctx.db.query("programs").collect(); + for (const program of allPrograms) { + await ctx.db.delete(program._id); + } + + console.log( + `Deleted ${allRequirements.length} requirements and ${allPrograms.length} programs`, + ); + + return { + success: true, + deletedRequirements: allRequirements.length, + deletedPrograms: allPrograms.length, + }; + }, +}); From edef155293b13ea069f25173fb5705e332b956a1 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Sat, 18 Oct 2025 19:31:51 -0400 Subject: [PATCH 02/54] imported shadcn chart skeleton --- apps/web/package.json | 3 +- .../components/charts/degree-charts.tsx | 103 +++++ apps/web/src/app/dashboard/page.tsx | 8 +- apps/web/src/components/ui/chart.tsx | 357 ++++++++++++++++++ 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/dashboard/components/charts/degree-charts.tsx create mode 100644 apps/web/src/components/ui/chart.tsx diff --git a/apps/web/package.json b/apps/web/package.json index e394b890..8be24c32 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -28,11 +28,12 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "^1.28.0", - "lucide-react": "^0.545.0", + "lucide-react": "^0.546.0", "next": "^15.5.5", "pdfjs-dist": "^5.4.296", "react": "^19.2.0", "react-dom": "^19.2.0", + "recharts": "2.15.4", "tailwind-merge": "^3.3.1", "zod": "^4.1.12" }, diff --git a/apps/web/src/app/dashboard/components/charts/degree-charts.tsx b/apps/web/src/app/dashboard/components/charts/degree-charts.tsx new file mode 100644 index 00000000..3b519f1b --- /dev/null +++ b/apps/web/src/app/dashboard/components/charts/degree-charts.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { TrendingUp } from "lucide-react"; +import { Bar, BarChart, XAxis, YAxis } from "recharts"; + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; + +export const description = "A mixed bar chart"; + +const chartData = [ + { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" }, + { browser: "safari", visitors: 200, fill: "var(--color-safari)" }, + { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" }, + { browser: "edge", visitors: 173, fill: "var(--color-edge)" }, + { browser: "other", visitors: 90, fill: "var(--color-other)" }, +]; + +const chartConfig = { + visitors: { + label: "Visitors", + }, + chrome: { + label: "Chrome", + color: "var(--chart-1)", + }, + safari: { + label: "Safari", + color: "var(--chart-2)", + }, + firefox: { + label: "Firefox", + color: "var(--chart-3)", + }, + edge: { + label: "Edge", + color: "var(--chart-4)", + }, + other: { + label: "Other", + color: "var(--chart-5)", + }, +} satisfies ChartConfig; + +export function ChartBarMixed() { + return ( + + + Bar Chart - Mixed + January - June 2024 + + + + + + chartConfig[value as keyof typeof chartConfig]?.label + } + /> + + } + /> + + + + + +
+ Trending up by 5.2% this month +
+
+ Showing total visitors for the last 6 months +
+
+
+ ); +} diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/web/src/app/dashboard/page.tsx index 53474f16..54868e98 100644 --- a/apps/web/src/app/dashboard/page.tsx +++ b/apps/web/src/app/dashboard/page.tsx @@ -1,7 +1,13 @@ import FileUploadButton from "@/modules/report-parsing/components/file-upload-button"; +import { ChartBarMixed } from "./components/charts/degree-charts"; const HomePage = () => { - return ; + return ( +
+ + +
+ ); }; export default HomePage; diff --git a/apps/web/src/components/ui/chart.tsx b/apps/web/src/components/ui/chart.tsx new file mode 100644 index 00000000..8b42f210 --- /dev/null +++ b/apps/web/src/components/ui/chart.tsx @@ -0,0 +1,357 @@ +"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 +} + +function ChartContainer({ + id, + className, + children, + config, + ...props +}: React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] +}) { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +