From 1dc69aeb507f03288b9e7dacf1aaca57e379cc85 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 04:13:07 +0000 Subject: [PATCH] Modified by www.SourceFiles.app --- src/routes/_authenticated/admin.tsx | 284 ---------------------------- 1 file changed, 284 deletions(-) delete mode 100644 src/routes/_authenticated/admin.tsx diff --git a/src/routes/_authenticated/admin.tsx b/src/routes/_authenticated/admin.tsx deleted file mode 100644 index f46223f..0000000 --- a/src/routes/_authenticated/admin.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import { createFileRoute } 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 { Label } from "@/components/ui/label"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Plus, Trash2, Lock, UserPlus, Copy } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { createUserFn } from "@/lib/user-admin.functions"; -import { genTempPassword } from "@/lib/temp-password"; - -type Role = "admin" | "teacher" | "parent"; - -export const Route = createFileRoute("/_authenticated/admin")({ - head: () => ({ meta: [{ title: "Admin — School Portal" }] }), - component: AdminPage, -}); - -function AdminPage() { - const { roles } = useAuth(); - if (!roles.includes("admin")) { - return

Admins only

; - } - return ( -
-

Admin

-

Manage users, roles, classes, and parent-student links.

- - - Users & roles - Classes - Parent ↔ student - - - - - -
- ); -} - -function UsersTab() { - const qc = useQueryClient(); - const { data } = useQuery({ - queryKey: ["admin-users"], - queryFn: async () => { - const { data: profiles } = await supabase.from("profiles").select("id, full_name, email"); - const { data: roles } = await supabase.from("user_roles").select("user_id, role"); - const rolesByUser: Record = {}; - (roles ?? []).forEach((r) => { rolesByUser[r.user_id] = [...(rolesByUser[r.user_id] ?? []), r.role as Role]; }); - return (profiles ?? []).map((p) => ({ ...p, roles: rolesByUser[p.id] ?? [] })); - }, - }); - - const setRole = useMutation({ - mutationFn: async ({ userId, role, on }: { userId: string; role: Role; on: boolean }) => { - if (on) { - const { error } = await supabase.from("user_roles").insert({ user_id: userId, role }); - if (error && !error.message.includes("duplicate")) throw error; - } else { - const { error } = await supabase.from("user_roles").delete().eq("user_id", userId).eq("role", role); - if (error) throw error; - } - }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-users"] }), - onError: (e: Error) => toast.error(e.message), - }); - - const [nu, setNu] = useState({ fullName: "", email: "", role: "teacher" as Role }); - const [created, setCreated] = useState<{ email: string; password: string } | null>(null); - const addUser = useMutation({ - mutationFn: async () => { - const password = genTempPassword(); - const res = await createUserFn({ data: { fullName: nu.fullName, email: nu.email, password, role: nu.role } }); - return { email: res.email, password }; - }, - onSuccess: (r) => { - setCreated(r); - setNu({ fullName: "", email: "", role: "teacher" }); - qc.invalidateQueries({ queryKey: ["admin-users"] }); - qc.invalidateQueries({ queryKey: ["teachers"] }); - toast.success(`Created ${r.email}`); - }, - onError: (e: Error) => toast.error(e.message), - }); - - return ( -
-
-
Add a user
-
- setNu({ ...nu, fullName: e.target.value })} /> - setNu({ ...nu, email: e.target.value })} /> - - -
-

Creates a login with a temporary password (shown once). They can change it after signing in. No email is sent.

- {created && ( -
-
Account created — share these with {created.email}:
-
- Email: {created.email} - Password: {created.password} - -
-
- )} -
- -
- - - - {(data ?? []).map((u) => ( - - - - - - ))} - -
NameEmailRoles
{u.full_name || "—"}{u.email} -
- {(["admin", "teacher", "parent"] as Role[]).map((r) => { - const on = u.roles.includes(r); - return ; - })} -
-
-
-
- ); -} - -function ClassesTab() { - const qc = useQueryClient(); - const [name, setName] = useState(""); - const [teacherId, setTeacherId] = useState(""); - - const { data: classes } = useQuery({ - queryKey: ["admin-classes"], - queryFn: async () => { - const { data } = await supabase.from("classes").select("*").order("name"); - const tids = Array.from(new Set((data ?? []).map((c) => c.teacher_id).filter(Boolean))) as string[]; - const { data: profs } = tids.length ? await supabase.from("profiles").select("id, full_name").in("id", tids) : { data: [] }; - const map = Object.fromEntries((profs ?? []).map((p) => [p.id, p.full_name])); - return (data ?? []).map((c) => ({ ...c, teacher_name: c.teacher_id ? map[c.teacher_id] : null })); - }, - }); - const { data: teachers } = useQuery({ - queryKey: ["teachers"], - 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 === 0) return []; - const { data } = await supabase.from("profiles").select("id, full_name, email").in("id", ids); - return data ?? []; - }, - }); - - const add = useMutation({ - mutationFn: async () => { - const { error } = await supabase.from("classes").insert({ name, teacher_id: teacherId || null }); - if (error) throw error; - }, - onSuccess: () => { setName(""); setTeacherId(""); qc.invalidateQueries({ queryKey: ["admin-classes"] }); toast.success("Class added"); }, - onError: (e: Error) => toast.error(e.message), - }); - const del = useMutation({ - mutationFn: async (id: string) => { const { error } = await supabase.from("classes").delete().eq("id", id); if (error) throw error; }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-classes"] }), - }); - - return ( -
-
- {(classes ?? []).map((c) => ( -
-
-
{c.name}
-
Teacher: {c.teacher_name ?? "unassigned"}
-
- -
- ))} - {classes?.length === 0 &&
No classes yet.
} -
-
-
New class
-
- setName(e.target.value)} /> - - -
-
-
- ); -} - -function LinksTab() { - const qc = useQueryClient(); - const [parentId, setParentId] = useState(""); - const [studentId, setStudentId] = useState(""); - - const { data: links } = useQuery({ - queryKey: ["admin-links"], - queryFn: async () => { - const { data } = await supabase.from("parent_students").select("id, parent_id, students(first_name, last_name)"); - const ids = (data ?? []).map((d) => d.parent_id); - const { data: profs } = ids.length ? await supabase.from("profiles").select("id, full_name, email").in("id", ids) : { data: [] }; - const map = Object.fromEntries((profs ?? []).map((p) => [p.id, p])); - return (data ?? []).map((d) => ({ ...d, profile: map[d.parent_id] })); - }, - }); - const { data: parents } = useQuery({ - queryKey: ["all-parents"], - queryFn: async () => { - const { data: r } = await supabase.from("user_roles").select("user_id").eq("role", "parent"); - const ids = (r ?? []).map((x) => x.user_id); - if (ids.length === 0) return []; - const { data } = await supabase.from("profiles").select("id, full_name, email").in("id", ids); - return data ?? []; - }, - }); - const { data: students } = useQuery({ - queryKey: ["all-students-link"], - queryFn: async () => (await supabase.from("students").select("id, first_name, last_name").order("last_name")).data ?? [], - }); - - const add = useMutation({ - mutationFn: async () => { - const { error } = await supabase.from("parent_students").insert({ parent_id: parentId, student_id: studentId }); - if (error) throw error; - }, - onSuccess: () => { setParentId(""); setStudentId(""); qc.invalidateQueries({ queryKey: ["admin-links"] }); toast.success("Linked"); }, - onError: (e: Error) => toast.error(e.message), - }); - const del = useMutation({ - mutationFn: async (id: string) => { const { error } = await supabase.from("parent_students").delete().eq("id", id); if (error) throw error; }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-links"] }), - }); - - return ( -
-
- {(links ?? []).map((l) => { - const p = l.profile as { full_name?: string; email?: string } | undefined; - const s = l.students as { first_name: string; last_name: string } | null; - return ( -
- {p?.full_name || p?.email} → {s?.first_name} {s?.last_name} - -
- ); - })} - {links?.length === 0 &&
No links yet.
} -
-
-
Link parent to student
-
- - - -
-
-
- ); -}