From 7308d9bbcdbdd675f688da6fbc4e1511359ec844 Mon Sep 17 00:00:00 2001 From: elnatansamuel25 Date: Mon, 27 Apr 2026 09:52:30 +0300 Subject: [PATCH] ui --- src/app/AppRoutes.tsx | 10 + src/components/ui/stepper.tsx | 2 +- .../content-management/CourseDetailPage.tsx | 14 +- .../CreateQuestionTypeFlow.tsx | 87 ++ .../ProgramTypeSelectionPage.tsx | 8 +- .../QuestionTypeLibraryPage.tsx | 127 ++ .../components/QuestionTypeCard.tsx | 83 ++ .../QuestionTypeBasicInfoStep.tsx | 150 +++ .../QuestionTypeConfigStep.tsx | 1190 +++++++++++++++++ 9 files changed, 1654 insertions(+), 17 deletions(-) create mode 100644 src/pages/content-management/CreateQuestionTypeFlow.tsx create mode 100644 src/pages/content-management/QuestionTypeLibraryPage.tsx create mode 100644 src/pages/content-management/components/QuestionTypeCard.tsx create mode 100644 src/pages/content-management/components/question-type-steps/QuestionTypeBasicInfoStep.tsx create mode 100644 src/pages/content-management/components/question-type-steps/QuestionTypeConfigStep.tsx diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index b93e3ba..cd4056d 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -29,6 +29,8 @@ import { ProgramTypeSelectionPage } from "../pages/content-management/ProgramTyp import { ProgramDetailPage } from "../pages/content-management/ProgramDetailPage"; import { CourseManagementPage } from "../pages/content-management/CourseManagementPage"; import { UnitManagementPage } from "../pages/content-management/UnitManagementPage"; +import { QuestionTypeLibraryPage } from "../pages/content-management/QuestionTypeLibraryPage"; +import { CreateQuestionTypeFlow } from "../pages/content-management/CreateQuestionTypeFlow"; import { NotFoundPage } from "../pages/NotFoundPage"; import { NotificationsPage } from "../pages/notifications/NotificationsPage"; import { CreateNotificationPage } from "../pages/notifications/CreateNotificationPage"; @@ -165,6 +167,14 @@ export function AppRoutes() { path="/new-content/courses" element={} /> + } + /> + } + /> } diff --git a/src/components/ui/stepper.tsx b/src/components/ui/stepper.tsx index 0fe69de..7fcaeba 100644 --- a/src/components/ui/stepper.tsx +++ b/src/components/ui/stepper.tsx @@ -22,7 +22,7 @@ export function Stepper({ steps, currentStep, className }: StepperProps) { {index < steps.length - 1 && (
)} diff --git a/src/pages/content-management/CourseDetailPage.tsx b/src/pages/content-management/CourseDetailPage.tsx index 1493eae..9d4578b 100644 --- a/src/pages/content-management/CourseDetailPage.tsx +++ b/src/pages/content-management/CourseDetailPage.tsx @@ -86,19 +86,7 @@ export function CourseDetailPage() {
-
- - + + +
{/* Gradient Divider */} diff --git a/src/pages/content-management/QuestionTypeLibraryPage.tsx b/src/pages/content-management/QuestionTypeLibraryPage.tsx new file mode 100644 index 0000000..75002f9 --- /dev/null +++ b/src/pages/content-management/QuestionTypeLibraryPage.tsx @@ -0,0 +1,127 @@ +import { useState } from "react"; +import { Link } from "react-router-dom"; +import { ArrowLeft, Plus, Search } from "lucide-react"; +import { Button } from "../../components/ui/button"; +import { Input } from "../../components/ui/input"; +import { Select } from "../../components/ui/select"; +import { Card } from "../../components/ui/card"; +import { cn } from "../../lib/utils"; +import { QuestionTypeCard } from "./components/QuestionTypeCard"; + +export function QuestionTypeLibraryPage() { + const [activeTab, setActiveTab] = useState("All"); + + const questionTypes = [ + { + title: "Describe a Photo", + exam: "DUOLINGO" as const, + skill: "Speaking" as const, + variations: 12, + status: "Published" as const, + }, + { + title: "Write About the Topic", + exam: "DUOLINGO" as const, + skill: "Writing" as const, + variations: 12, + status: "Published" as const, + }, + { + title: "Fill in the Blanks", + exam: "IELTS" as const, + skill: "Writing" as const, + variations: 12, + status: "Published" as const, + }, + { + title: "Describe a Photo", + exam: "DUOLINGO" as const, + skill: "Speaking" as const, + variations: 12, + status: "Published" as const, + }, + ]; + + return ( +
+ {/* Navigation & Header */} +
+ + + Back to Courses + + +
+
+

+ Question Type Library +

+

+ Create and manage reusable question structures for practices and + assessments. +

+
+ + + +
+
+ + {/* Control Bar */} + +
+
+ + +
+ + +
+ +
+ + STATUS: + + {["All", "Published", "Drafts", "Archived"].map((tab) => ( + + ))} +
+
+ + {/* Grid of Cards */} +
+ {questionTypes.map((qt, index) => ( + + ))} +
+
+ ); +} diff --git a/src/pages/content-management/components/QuestionTypeCard.tsx b/src/pages/content-management/components/QuestionTypeCard.tsx new file mode 100644 index 0000000..c58ac2e --- /dev/null +++ b/src/pages/content-management/components/QuestionTypeCard.tsx @@ -0,0 +1,83 @@ +import { Edit2, Trash2, Mic2, Keyboard, Layers, MicIcon } from "lucide-react"; +import { Badge } from "../../../components/ui/badge"; +import { Card } from "../../../components/ui/card"; +import { cn } from "../../../lib/utils"; + +interface QuestionTypeCardProps { + title: string; + exam: "DUOLINGO" | "IELTS" | "TOEFL"; + skill: "Speaking" | "Writing" | "Listening" | "Reading"; + variations: number; + status: "Published" | "Draft" | "Archived"; +} + +export function QuestionTypeCard({ + title, + exam, + skill, + variations, + status, +}: QuestionTypeCardProps) { + const SkillIcon = skill === "Speaking" ? MicIcon : Keyboard; + + const examColors = { + DUOLINGO: "bg-[#22C55EE5] text-[#fff] border-transparent", + IELTS: "bg-[#EF4444E5] text-[#fff] border-transparent", + TOEFL: "bg-[#DBEAFE] text-[#fff] border-transparent", + }; + + const statusColors = { + Published: "bg-[#F0FDF4] text-[#16A34A]", + Draft: "bg-grayScale-50 text-grayScale-500", + Archived: "bg-red-50 text-red-500", + }; + + return ( + +
+

+ {title} +

+ +
+ + {exam} + +
+ + {skill} +
+
+ +
+ + {variations} Variations +
+ +
+ + {status} + +
+ + +
+
+
+
+ ); +} diff --git a/src/pages/content-management/components/question-type-steps/QuestionTypeBasicInfoStep.tsx b/src/pages/content-management/components/question-type-steps/QuestionTypeBasicInfoStep.tsx new file mode 100644 index 0000000..ad75911 --- /dev/null +++ b/src/pages/content-management/components/question-type-steps/QuestionTypeBasicInfoStep.tsx @@ -0,0 +1,150 @@ +import { useState } from "react"; +import { X, ArrowRight } from "lucide-react"; +import { Button } from "../../../../components/ui/button"; +import { Card } from "../../../../components/ui/card"; +import { Select } from "../../../../components/ui/select"; +import { Badge } from "../../../../components/ui/badge"; + +interface QuestionTypeBasicInfoStepProps { + onNext: () => void; +} + +export function QuestionTypeBasicInfoStep({ + onNext, +}: QuestionTypeBasicInfoStepProps) { + const [selectedChips, setSelectedChips] = useState([ + "Multiple Choice", + "Sentence Completion", + ]); + const suggestions = ["Matching Headings", "True/False/NG"]; + + const removeChip = (chip: string) => { + setSelectedChips(selectedChips.filter((c) => c !== chip)); + }; + + const addChip = (chip: string) => { + if (!selectedChips.includes(chip)) { + setSelectedChips([...selectedChips, chip]); + } + }; + + return ( +
+ +
+

+ STEP 1: Basic Info +

+

+ Define what this question type is and where it applies. +

+
+ +
+ {/* Top Row: Course Type & Skill Category */} +
+
+ + +

+ The core framework for the practice test. +

+
+ +
+ + +
+
+ + {/* Question Type */} +
+ + +
+ + {/* Question Types Chip Input */} +
+ +
+ {selectedChips.map((chip) => ( + + {chip} + + + ))} + +
+ +
+ + Suggestions: + +
+ {suggestions.map((s) => ( + + ))} +
+
+
+
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/src/pages/content-management/components/question-type-steps/QuestionTypeConfigStep.tsx b/src/pages/content-management/components/question-type-steps/QuestionTypeConfigStep.tsx new file mode 100644 index 0000000..110a7ab --- /dev/null +++ b/src/pages/content-management/components/question-type-steps/QuestionTypeConfigStep.tsx @@ -0,0 +1,1190 @@ +import { useState } from "react"; +import { + ChevronUp, + ChevronDown, + Plus, + ArrowRight, + Clock, + Info, + Volume2, + FileText, + Image as ImageIcon, + Link2, + CheckSquare, + Table as TableIcon, + GitBranch, + Type, + ListTodo, + FileUp, + GitCompare, + MousePointer2, + Mic2, + Hourglass, + Check, + GripVertical, + MinusCircle, + UploadCloud, + ArrowDown, + Trash2, + X, + Sliders, + Settings2, +} from "lucide-react"; +import { Button } from "../../../../components/ui/button"; +import { Input } from "../../../../components/ui/input"; +import { cn } from "../../../../lib/utils"; + +interface ConfigCardProps { + icon: any; + label: string; + selected: boolean; + onClick: () => void; +} + +function ConfigCard({ icon: Icon, label, selected, onClick }: ConfigCardProps) { + return ( + + ); +} + +interface QuestionTypeConfigStepProps { + onNext: () => void; + onBack: () => void; +} + +export function QuestionTypeConfigStep({ + onNext, + onBack, +}: QuestionTypeConfigStepProps) { + const [expandedSection, setExpandedSection] = useState( + "Speak About the Photo", + ); + const [selectedInputs, setSelectedInputs] = useState([]); + const [selectedAnswers, setSelectedAnswers] = useState([]); + const [matchingKeys, setMatchingKeys] = useState([ + "Verbose", + "Succinct", + "Ambiguous", + ]); + const [matchingValues, setMatchingValues] = useState([ + "Using more words than needed", + "Briefly and clearly expressed", + "Open to more than one interpretation", + ]); + const [shortQuestions, setShortQuestions] = useState([ + "Verbose", + "Succinct", + "Ambiguous", + ]); + const [shortAnswers, setShortAnswers] = useState([ + "Using more words than needed", + "Briefly and clearly expressed", + "Open to more than one interpretation", + ]); + const [answerKeyItems, setAnswerKeyItems] = useState(["C", "B", "A"]); + const [selectedLabelSet, setSelectedLabelSet] = useState( + "True / False / Not Given", + ); + const [correctLabelAnswer, setCorrectLabelAnswer] = useState("True"); + const [mcOptions, setMcOptions] = useState([ + { label: "A", text: "The process of photosynthesis", isCorrect: false }, + { label: "B", text: "Cellular respiration in plants", isCorrect: true }, + { label: "C", text: "Enter option text...", isCorrect: false }, + ]); + const [selectionMode, setSelectionMode] = useState<"Single" | "Multiple">( + "Single", + ); + const [missingWordTags, setMissingWordTags] = useState(["Jump", "Lazy"]); + const [distractors, setDistractors] = useState< + Record + >({ + Jump: [ + { id: 1, text: "sleepy" }, + { id: 2, text: "runs" }, + ], + Lazy: [ + { id: 3, text: "sleepy" }, + { id: 4, text: "runs" }, + ], + }); + const [tableRows, setTableRows] = useState([ + { + id: "01", + content: "Basic introduction and greetings", + type: "Read Only", + }, + { + id: "02", + content: "Identifying main ideas in simple text", + type: "Learner Input", + }, + { id: "03", content: "Understanding specific details", type: "Read Only" }, + { id: "04", content: "Inference and deduction skills", type: "Read Only" }, + { id: "05", content: "Vocabulary usage in context", type: "Learner Input" }, + ]); + const [flowchartSteps, setFlowchartSteps] = useState([ + { id: 1, content: "Enter text for step 1...", isBlank: false }, + { id: 2, content: "Process Data", isBlank: true }, + { id: 3, content: "Enter text for step 3...", isBlank: false }, + { id: 4, content: "Enter text for step 4...", isBlank: false }, + { id: 5, content: "Enter text for step 5...", isBlank: false }, + ]); + + const inputTypes = [ + { label: "Prep Time", icon: Clock }, + { label: "Instruction", icon: Info }, + { label: "Audio Clip", icon: Volume2 }, + { label: "Text Passage", icon: FileText }, + { label: "Image", icon: ImageIcon }, + { label: "Matching Inputs", icon: Link2 }, + { label: "Select Missing Words", icon: ListTodo }, + { label: "Table", icon: TableIcon }, + { label: "Flow Chart", icon: GitBranch }, + ]; + + const answerTypes = [ + { label: "Audio Clip", icon: Mic2 }, + { label: "Text Input", icon: Type }, + { label: "Short Answer", icon: ListTodo }, + { label: "Multiple Choice", icon: CheckSquare }, + { label: "Answer Timer", icon: Clock }, + { label: "Select Missing Words", icon: ListTodo }, + { label: "PDF Upload", icon: FileUp }, + { label: "Matching Answer", icon: GitCompare }, + { label: "Label Selection", icon: MousePointer2 }, + ]; + + const toggleSelection = (list: string[], setList: any, label: string) => { + if (list.includes(label)) { + setList(list.filter((item) => item !== label)); + } else { + setList([...list, label]); + } + }; + + return ( +
+ {/* Variation Accordion */} +
+
+
+
+ +
+

+ Speak About the Photo +

+
+ {expandedSection === "Speak About the Photo" ? ( + + ) : ( + + )} +
+ +
+ + +
+ +
+ {/* SECTION A */} +
+
+

+ SECTION A: Question Input Types +

+

+ Choose how the question is presented to the learner. +

+
+ +
+ {inputTypes.map((type) => ( + + toggleSelection( + selectedInputs, + setSelectedInputs, + type.label, + ) + } + /> + ))} +
+ + {/* Dynamic Inputs for Section A */} + {selectedInputs.length > 0 && ( +
+ {selectedInputs.map((input) => ( +
+ {input === "Prep Time" ? ( +
+ +
+ + + SEC + +
+
+ ) : input === "Instruction" ? ( +
+ + +
+ ) : input === "Image" ? ( +
+
+ +

+ Upload your image. 1280×720px recommended. +

+
+
+ + + Click to upload + +
+
+ ) : input === "Text Passage" ? ( +
+
+
+ +
+

+ Text Input +

+
+
+
+