Files
encoach_frontend_new_v2/src/pages/teacher/CourseBuilder.tsx
Yamen Ahmad 11a7265460 feat(v3): restructure project + add complete frontend
- Restructure: move backend from new_project/ to backend/
- Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks)
- Add docs/ with SRS specs, user stories, and workflow documentation
- Update .gitignore for new directory layout

Workflows implemented:
  WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration,
  WF4 General English Exam, WF5 Course Generation,
  WF6 Entity Student Onboarding, AI Course Generation,
  Adaptive Learning Engine UI, White-Label Branding, Score Release

Made-with: Cursor
2026-04-10 17:26:42 +04:00

120 lines
5.7 KiB
TypeScript

import { useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useNavigate } from "react-router-dom";
import { useToast } from "@/hooks/use-toast";
import { useCreateCourse } from "@/hooks/queries";
const steps = ["Basic Info", "Schedule", "Review"];
export default function CourseBuilder() {
const [step, setStep] = useState(0);
const navigate = useNavigate();
const { toast } = useToast();
const createMut = useCreateCourse();
const [form, setForm] = useState({
title: "", code: "", description: "",
level: "Beginner" as "Beginner" | "Intermediate" | "Advanced",
start_date: "", end_date: "", max_capacity: "",
});
function handlePublish() {
createMut.mutate(
{
title: form.title,
code: form.code,
description: form.description,
level: form.level,
instructor_id: 0,
max_capacity: Number(form.max_capacity) || 30,
start_date: form.start_date,
end_date: form.end_date,
},
{
onSuccess: () => {
toast({ title: "Course Created", description: "Your new course has been saved." });
navigate("/admin/courses");
},
onError: (err: Error) => toast({ title: "Error", description: err.message, variant: "destructive" }),
},
);
}
return (
<div className="space-y-6 max-w-3xl">
<div>
<h1 className="text-2xl font-bold">Course Builder</h1>
<p className="text-muted-foreground">Create a new course in {steps.length} steps.</p>
</div>
<div className="flex gap-2 mb-6">
{steps.map((s, i) => (
<div key={s} className={`flex-1 h-2 rounded-full ${i <= step ? "bg-primary" : "bg-muted"}`} />
))}
</div>
<p className="text-sm font-medium mb-4">Step {step + 1}: {steps[step]}</p>
<Card>
<CardContent className="pt-6 space-y-4">
{step === 0 && (
<>
<div className="space-y-2"><Label>Course Title</Label><Input value={form.title} onChange={(e) => setForm(f => ({ ...f, title: e.target.value }))} placeholder="e.g. IELTS Academic Writing" /></div>
<div className="space-y-2"><Label>Course Code</Label><Input value={form.code} onChange={(e) => setForm(f => ({ ...f, code: e.target.value }))} placeholder="e.g. IELTS-W101" /></div>
<div className="space-y-2"><Label>Description</Label><Textarea value={form.description} onChange={(e) => setForm(f => ({ ...f, description: e.target.value }))} placeholder="Describe the course..." rows={4} /></div>
<div className="space-y-2">
<Label>Level</Label>
<Select value={form.level} onValueChange={(v) => setForm(f => ({ ...f, level: v as typeof form.level }))}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="Beginner">Beginner</SelectItem>
<SelectItem value="Intermediate">Intermediate</SelectItem>
<SelectItem value="Advanced">Advanced</SelectItem>
</SelectContent>
</Select>
</div>
</>
)}
{step === 1 && (
<>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2"><Label>Start Date</Label><Input type="date" value={form.start_date} onChange={(e) => setForm(f => ({ ...f, start_date: e.target.value }))} /></div>
<div className="space-y-2"><Label>End Date</Label><Input type="date" value={form.end_date} onChange={(e) => setForm(f => ({ ...f, end_date: e.target.value }))} /></div>
</div>
<div className="space-y-2"><Label>Max Capacity</Label><Input type="number" value={form.max_capacity} onChange={(e) => setForm(f => ({ ...f, max_capacity: e.target.value }))} placeholder="30" /></div>
</>
)}
{step === 2 && (
<div className="space-y-3 py-4">
<p className="font-medium text-lg text-center">Review & Publish</p>
<div className="grid grid-cols-2 gap-2 text-sm">
<p className="text-muted-foreground">Title:</p><p className="font-medium">{form.title || "—"}</p>
<p className="text-muted-foreground">Code:</p><p className="font-medium">{form.code || "—"}</p>
<p className="text-muted-foreground">Level:</p><p className="font-medium">{form.level}</p>
<p className="text-muted-foreground">Capacity:</p><p className="font-medium">{form.max_capacity || "30"}</p>
<p className="text-muted-foreground">Dates:</p><p className="font-medium">{form.start_date || "—"} {form.end_date || "—"}</p>
</div>
{form.description && <p className="text-sm mt-2 text-muted-foreground">{form.description}</p>}
</div>
)}
</CardContent>
</Card>
<div className="flex justify-between">
<Button variant="outline" disabled={step === 0} onClick={() => setStep(s => s - 1)}>Previous</Button>
{step < steps.length - 1 ? (
<Button onClick={() => setStep(s => s + 1)} disabled={step === 0 && !form.title}>Next</Button>
) : (
<Button onClick={handlePublish} disabled={createMut.isPending || !form.title}>
{createMut.isPending ? "Publishing..." : "Publish Course"}
</Button>
)}
</div>
</div>
);
}