import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { X } from "lucide-react"; import { StepWizard, type WizardStepDef } from "@/components/wizard/StepWizard"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { coursePlanService } from "@/services/coursePlan.service"; import { describeApiError } from "@/lib/api-client"; import type { CoursePlanGenerateBrief } from "@/types"; /** * AI course-plan generation wizard. * * Four steps: * 1. Basics — title, CEFR level, total weeks, contact hours. * 2. Coverage — skills division (e.g. "10 hrs Reading+Writing / * 8 hrs Listening+Speaking"), learner profile. * 3. Scope — grammar focus chips, resource citations chips, * optional free-form notes. * 4. Review — finish → POST /api/ai/course-plan which triggers * the OpenAI call, persists the plan, and returns * the finished record; we navigate to the detail * page so the user can start generating Week 1 * materials immediately. */ interface CoursePlanWizardState { title: string; cefr_level: string; total_weeks: number; contact_hours_per_week: number; skills_division: string; learner_profile: string; grammar_focus: string[]; resources: string[]; notes: string; } 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" }, ]; export default function CoursePlanWizard() { const { t } = useTranslation(); const navigate = useNavigate(); const qc = useQueryClient(); const mutation = useMutation({ mutationFn: (brief: CoursePlanGenerateBrief) => coursePlanService.generate(brief), onSuccess: (resp) => { qc.invalidateQueries({ queryKey: ["course-plans"] }); toast.success(t("coursePlan.generateSuccess")); const id = resp?.data?.id; if (id) navigate(`/admin/course-plans/${id}`); else navigate("/admin/course-plans"); }, onError: (err) => { toast.error(describeApiError(err, t("coursePlan.generateFailed"))); }, }); const steps: WizardStepDef[] = [ { id: "basics", titleKey: "coursePlan.wizard.steps.basics", descriptionKey: "coursePlan.wizard.steps.basicsDesc", validate: (s) => { if (!s.title.trim()) return t("coursePlan.wizard.errors.titleRequired"); if (!s.cefr_level) return t("coursePlan.wizard.errors.cefrRequired"); if (!s.total_weeks || s.total_weeks < 1) return t("coursePlan.wizard.errors.weeksRange"); return null; }, render: ({ state, update }) => (
update({ title: e.target.value })} />
update({ total_weeks: Number(e.target.value) || 0 })} />
update({ contact_hours_per_week: Number(e.target.value) || 0 }) } />
), }, { id: "coverage", titleKey: "coursePlan.wizard.steps.coverage", descriptionKey: "coursePlan.wizard.steps.coverageDesc", render: ({ state, update }) => (
update({ skills_division: e.target.value })} />

{t("coursePlan.wizard.fields.skillsDivisionHint")}