Modified by www.SourceFiles.app

This commit is contained in:
2026-08-07 04:15:27 +00:00
parent 1ff0b76240
commit 8c2e0379ba
@@ -0,0 +1,87 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { GradingConfigEditor } from "@/components/gradebook";
import { BookOpen, ChevronRight, Plus, SlidersHorizontal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export const Route = createFileRoute("/_authenticated/classes/")({
head: () => ({ meta: [{ title: "Classes — School Portal" }] }),
component: ClassesIndex,
});
function ClassesIndex() {
const { user, roles } = useAuth();
const isAdmin = roles.includes("admin");
const qc = useQueryClient();
const { data: classes } = useQuery({
queryKey: ["classes-list"],
queryFn: async () => (await supabase.from("classes").select("id, name, teacher_id").order("name")).data ?? [],
});
const visible = isAdmin ? (classes ?? []) : (classes ?? []).filter((c) => c.teacher_id === user?.id);
const { data: teachers } = useQuery({
queryKey: ["teachers"], enabled: isAdmin,
queryFn: async () => {
const { data: r } = await supabase.from("user_roles").select("user_id").eq("role", "teacher");
const ids = (r ?? []).map((x) => x.user_id);
if (!ids.length) return [];
return (await supabase.from("profiles").select("id, full_name, email").in("id", ids)).data ?? [];
},
});
const [name, setName] = useState("");
const [teacherId, setTeacherId] = useState("");
const create = useMutation({
mutationFn: async () => { const { error } = await supabase.from("classes").insert({ name: name.trim(), teacher_id: teacherId || null }); if (error) throw error; },
onSuccess: () => { setName(""); setTeacherId(""); qc.invalidateQueries({ queryKey: ["classes-list"] }); toast.success("Class created"); },
onError: (e: Error) => toast.error(e.message),
});
const [showDefaults, setShowDefaults] = useState(false);
return (
<div className="p-8 max-w-4xl">
<h1 className="text-2xl font-semibold mb-1">Classes</h1>
<p className="text-muted-foreground text-sm mb-6">Rosters and gradebooks.</p>
{isAdmin && (
<div className="bg-card border rounded-lg p-4 space-y-3 mb-4">
<div className="font-medium text-sm">New class</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
<Input placeholder="Class name" value={name} onChange={(e) => setName(e.target.value)} />
<Select value={teacherId} onValueChange={setTeacherId}>
<SelectTrigger><SelectValue placeholder="Teacher (optional)" /></SelectTrigger>
<SelectContent>{(teachers ?? []).map((t) => <SelectItem key={t.id} value={t.id}>{t.full_name || t.email}</SelectItem>)}</SelectContent>
</Select>
<Button onClick={() => create.mutate()} disabled={!name.trim() || create.isPending}><Plus className="h-4 w-4 mr-1" /> Add class</Button>
</div>
</div>
)}
<div className="bg-card border rounded-lg divide-y mb-6">
{visible.map((c) => (
<Link key={c.id} to="/classes/$id" params={{ id: c.id }} className="flex items-center justify-between p-3 hover:bg-muted/50">
<span className="flex items-center gap-2 font-medium"><BookOpen className="h-4 w-4 text-muted-foreground" /> {c.name}</span>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
</Link>
))}
{visible.length === 0 && <div className="p-4 text-sm text-muted-foreground">{isAdmin ? "No classes yet — create one above." : "You have no classes assigned."}</div>}
</div>
{isAdmin && (
<div className="bg-card border rounded-lg p-4">
<button className="flex items-center gap-2 font-medium text-sm" onClick={() => setShowDefaults((s) => !s)}>
<SlidersHorizontal className="h-4 w-4" /> School grading defaults
</button>
<p className="text-xs text-muted-foreground mt-1">Applied to any class that hasn't set its own categories/scale.</p>
{showDefaults && <div className="mt-4"><GradingConfigEditor classId={null} /></div>}
</div>
)}
</div>
);
}