import { useState, useEffect } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useCourses, useCreateCourse, useStudents, useBulkEnroll, useBatches } from "@/hooks/queries"; import { lmsService, taxonomyService, resourcesService } from "@/services"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Search, Plus, Trash2, UserPlus, FileEdit, Pencil, BookOpen, Target, Loader2, Users, GraduationCap } from "lucide-react"; import { Link } from "react-router-dom"; import AiCreationAssistant from "@/components/ai/AiCreationAssistant"; import AiTipBanner from "@/components/ai/AiTipBanner"; import { useToast } from "@/hooks/use-toast"; import type { Course, CourseCreateRequest } from "@/types"; import type { ResourceTag } from "@/types/adaptive"; const DIFFICULTY_OPTIONS = [ { value: "beginner", label: "Beginner" }, { value: "intermediate", label: "Intermediate" }, { value: "advanced", label: "Advanced" }, ]; const CEFR_OPTIONS = [ { value: "pre_a1", label: "Pre-A1" }, { value: "a1", label: "A1" }, { value: "a2", label: "A2" }, { value: "b1", label: "B1" }, { value: "b2", label: "B2" }, { value: "c1", label: "C1" }, { value: "c2", label: "C2" }, ]; interface CourseFormData { title: string; code: string; description: string; max_capacity: number; encoach_subject_id: number | null; topic_ids: number[]; learning_objective_ids: number[]; tag_ids: number[]; difficulty_level: string; cefr_level: string; } const emptyForm: CourseFormData = { title: "", code: "", description: "", max_capacity: 30, encoach_subject_id: null, topic_ids: [], learning_objective_ids: [], tag_ids: [], difficulty_level: "", cefr_level: "", }; function CourseFormDialog({ open, onOpenChange, initialData, courseId, }: { open: boolean; onOpenChange: (open: boolean) => void; initialData?: CourseFormData; courseId?: number; }) { const { toast } = useToast(); const qc = useQueryClient(); const createMut = useCreateCourse(); const [form, setForm] = useState(initialData ?? emptyForm); const [saving, setSaving] = useState(false); useEffect(() => { if (open) { setForm(initialData ?? emptyForm); } }, [open, initialData]); const { data: subjects = [] } = useQuery({ queryKey: ["taxonomy", "subjects"], queryFn: () => taxonomyService.listSubjects(), }); const { data: topicsData } = useQuery({ queryKey: ["taxonomy", "topics", form.encoach_subject_id], queryFn: () => form.encoach_subject_id ? taxonomyService.listTopics({ subject_id: form.encoach_subject_id }) : Promise.resolve([]), enabled: !!form.encoach_subject_id, }); const topics = topicsData ?? []; const { data: objectivesData } = useQuery({ queryKey: ["lms", "objectives", form.topic_ids.join(",")], queryFn: () => form.topic_ids.length > 0 ? lmsService.listLearningObjectives({ topic_ids: form.topic_ids.join(",") }) : Promise.resolve({ items: [], total: 0 }), enabled: form.topic_ids.length > 0, }); const objectives = objectivesData?.items ?? []; const { data: allTags = [] } = useQuery({ queryKey: ["resource-tags"], queryFn: () => resourcesService.listTags(), }); function handleSubjectChange(val: string) { const sid = val === "none" ? null : Number(val); setForm((f) => ({ ...f, encoach_subject_id: sid, topic_ids: [], learning_objective_ids: [], })); } function toggleTopic(id: number) { setForm((f) => { const next = f.topic_ids.includes(id) ? f.topic_ids.filter((t) => t !== id) : [...f.topic_ids, id]; const validObjectiveTopics = new Set( topics.filter((t) => next.includes(t.id)).flatMap((t) => [t.id]) ); return { ...f, topic_ids: next, learning_objective_ids: f.learning_objective_ids.filter((oid) => objectives.some( (o) => o.id === oid && validObjectiveTopics.has(o.topic_id) ) ), }; }); } function toggleObjective(id: number) { setForm((f) => ({ ...f, learning_objective_ids: f.learning_objective_ids.includes(id) ? f.learning_objective_ids.filter((o) => o !== id) : [...f.learning_objective_ids, id], })); } function toggleTag(id: number) { setForm((f) => ({ ...f, tag_ids: f.tag_ids.includes(id) ? f.tag_ids.filter((t) => t !== id) : [...f.tag_ids, id], })); } async function handleSave() { if (!form.title.trim()) return; const payload: Partial = { title: form.title.trim(), code: form.code.trim() || form.title.trim().toUpperCase().replace(/\s+/g, "-").slice(0, 16), description: form.description, max_capacity: form.max_capacity, encoach_subject_id: form.encoach_subject_id ?? undefined, topic_ids: form.topic_ids, learning_objective_ids: form.learning_objective_ids, tag_ids: form.tag_ids, difficulty_level: (form.difficulty_level as CourseCreateRequest["difficulty_level"]) || undefined, cefr_level: form.cefr_level || undefined, }; if (courseId) { setSaving(true); try { await lmsService.updateCourse(courseId, payload); qc.invalidateQueries({ queryKey: ["lms", "courses"] }); toast({ title: "Course updated" }); onOpenChange(false); } catch (e: unknown) { toast({ title: "Error", description: String(e instanceof Error ? e.message : e), variant: "destructive" }); } setSaving(false); } else { createMut.mutate(payload as CourseCreateRequest, { onSuccess: () => { onOpenChange(false); toast({ title: "Course created" }); }, onError: (e) => toast({ title: "Error", description: String(e.message || e), variant: "destructive" }), }); } } const isPending = saving || createMut.isPending; return ( {courseId ? "Edit Course" : "Create New Course"}
setForm((f) => ({ ...f, title: e.target.value }))} placeholder="e.g. IELTS Academic Preparation" />
setForm((f) => ({ ...f, code: e.target.value }))} placeholder="Auto-generated if empty" />