"use client" import Link from "next/link" import { useMemo, useState } from "react" import { useRouter } from "next/navigation" import { Calendar, Copy, MoreHorizontal, Pencil, Plus, RefreshCw, Trash2, Users } from "lucide-react" import { toast } from "sonner" import { parseAsString, useQueryState } from "nuqs" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { Button } from "@/shared/components/ui/button" import { Badge } from "@/shared/components/ui/badge" import { EmptyState } from "@/shared/components/ui/empty-state" import { cn } from "@/shared/lib/utils" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/shared/components/ui/dropdown-menu" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/shared/components/ui/alert-dialog" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/shared/components/ui/dialog" import { Input } from "@/shared/components/ui/input" import { Label } from "@/shared/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/shared/components/ui/select" import type { TeacherClass } from "../types" import { createTeacherClassAction, deleteTeacherClassAction, ensureClassInvitationCodeAction, regenerateClassInvitationCodeAction, updateTeacherClassAction, } from "../actions" export function MyClassesGrid({ classes }: { classes: TeacherClass[] }) { const router = useRouter() const [isWorking, setIsWorking] = useState(false) const [createOpen, setCreateOpen] = useState(false) const [q, setQ] = useQueryState("q", parseAsString.withDefault("")) const [grade, setGrade] = useQueryState("grade", parseAsString.withDefault("all")) const gradeOptions = useMemo(() => { const set = new Set() for (const c of classes) set.add(c.grade) return Array.from(set).sort((a, b) => a.localeCompare(b)) }, [classes]) const filteredClasses = useMemo(() => { const needle = q.trim().toLowerCase() return classes.filter((c) => { const gradeOk = grade === "all" ? true : c.grade === grade const qOk = needle.length === 0 ? true : c.name.toLowerCase().includes(needle) return gradeOk && qOk }) }, [classes, grade, q]) const defaultGrade = useMemo(() => (grade !== "all" ? grade : classes[0]?.grade ?? ""), [classes, grade]) const handleCreate = async (formData: FormData) => { setIsWorking(true) try { const res = await createTeacherClassAction(null, formData) if (res.success) { toast.success(res.message) setCreateOpen(false) router.refresh() } else { toast.error(res.message || "Failed to create class") } } catch { toast.error("Failed to create class") } finally { setIsWorking(false) } } return (
setQ(e.target.value || null)} />
{(q || grade !== "all") && ( )}
{ if (isWorking) return setCreateOpen(open) }} > Create class Add a new class to start managing students.
{classes.length === 0 ? ( setCreateOpen(true) }} className="h-[360px] bg-card sm:col-span-2 lg:col-span-3" /> ) : filteredClasses.length === 0 ? ( { setQ(null) setGrade(null) }}} className="h-[360px] bg-card sm:col-span-2 lg:col-span-3" /> ) : ( filteredClasses.map((c) => ( )) )}
) } function ClassCard({ c, isWorking, onWorkingChange, }: { c: TeacherClass isWorking: boolean onWorkingChange: (v: boolean) => void }) { const router = useRouter() const [showEdit, setShowEdit] = useState(false) const [showDelete, setShowDelete] = useState(false) const handleEnsureCode = async () => { onWorkingChange(true) try { const res = await ensureClassInvitationCodeAction(c.id) if (res.success) { toast.success(res.message || "Invitation code ready") router.refresh() } else { toast.error(res.message || "Failed to generate invitation code") } } catch { toast.error("Failed to generate invitation code") } finally { onWorkingChange(false) } } const handleRegenerateCode = async () => { onWorkingChange(true) try { const res = await regenerateClassInvitationCodeAction(c.id) if (res.success) { toast.success(res.message || "Invitation code updated") router.refresh() } else { toast.error(res.message || "Failed to regenerate invitation code") } } catch { toast.error("Failed to regenerate invitation code") } finally { onWorkingChange(false) } } const handleCopyCode = async () => { const code = c.invitationCode ?? "" if (!code) return try { await navigator.clipboard.writeText(code) toast.success("Copied invitation code") } catch { toast.error("Failed to copy") } } const handleEdit = async (formData: FormData) => { onWorkingChange(true) try { const res = await updateTeacherClassAction(c.id, null, formData) if (res.success) { toast.success(res.message) setShowEdit(false) router.refresh() } else { toast.error(res.message || "Failed to update class") } } catch { toast.error("Failed to update class") } finally { onWorkingChange(false) } } const handleDelete = async () => { onWorkingChange(true) try { const res = await deleteTeacherClassAction(c.id) if (res.success) { toast.success(res.message) setShowDelete(false) router.refresh() } else { toast.error(res.message || "Failed to delete class") } } catch { toast.error("Failed to delete class") } finally { onWorkingChange(false) } } return (
{c.name}
{c.room ? `Room: ${c.room}` : "Room: Not set"}
{c.grade} setShowEdit(true)}> Edit setShowDelete(true)} > Delete
{c.studentCount} students
{c.homeroom ? {c.homeroom} : null}
Invitation code
{c.invitationCode ?? "-"}
{c.invitationCode ? ( <> ) : ( )}
{ if (isWorking) return setShowEdit(open) }} > Edit class Update basic class information.
{ if (isWorking) return setShowDelete(open) }} > Delete class? This will permanently delete {c.name} and remove all enrollments. Cancel {isWorking ? "Deleting..." : "Delete"}
) }