Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions src/app/api/schools/[name]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export async function GET(
{ params }: { params: Promise<{ name: string }> },
) {
try {
const { searchParams } = new URL(req.url);
const year = Number(searchParams.get("year"));
const { name } = await params;
const searchName = name.replace(/-/g, " ");

Expand All @@ -100,10 +102,7 @@ export async function GET(
.select({ total: sum(projects.numStudents) })
.from(projects)
.where(
and(
eq(projects.schoolId, school.id),
eq(projects.year, pastYear),
),
and(eq(projects.schoolId, school.id), eq(projects.year, year)),
);

const teacherCount = await db
Expand All @@ -112,18 +111,27 @@ export async function GET(
.where(
and(
eq(yearlyTeacherParticipation.schoolId, school.id),
eq(yearlyTeacherParticipation.year, pastYear),
eq(yearlyTeacherParticipation.year, year),
),
);

const projectCount = await db
.select({ count: sql<number>`count(*)` })
.from(projects)
.where(
and(
eq(projects.schoolId, school.id),
eq(projects.year, pastYear),
),
and(eq(projects.schoolId, school.id), eq(projects.year, year)),
);

const projectRows = await db
.select({
id: projects.id,
title: projects.title,
numStudents: projects.numStudents,
year: projects.year,
})
.from(projects)
.where(
and(eq(projects.schoolId, school.id), eq(projects.year, year)),
);

// First year would be minimum year found in a school's projects
Expand All @@ -143,6 +151,7 @@ export async function GET(
teacherCount: teacherCount[0]?.count ?? 0,
projectCount: projectCount[0]?.count ?? 0,
firstYear: firstYearData[0]?.year ?? null,
projects: projectRows,
// TO DO: Instructional model not in database yet
instructionalModel: "normal",
});
Expand Down
127 changes: 119 additions & 8 deletions src/app/schools/[name]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import { Breadcrumbs } from "@/components/Breadcrumbs";
import { SchoolProfileSkeleton } from "@/components/skeletons/SchoolProfileSkeleton";
import { MapPlacer } from "@/components/ui/mapPlacer";
import { SchoolInfoRow } from "@/components/SchoolInfoRow";
import YearDropdown from "@/components/YearDropdown";
import MultiLineGraph, { GraphDataset } from "@/components/LineGraph";
import { DataTable } from "@/components/DataTable";
import { ColumnDef } from "@tanstack/react-table";
import { ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button";

// interface such that data can be blank if API is loading
type SchoolData = {
Expand All @@ -27,6 +33,7 @@ type SchoolData = {
teacherCount: string;
projectCount: string;
firstYear: string;
projects: ProjectRow[];
instructionalModel: string;
};

Expand All @@ -35,6 +42,19 @@ type MapCoordinates = {
longitude: number | null;
};

type ProjectRow = {
id: string;
title: string;
numStudents: number;
year: number;
};

type chartFilters = {
yearStart: number;
yearEnd: number;
isProjects: boolean;
};

export default function SchoolProfilePage() {
const params = useParams();
const schoolName = params.name as string;
Expand All @@ -43,9 +63,31 @@ export default function SchoolProfilePage() {

const [schoolData, setSchoolData] = useState<SchoolData | null>(null);
const [coordinates, setCoordinates] = useState<MapCoordinates | null>(null);
const [year, setYear] = useState<number>(2025);
const [projects, setProjects] = useState<ProjectRow[]>([]);
const [studentYearData, setstudentYearData] = useState<
{ x: string | number; y: number }[]
>([]);

const projectColumns: ColumnDef<ProjectRow>[] = [
{
accessorKey: "title",
header: "Title",
},
{
accessorKey: "numStudents",
header: "Students",
},
{
accessorKey: "year",
header: "Year",
},
];

useEffect(() => {
fetch(`/api/schools/${schoolName}`)
if (!year) return;

fetch(`/api/schools/${schoolName}?year=${year}`)
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch school data`);
Expand All @@ -54,6 +96,7 @@ export default function SchoolProfilePage() {
})
.then((data) => {
setSchoolData(data);
setProjects(data.projects);
})
.catch(() => {
toast.error(
Expand All @@ -64,7 +107,44 @@ export default function SchoolProfilePage() {
router.push("/schools");
}, 2000);
});
}, [schoolName, router]);
}, [schoolName, router, year]);

// Fetches student data for the last 5 years
useEffect(() => {
const fetchData = async () => {
setstudentYearData([]);
for (let i = 5; i >= 0; i--) {
try {
const res = await fetch(
`/api/schools/${schoolName}?year=${year - i}`,
);
const yearInfo = await res.json();

const thisYear: { x: string | number; y: number } = {
x: year - i,
y: yearInfo.studentCount,
};
setstudentYearData((prev) => [thisYear, ...prev]);
} catch {
toast.error(
"Failed to load dashboard data. Please try again.",
);
}
}
};
fetchData();
}, [year]);

const studentData: GraphDataset = {
label: "Students by Year",
data: studentYearData,
};

const studentChartFilters: chartFilters = {
yearStart: year - 5,
yearEnd: year,
isProjects: false,
};

if (!schoolData) {
return <SchoolProfileSkeleton />;
Expand All @@ -76,6 +156,16 @@ export default function SchoolProfilePage() {
<Breadcrumbs />
{/* Header with school name */}
<h1 className="text-2xl font-bold">{schoolData.name}</h1>
<YearDropdown
showDataIndicator={true}
selectedYear={year}
onYearChange={(selectedYear) => {
if (selectedYear !== null) {
setYear(selectedYear);
}
}}
school={schoolData.name}
/>

{/* Stats cards */}
<div className="grid grid-cols-3 gap-8">
Expand All @@ -99,6 +189,28 @@ export default function SchoolProfilePage() {
instructionalModel={schoolData.instructionalModel}
firstYear={schoolData.firstYear}
/>
<div className="flex flex-col m-5">
<div className="flex flex-row gap-3 items-center">
Total # Students
<Button
onClick={() =>
router.push(
`/chart?type=line&startYear=${studentChartFilters.yearStart}&endYear=${studentChartFilters.yearEnd}&measuredAs=total-student-count&schools=${schoolData.name}`,
)
}
>
<div className="flex flex-row gap-3">
View More
<ArrowRight></ArrowRight>
</div>
</Button>
</div>
<MultiLineGraph
datasets={[studentData]}
yAxisLabel={"Total # Students"}
xAxisLabel="Year"
/>
</div>

{/* Placeholders for charts */}
<div className="grid grid-cols-3 gap-8">
Expand Down Expand Up @@ -150,13 +262,12 @@ export default function SchoolProfilePage() {
{/* Data table placeholder */}
<div className="border border-border rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4 text-foreground">
View and edit data
Project Data
</h2>
<div className="h-48 flex items-center justify-center bg-muted rounded">
<p className="text-sm text-muted-foreground">
Data table placeholder
</p>
</div>
<DataTable
columns={projectColumns}
data={projects}
></DataTable>
</div>
</div>
</div>
Expand Down
22 changes: 22 additions & 0 deletions src/app/schools/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import YearDropdown from "@/components/YearDropdown";

export default function SchoolsPage() {
const [schoolInfo, setSchoolInfo] = useState([]);
const [prevYearSchoolInfo, setPrevYearSchoolInfo] = useState([]);
const [year, setYear] = useState<number | null>(2025);
const [search, setSearch] = useState("");
const [error, setError] = useState<string | null>(null);
Expand All @@ -44,6 +45,26 @@ export default function SchoolsPage() {
});
}, [year]);

useEffect(() => {
if (!year) return;

setError(null);

fetch(`/api/schools?year=${year - 1}`)
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch school data`);
}
return response.json();
})
.then((data) => {
setPrevYearSchoolInfo(data);
})
.catch((error) => {
setError(error.message || "Failed to load school data");
});
}, [year]);

return (
<div className="font-sans h-full w-full max-w-full flex flex-col overflow-hidden px-6 py-5">
<div className="flex items-center shrink-0 pb-5">
Expand All @@ -68,6 +89,7 @@ export default function SchoolsPage() {
<SchoolsDataTable
columns={columns}
data={schoolInfo}
prevData={prevYearSchoolInfo}
globalFilter={search}
setGlobalFilter={setSearch}
/>
Expand Down
6 changes: 4 additions & 2 deletions src/components/BarGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type BarGraphProps = {
yAxisLabel: string;
xAxisLabel: string;
legendTitle?: string;
svgRefCopy: React.RefObject<SVGSVGElement | null>;
svgRefCopy?: React.RefObject<SVGSVGElement | null>;
};

export default function BarGraph({
Expand Down Expand Up @@ -281,7 +281,9 @@ export default function BarGraph({
return transform;
});

svgRefCopy.current = svgRef.current;
if (svgRefCopy !== undefined) {
svgRefCopy.current = svgRef.current;
}
}, [dataset]);

return (
Expand Down
Loading