Terminal
This commit is contained in:
@@ -1,87 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user