@thanh01.pmt/interactive-quiz-kit 1.0.23 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring.cjs +1765 -0
- package/dist/authoring.js +1751 -3
- package/dist/index.cjs +256 -0
- package/dist/index.js +255 -1
- package/dist/react-ui.cjs +2533 -990
- package/dist/react-ui.js +2232 -705
- package/package.json +1 -1
package/dist/authoring.js
CHANGED
|
@@ -6,9 +6,9 @@ import { clsx } from 'clsx';
|
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
import { GoogleGenAI } from '@google/genai';
|
|
8
8
|
import * as React53 from 'react';
|
|
9
|
-
import React53__default, { useRef, useState, useImperativeHandle, useCallback, useEffect, forwardRef } from 'react';
|
|
9
|
+
import React53__default, { useRef, useState, useImperativeHandle, useCallback, useEffect, forwardRef, useTransition, Suspense } from 'react';
|
|
10
10
|
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
11
|
-
import { Circle, Check, ChevronDown, ChevronUp, X, Loader2, Play, CheckCircle, XCircle, RotateCcw, Save, Wand2, AlertTriangle, FileText, DownloadCloud, Settings, Upload, ListOrdered, ArrowUp, ArrowDown, Eye, Edit,
|
|
11
|
+
import { Circle, Check, ChevronDown, ChevronUp, X, Loader2, Play, CheckCircle, XCircle, RotateCcw, Save, KeyRound, Trash2, Wand2, AlertTriangle, FileText, DownloadCloud, Settings, Upload, ListOrdered, ArrowUp, ArrowDown, Eye, Edit, LogOut, Edit3, Search, BookCopy, PlusCircle, Award, Tag, Layers, Brain, HelpCircle, Lightbulb, ScanText, Settings2, FileJson, Table, ArrowLeft } from 'lucide-react';
|
|
12
12
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
13
13
|
import { cva } from 'class-variance-authority';
|
|
14
14
|
import ReactMarkdown from 'react-markdown';
|
|
@@ -28,6 +28,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
28
28
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
29
29
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
30
30
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
|
31
|
+
import { format } from 'date-fns';
|
|
32
|
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
31
33
|
import * as ToastPrimitives from '@radix-ui/react-toast';
|
|
32
34
|
|
|
33
35
|
var __defProp = Object.defineProperty;
|
|
@@ -2659,6 +2661,260 @@ var KnowledgeCardService = class {
|
|
|
2659
2661
|
}
|
|
2660
2662
|
};
|
|
2661
2663
|
|
|
2664
|
+
// src/services/metadataService.ts
|
|
2665
|
+
var LocalStorageManager = class {
|
|
2666
|
+
constructor(key) {
|
|
2667
|
+
this.key = `iqk_metadata_${key}`;
|
|
2668
|
+
}
|
|
2669
|
+
getAll() {
|
|
2670
|
+
if (typeof window === "undefined") return [];
|
|
2671
|
+
try {
|
|
2672
|
+
const stored = localStorage.getItem(this.key);
|
|
2673
|
+
return stored ? JSON.parse(stored) : [];
|
|
2674
|
+
} catch (e) {
|
|
2675
|
+
console.error(`Error reading from localStorage key ${this.key}:`, e);
|
|
2676
|
+
return [];
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
saveAll(items) {
|
|
2680
|
+
if (typeof window === "undefined") return;
|
|
2681
|
+
try {
|
|
2682
|
+
localStorage.setItem(this.key, JSON.stringify(items));
|
|
2683
|
+
} catch (e) {
|
|
2684
|
+
console.error(`Error writing to localStorage key ${this.key}:`, e);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
add(item) {
|
|
2688
|
+
const items = this.getAll();
|
|
2689
|
+
if (items.some((i) => i.code === item.code)) {
|
|
2690
|
+
throw new Error(`An item with code "${item.code}" already exists for ${this.key}.`);
|
|
2691
|
+
}
|
|
2692
|
+
const newItem = __spreadProps(__spreadValues({}, item), { id: generateUniqueId(`${this.key}_`) });
|
|
2693
|
+
this.saveAll([...items, newItem]);
|
|
2694
|
+
return newItem;
|
|
2695
|
+
}
|
|
2696
|
+
// ===== FIX IS HERE =====
|
|
2697
|
+
// Changed the type of 'updates' to allow 'code' to be part of the update object.
|
|
2698
|
+
update(id, updates) {
|
|
2699
|
+
const items = this.getAll();
|
|
2700
|
+
const index = items.findIndex((i) => i.id === id);
|
|
2701
|
+
if (index === -1) {
|
|
2702
|
+
console.warn(`Item with id "${id}" not found in ${this.key} for update.`);
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
const updatedItem = __spreadValues(__spreadValues({}, items[index]), updates);
|
|
2706
|
+
items[index] = updatedItem;
|
|
2707
|
+
this.saveAll(items);
|
|
2708
|
+
return updatedItem;
|
|
2709
|
+
}
|
|
2710
|
+
// =======================
|
|
2711
|
+
delete(code) {
|
|
2712
|
+
const items = this.getAll();
|
|
2713
|
+
const newItems = items.filter((i) => i.code !== code);
|
|
2714
|
+
if (items.length === newItems.length) {
|
|
2715
|
+
return false;
|
|
2716
|
+
}
|
|
2717
|
+
this.saveAll(newItems);
|
|
2718
|
+
return true;
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2721
|
+
var subjectManager = new LocalStorageManager("subjects");
|
|
2722
|
+
var gradeLevelManager = new LocalStorageManager("grade_levels");
|
|
2723
|
+
var topicManager = new LocalStorageManager("topics");
|
|
2724
|
+
var categoryManager = new LocalStorageManager("categories");
|
|
2725
|
+
var bloomLevelManager = new LocalStorageManager("bloom_levels");
|
|
2726
|
+
var questionTypeManager = new LocalStorageManager("question_types");
|
|
2727
|
+
var learningObjectiveManager = new LocalStorageManager("learning_objectives");
|
|
2728
|
+
var contextManager = new LocalStorageManager("contexts");
|
|
2729
|
+
var approachManager = new LocalStorageManager("approaches");
|
|
2730
|
+
function mapRawDifficultyToStandard(rawDifficulty) {
|
|
2731
|
+
switch (rawDifficulty) {
|
|
2732
|
+
case "E":
|
|
2733
|
+
case "E~M":
|
|
2734
|
+
return "easy";
|
|
2735
|
+
case "M":
|
|
2736
|
+
case "M~H":
|
|
2737
|
+
return "medium";
|
|
2738
|
+
case "H":
|
|
2739
|
+
return "hard";
|
|
2740
|
+
default:
|
|
2741
|
+
return "medium";
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
var _MetadataService = class _MetadataService {
|
|
2745
|
+
};
|
|
2746
|
+
// --- Subject Services ---
|
|
2747
|
+
_MetadataService.getSubjects = () => subjectManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2748
|
+
_MetadataService.addSubject = (name, code) => {
|
|
2749
|
+
return subjectManager.add({ code, name, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2750
|
+
};
|
|
2751
|
+
_MetadataService.updateSubject = (id, name, code) => {
|
|
2752
|
+
return subjectManager.update(id, { name, code, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2753
|
+
};
|
|
2754
|
+
_MetadataService.deleteSubject = (code) => {
|
|
2755
|
+
const topics = _MetadataService.getTopics(code);
|
|
2756
|
+
if (topics.length > 0) {
|
|
2757
|
+
throw new Error("Cannot delete subject: It is referenced by topics.");
|
|
2758
|
+
}
|
|
2759
|
+
return subjectManager.delete(code);
|
|
2760
|
+
};
|
|
2761
|
+
// --- GradeLevel Services ---
|
|
2762
|
+
_MetadataService.getGradeLevels = () => gradeLevelManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2763
|
+
_MetadataService.addGradeLevel = (name, code) => gradeLevelManager.add({ name, code });
|
|
2764
|
+
_MetadataService.updateGradeLevel = (id, name, code) => gradeLevelManager.update(id, { name, code });
|
|
2765
|
+
_MetadataService.deleteGradeLevel = (code) => gradeLevelManager.delete(code);
|
|
2766
|
+
// --- Topic Services ---
|
|
2767
|
+
_MetadataService.getTopics = (subjectCode) => {
|
|
2768
|
+
const allTopics = topicManager.getAll();
|
|
2769
|
+
const filtered = subjectCode ? allTopics.filter((t) => t.subjectCode === subjectCode) : allTopics;
|
|
2770
|
+
return filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
2771
|
+
};
|
|
2772
|
+
_MetadataService.addTopic = (name, code, subjectCode) => topicManager.add({ name, code, subjectCode });
|
|
2773
|
+
_MetadataService.updateTopic = (id, name, code, subjectCode) => topicManager.update(id, { name, code, subjectCode });
|
|
2774
|
+
_MetadataService.deleteTopic = (code) => topicManager.delete(code);
|
|
2775
|
+
// --- BloomLevel Services ---
|
|
2776
|
+
_MetadataService.getBloomLevels = () => bloomLevelManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2777
|
+
_MetadataService.addBloomLevel = (name, code, description) => bloomLevelManager.add({ name, code, description });
|
|
2778
|
+
_MetadataService.updateBloomLevel = (id, name, code, description) => bloomLevelManager.update(id, { name, code, description });
|
|
2779
|
+
_MetadataService.deleteBloomLevel = (code) => bloomLevelManager.delete(code);
|
|
2780
|
+
// --- QuestionType Services ---
|
|
2781
|
+
_MetadataService.getQuestionTypes = () => questionTypeManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2782
|
+
_MetadataService.addQuestionType = (name, code, description) => questionTypeManager.add({ name, code, description });
|
|
2783
|
+
_MetadataService.updateQuestionType = (id, name, code, description) => questionTypeManager.update(id, { name, code, description });
|
|
2784
|
+
_MetadataService.deleteQuestionType = (code) => questionTypeManager.delete(code);
|
|
2785
|
+
// --- Category Services ---
|
|
2786
|
+
_MetadataService.getCategories = () => categoryManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2787
|
+
_MetadataService.addCategory = (name, code, description) => categoryManager.add({ name, code, description });
|
|
2788
|
+
_MetadataService.updateCategory = (id, name, code, description) => categoryManager.update(id, { name, code, description });
|
|
2789
|
+
_MetadataService.deleteCategory = (code) => categoryManager.delete(code);
|
|
2790
|
+
// --- Context Services ---
|
|
2791
|
+
_MetadataService.getContexts = () => contextManager.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
2792
|
+
_MetadataService.addContext = (name, code, description) => contextManager.add({ name, code, description });
|
|
2793
|
+
_MetadataService.updateContext = (id, name, code, description) => contextManager.update(id, { name, code, description });
|
|
2794
|
+
_MetadataService.deleteContext = (code) => contextManager.delete(code);
|
|
2795
|
+
// --- Approach Services ---
|
|
2796
|
+
_MetadataService.getApproaches = () => approachManager.getAll().sort((a, b) => a.code.localeCompare(b.code));
|
|
2797
|
+
_MetadataService.addApproach = (approachData) => {
|
|
2798
|
+
const difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
2799
|
+
return approachManager.add(__spreadProps(__spreadValues({}, approachData), { difficulty }));
|
|
2800
|
+
};
|
|
2801
|
+
_MetadataService.updateApproach = (id, approachData) => {
|
|
2802
|
+
const updates = __spreadValues({}, approachData);
|
|
2803
|
+
if (approachData.rawDifficulty) {
|
|
2804
|
+
updates.difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
2805
|
+
}
|
|
2806
|
+
return approachManager.update(id, updates);
|
|
2807
|
+
};
|
|
2808
|
+
_MetadataService.deleteApproach = (code) => approachManager.delete(code);
|
|
2809
|
+
// --- LearningObjective Services ---
|
|
2810
|
+
_MetadataService.getLearningObjectives = (subjectCode) => {
|
|
2811
|
+
const allLOs = learningObjectiveManager.getAll();
|
|
2812
|
+
const filtered = subjectCode ? allLOs.filter((lo) => lo.subjectCode === subjectCode) : allLOs;
|
|
2813
|
+
return filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
2814
|
+
};
|
|
2815
|
+
_MetadataService.addLearningObjective = (name, code, subjectCode, description) => learningObjectiveManager.add({ name, code, subjectCode, description });
|
|
2816
|
+
_MetadataService.updateLearningObjective = (id, name, code, subjectCode, description) => learningObjectiveManager.update(id, { name, code, subjectCode, description });
|
|
2817
|
+
_MetadataService.deleteLearningObjective = (code) => learningObjectiveManager.delete(code);
|
|
2818
|
+
var MetadataService = _MetadataService;
|
|
2819
|
+
|
|
2820
|
+
// src/services/questionBankService.ts
|
|
2821
|
+
var LocalStorageManager2 = class {
|
|
2822
|
+
constructor(key) {
|
|
2823
|
+
this.key = key;
|
|
2824
|
+
}
|
|
2825
|
+
getAll() {
|
|
2826
|
+
if (typeof window === "undefined") return [];
|
|
2827
|
+
try {
|
|
2828
|
+
const stored = localStorage.getItem(this.key);
|
|
2829
|
+
return stored ? JSON.parse(stored) : [];
|
|
2830
|
+
} catch (e) {
|
|
2831
|
+
console.error(`Error reading from localStorage key ${this.key}:`, e);
|
|
2832
|
+
return [];
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
saveAll(items) {
|
|
2836
|
+
if (typeof window === "undefined") return;
|
|
2837
|
+
try {
|
|
2838
|
+
localStorage.setItem(this.key, JSON.stringify(items));
|
|
2839
|
+
} catch (e) {
|
|
2840
|
+
console.error(`Error writing to localStorage key ${this.key}:`, e);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
var questionBankManager = new LocalStorageManager2("iqk_question_bank");
|
|
2845
|
+
var QuestionBankService = class {
|
|
2846
|
+
static getQuestions(filters) {
|
|
2847
|
+
let questions = questionBankManager.getAll();
|
|
2848
|
+
if (filters) {
|
|
2849
|
+
if (filters.subjectCode) {
|
|
2850
|
+
questions = questions.filter((q) => q.subjectCode === filters.subjectCode);
|
|
2851
|
+
}
|
|
2852
|
+
if (filters.topicCode) {
|
|
2853
|
+
questions = questions.filter((q) => q.topicCode === filters.topicCode);
|
|
2854
|
+
}
|
|
2855
|
+
if (filters.gradeLevelCode) {
|
|
2856
|
+
questions = questions.filter((q) => q.gradeLevelCode === filters.gradeLevelCode);
|
|
2857
|
+
}
|
|
2858
|
+
if (filters.bloomLevelCode) {
|
|
2859
|
+
questions = questions.filter((q) => q.bloomLevelCode === filters.bloomLevelCode);
|
|
2860
|
+
}
|
|
2861
|
+
if (filters.questionTypeCode) {
|
|
2862
|
+
questions = questions.filter((q) => q.questionTypeCode === filters.questionTypeCode);
|
|
2863
|
+
}
|
|
2864
|
+
if (filters.difficulty) {
|
|
2865
|
+
questions = questions.filter((q) => q.difficulty === filters.difficulty);
|
|
2866
|
+
}
|
|
2867
|
+
if (filters.searchTerm) {
|
|
2868
|
+
const lowercasedTerm = filters.searchTerm.toLowerCase();
|
|
2869
|
+
questions = questions.filter(
|
|
2870
|
+
(q) => q.text.toLowerCase().includes(lowercasedTerm) || q.code.toLowerCase().includes(lowercasedTerm)
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
return questions.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
|
|
2875
|
+
}
|
|
2876
|
+
static getQuestionByCode(code) {
|
|
2877
|
+
return questionBankManager.getAll().find((q) => q.code === code);
|
|
2878
|
+
}
|
|
2879
|
+
// CHANGE 2: Simplified function signature. It now takes the full object to be added.
|
|
2880
|
+
static addQuestion(questionData) {
|
|
2881
|
+
const allQuestions = questionBankManager.getAll();
|
|
2882
|
+
if (allQuestions.some((q) => q.code === questionData.code)) {
|
|
2883
|
+
throw new Error(`A question with code "${questionData.code}" already exists.`);
|
|
2884
|
+
}
|
|
2885
|
+
const newQuestion = __spreadProps(__spreadValues({}, questionData), {
|
|
2886
|
+
id: generateUniqueId("qb_"),
|
|
2887
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
2888
|
+
});
|
|
2889
|
+
questionBankManager.saveAll([...allQuestions, newQuestion]);
|
|
2890
|
+
return newQuestion;
|
|
2891
|
+
}
|
|
2892
|
+
// CHANGE 2: Simplified function signature.
|
|
2893
|
+
static updateQuestion(id, updates) {
|
|
2894
|
+
const allQuestions = questionBankManager.getAll();
|
|
2895
|
+
const index = allQuestions.findIndex((q) => q.id === id);
|
|
2896
|
+
if (index === -1) {
|
|
2897
|
+
console.warn(`Question with id "${id}" not found for update.`);
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
const updatedQuestion = __spreadProps(__spreadValues(__spreadValues({}, allQuestions[index]), updates), {
|
|
2901
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
2902
|
+
});
|
|
2903
|
+
allQuestions[index] = updatedQuestion;
|
|
2904
|
+
questionBankManager.saveAll(allQuestions);
|
|
2905
|
+
return updatedQuestion;
|
|
2906
|
+
}
|
|
2907
|
+
static deleteQuestionByCode(code) {
|
|
2908
|
+
const allQuestions = questionBankManager.getAll();
|
|
2909
|
+
const newQuestions = allQuestions.filter((q) => q.code !== code);
|
|
2910
|
+
if (allQuestions.length === newQuestions.length) {
|
|
2911
|
+
return false;
|
|
2912
|
+
}
|
|
2913
|
+
questionBankManager.saveAll(newQuestions);
|
|
2914
|
+
return true;
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
|
|
2662
2918
|
// src/services/HTMLLauncherGenerator.ts
|
|
2663
2919
|
var escapeAttribute = (unsafe) => {
|
|
2664
2920
|
if (typeof unsafe !== "string") return "";
|
|
@@ -11091,6 +11347,1498 @@ var QuizAuthoringTool = ({
|
|
|
11091
11347
|
}
|
|
11092
11348
|
));
|
|
11093
11349
|
};
|
|
11350
|
+
var Table2 = React53.forwardRef((_a, ref) => {
|
|
11351
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11352
|
+
return /* @__PURE__ */ React53.createElement("div", { className: "relative w-full overflow-auto" }, /* @__PURE__ */ React53.createElement(
|
|
11353
|
+
"table",
|
|
11354
|
+
__spreadValues({
|
|
11355
|
+
ref,
|
|
11356
|
+
className: cn("w-full caption-bottom text-sm", className)
|
|
11357
|
+
}, props)
|
|
11358
|
+
));
|
|
11359
|
+
});
|
|
11360
|
+
Table2.displayName = "Table";
|
|
11361
|
+
var TableHeader = React53.forwardRef((_a, ref) => {
|
|
11362
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11363
|
+
return /* @__PURE__ */ React53.createElement("thead", __spreadValues({ ref, className: cn("[&_tr]:border-b", className) }, props));
|
|
11364
|
+
});
|
|
11365
|
+
TableHeader.displayName = "TableHeader";
|
|
11366
|
+
var TableBody = React53.forwardRef((_a, ref) => {
|
|
11367
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11368
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11369
|
+
"tbody",
|
|
11370
|
+
__spreadValues({
|
|
11371
|
+
ref,
|
|
11372
|
+
className: cn("[&_tr:last-child]:border-0", className)
|
|
11373
|
+
}, props)
|
|
11374
|
+
);
|
|
11375
|
+
});
|
|
11376
|
+
TableBody.displayName = "TableBody";
|
|
11377
|
+
var TableFooter = React53.forwardRef((_a, ref) => {
|
|
11378
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11379
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11380
|
+
"tfoot",
|
|
11381
|
+
__spreadValues({
|
|
11382
|
+
ref,
|
|
11383
|
+
className: cn(
|
|
11384
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
11385
|
+
className
|
|
11386
|
+
)
|
|
11387
|
+
}, props)
|
|
11388
|
+
);
|
|
11389
|
+
});
|
|
11390
|
+
TableFooter.displayName = "TableFooter";
|
|
11391
|
+
var TableRow = React53.forwardRef((_a, ref) => {
|
|
11392
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11393
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11394
|
+
"tr",
|
|
11395
|
+
__spreadValues({
|
|
11396
|
+
ref,
|
|
11397
|
+
className: cn(
|
|
11398
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
11399
|
+
className
|
|
11400
|
+
)
|
|
11401
|
+
}, props)
|
|
11402
|
+
);
|
|
11403
|
+
});
|
|
11404
|
+
TableRow.displayName = "TableRow";
|
|
11405
|
+
var TableHead = React53.forwardRef((_a, ref) => {
|
|
11406
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11407
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11408
|
+
"th",
|
|
11409
|
+
__spreadValues({
|
|
11410
|
+
ref,
|
|
11411
|
+
className: cn(
|
|
11412
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
11413
|
+
className
|
|
11414
|
+
)
|
|
11415
|
+
}, props)
|
|
11416
|
+
);
|
|
11417
|
+
});
|
|
11418
|
+
TableHead.displayName = "TableHead";
|
|
11419
|
+
var TableCell = React53.forwardRef((_a, ref) => {
|
|
11420
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11421
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11422
|
+
"td",
|
|
11423
|
+
__spreadValues({
|
|
11424
|
+
ref,
|
|
11425
|
+
className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)
|
|
11426
|
+
}, props)
|
|
11427
|
+
);
|
|
11428
|
+
});
|
|
11429
|
+
TableCell.displayName = "TableCell";
|
|
11430
|
+
var TableCaption = React53.forwardRef((_a, ref) => {
|
|
11431
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11432
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11433
|
+
"caption",
|
|
11434
|
+
__spreadValues({
|
|
11435
|
+
ref,
|
|
11436
|
+
className: cn("mt-4 text-sm text-muted-foreground", className)
|
|
11437
|
+
}, props)
|
|
11438
|
+
);
|
|
11439
|
+
});
|
|
11440
|
+
TableCaption.displayName = "TableCaption";
|
|
11441
|
+
function QuestionList({
|
|
11442
|
+
questions,
|
|
11443
|
+
onEdit,
|
|
11444
|
+
onDelete,
|
|
11445
|
+
onView
|
|
11446
|
+
}) {
|
|
11447
|
+
const [metadata, setMetadata] = useState({ subjects: [], topics: [], gradeLevels: [], questionTypes: [], bloomLevels: [] });
|
|
11448
|
+
useEffect(() => {
|
|
11449
|
+
const subjectsData = MetadataService.getSubjects();
|
|
11450
|
+
const topicsData = MetadataService.getTopics();
|
|
11451
|
+
const gradeLevelsData = MetadataService.getGradeLevels();
|
|
11452
|
+
const questionTypesData = MetadataService.getQuestionTypes();
|
|
11453
|
+
const bloomLevelsData = MetadataService.getBloomLevels();
|
|
11454
|
+
setMetadata({
|
|
11455
|
+
subjects: subjectsData,
|
|
11456
|
+
topics: topicsData,
|
|
11457
|
+
gradeLevels: gradeLevelsData,
|
|
11458
|
+
questionTypes: questionTypesData,
|
|
11459
|
+
bloomLevels: bloomLevelsData
|
|
11460
|
+
});
|
|
11461
|
+
}, []);
|
|
11462
|
+
const getLookupName = (code, items) => {
|
|
11463
|
+
var _a;
|
|
11464
|
+
if (!code || !items) return "N/A";
|
|
11465
|
+
return ((_a = items.find((item) => item.code === code)) == null ? void 0 : _a.name) || code;
|
|
11466
|
+
};
|
|
11467
|
+
if (questions.length === 0) {
|
|
11468
|
+
return /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-8" }, "No questions match the current filters. Try adjusting your search or add new questions.");
|
|
11469
|
+
}
|
|
11470
|
+
return /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, { className: "w-[30%]" }, "Question Text"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Type"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Grade"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Bloom's"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Last Modified"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, questions.map((question) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: question.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium max-w-xs truncate", title: question.text }, question.text), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, question.code), /* @__PURE__ */ React53__default.createElement(TableCell, null, /* @__PURE__ */ React53__default.createElement(Badge, { variant: "secondary" }, getLookupName(question.questionTypeCode, metadata.questionTypes))), /* @__PURE__ */ React53__default.createElement(TableCell, null, getLookupName(question.subjectCode, metadata.subjects)), /* @__PURE__ */ React53__default.createElement(TableCell, null, getLookupName(question.topicCode, metadata.topics)), /* @__PURE__ */ React53__default.createElement(TableCell, null, getLookupName(question.gradeLevelCode, metadata.gradeLevels)), /* @__PURE__ */ React53__default.createElement(TableCell, null, /* @__PURE__ */ React53__default.createElement(Badge, { variant: "outline" }, getLookupName(question.bloomLevelCode, metadata.bloomLevels))), /* @__PURE__ */ React53__default.createElement(TableCell, null, format(new Date(question.lastModified), "MMM d, yyyy")), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, onView && /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onView(question), className: "mr-1" }, /* @__PURE__ */ React53__default.createElement(Eye, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onEdit(question), className: "mr-1" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onDelete(question), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" }))))))));
|
|
11471
|
+
}
|
|
11472
|
+
var ALL_ITEMS_VALUE = "_ALL_ITEMS_";
|
|
11473
|
+
function QuestionFilters({
|
|
11474
|
+
onFilterChange,
|
|
11475
|
+
initialFilters = {}
|
|
11476
|
+
}) {
|
|
11477
|
+
const [searchTerm, setSearchTerm] = useState(initialFilters.searchTerm || "");
|
|
11478
|
+
const [subjectCode, setSubjectCode] = useState(initialFilters.subjectCode || "");
|
|
11479
|
+
const [topicCode, setTopicCode] = useState(initialFilters.topicCode || "");
|
|
11480
|
+
const [gradeLevelCode, setGradeLevelCode] = useState(initialFilters.gradeLevelCode || "");
|
|
11481
|
+
const [bloomLevelCode, setBloomLevelCode] = useState(initialFilters.bloomLevelCode || "");
|
|
11482
|
+
const [questionTypeCode, setQuestionTypeCode] = useState(initialFilters.questionTypeCode || "");
|
|
11483
|
+
const [difficulty, setDifficulty] = useState(initialFilters.difficulty || "");
|
|
11484
|
+
const [subjects, setSubjects] = useState([]);
|
|
11485
|
+
const [allTopics, setAllTopics] = useState([]);
|
|
11486
|
+
const [gradeLevels, setGradeLevels] = useState([]);
|
|
11487
|
+
const [questionTypes, setQuestionTypes] = useState([]);
|
|
11488
|
+
const [bloomLevels, setBloomLevels] = useState([]);
|
|
11489
|
+
const [filteredTopics, setFilteredTopics] = useState([]);
|
|
11490
|
+
useEffect(() => {
|
|
11491
|
+
const subjectsData = MetadataService.getSubjects();
|
|
11492
|
+
const topicsData = MetadataService.getTopics();
|
|
11493
|
+
const gradeLevelsData = MetadataService.getGradeLevels();
|
|
11494
|
+
const questionTypesData = MetadataService.getQuestionTypes();
|
|
11495
|
+
const bloomLevelsData = MetadataService.getBloomLevels();
|
|
11496
|
+
setSubjects(subjectsData);
|
|
11497
|
+
setAllTopics(topicsData);
|
|
11498
|
+
setGradeLevels(gradeLevelsData);
|
|
11499
|
+
setQuestionTypes(questionTypesData);
|
|
11500
|
+
setBloomLevels(bloomLevelsData);
|
|
11501
|
+
}, []);
|
|
11502
|
+
useEffect(() => {
|
|
11503
|
+
if (subjectCode) {
|
|
11504
|
+
const relatedTopics = allTopics.filter((t) => t.subjectCode === subjectCode);
|
|
11505
|
+
setFilteredTopics(relatedTopics);
|
|
11506
|
+
if (topicCode && !relatedTopics.find((t) => t.code === topicCode)) {
|
|
11507
|
+
setTopicCode("");
|
|
11508
|
+
}
|
|
11509
|
+
} else {
|
|
11510
|
+
setFilteredTopics(allTopics);
|
|
11511
|
+
setTopicCode("");
|
|
11512
|
+
}
|
|
11513
|
+
}, [subjectCode, allTopics, topicCode]);
|
|
11514
|
+
const handleApplyFilters = () => {
|
|
11515
|
+
onFilterChange({
|
|
11516
|
+
searchTerm: searchTerm || void 0,
|
|
11517
|
+
subjectCode: subjectCode || void 0,
|
|
11518
|
+
topicCode: topicCode || void 0,
|
|
11519
|
+
gradeLevelCode: gradeLevelCode || void 0,
|
|
11520
|
+
bloomLevelCode: bloomLevelCode || void 0,
|
|
11521
|
+
questionTypeCode: questionTypeCode || void 0,
|
|
11522
|
+
difficulty: difficulty || void 0
|
|
11523
|
+
});
|
|
11524
|
+
};
|
|
11525
|
+
const handleClearFilters = () => {
|
|
11526
|
+
setSearchTerm("");
|
|
11527
|
+
setSubjectCode("");
|
|
11528
|
+
setTopicCode("");
|
|
11529
|
+
setGradeLevelCode("");
|
|
11530
|
+
setBloomLevelCode("");
|
|
11531
|
+
setQuestionTypeCode("");
|
|
11532
|
+
setDifficulty("");
|
|
11533
|
+
onFilterChange({});
|
|
11534
|
+
};
|
|
11535
|
+
const createSelectHandler = (setter) => {
|
|
11536
|
+
return (selectedValue) => {
|
|
11537
|
+
setter(selectedValue === ALL_ITEMS_VALUE ? "" : selectedValue);
|
|
11538
|
+
};
|
|
11539
|
+
};
|
|
11540
|
+
return /* @__PURE__ */ React53__default.createElement("div", { className: "p-4 mb-6 bg-card border rounded-lg shadow-sm" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-7 gap-4 items-end" }, /* @__PURE__ */ React53__default.createElement(
|
|
11541
|
+
Input,
|
|
11542
|
+
{
|
|
11543
|
+
placeholder: "Search questions...",
|
|
11544
|
+
value: searchTerm,
|
|
11545
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
11546
|
+
className: "lg:col-span-2 xl:col-span-1"
|
|
11547
|
+
}
|
|
11548
|
+
), /* @__PURE__ */ React53__default.createElement(Select, { value: subjectCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setSubjectCode) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Subject" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Subjects"), subjects.map((s) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: s.code, value: s.code }, s.name)))), /* @__PURE__ */ React53__default.createElement(Select, { value: topicCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setTopicCode), disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Topic" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Topics"), filteredTopics.map((t) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: t.code, value: t.code }, t.name)))), /* @__PURE__ */ React53__default.createElement(Select, { value: gradeLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setGradeLevelCode) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Grade Level" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Grade Levels"), gradeLevels.map((gl) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: gl.code, value: gl.code }, gl.name)))), /* @__PURE__ */ React53__default.createElement(Select, { value: bloomLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setBloomLevelCode) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Bloom's Level" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Levels"), bloomLevels.map((bl) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: bl.code, value: bl.code }, bl.name)))), /* @__PURE__ */ React53__default.createElement(Select, { value: questionTypeCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setQuestionTypeCode) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Question Type" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Types"), questionTypes.map((qt) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: qt.code, value: qt.code }, qt.name)))), /* @__PURE__ */ React53__default.createElement(Select, { value: difficulty || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setDifficulty) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Difficulty" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: ALL_ITEMS_VALUE }, "All Difficulties"), /* @__PURE__ */ React53__default.createElement(SelectItem, { value: "easy" }, "Easy"), /* @__PURE__ */ React53__default.createElement(SelectItem, { value: "medium" }, "Medium"), /* @__PURE__ */ React53__default.createElement(SelectItem, { value: "hard" }, "Hard"))), /* @__PURE__ */ React53__default.createElement("div", { className: "flex gap-2 col-span-full sm:col-span-1 xl:col-span-2 xl:col-start-6" }, /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleApplyFilters, className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React53__default.createElement(Search, { className: "mr-2 h-4 w-4" }), " Apply"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleClearFilters, variant: "outline", className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React53__default.createElement(XCircle, { className: "mr-2 h-4 w-4" }), " Clear"))));
|
|
11549
|
+
}
|
|
11550
|
+
function QuestionFormDialog({
|
|
11551
|
+
isOpen,
|
|
11552
|
+
onOpenChange,
|
|
11553
|
+
onSave,
|
|
11554
|
+
questionToEdit
|
|
11555
|
+
}) {
|
|
11556
|
+
const [code, setCode] = useState("");
|
|
11557
|
+
const [subjectCode, setSubjectCode] = useState("");
|
|
11558
|
+
const [topicCode, setTopicCode] = useState("");
|
|
11559
|
+
const [gradeLevelCode, setGradeLevelCode] = useState("");
|
|
11560
|
+
const [bloomLevelCode, setBloomLevelCode] = useState("");
|
|
11561
|
+
const [isQuestionEditorOpen, setIsQuestionEditorOpen] = useState(false);
|
|
11562
|
+
const [questionConfig, setQuestionConfig] = useState(null);
|
|
11563
|
+
const [subjects, setSubjects] = useState([]);
|
|
11564
|
+
const [topics, setTopics] = useState([]);
|
|
11565
|
+
const [gradeLevels, setGradeLevels] = useState([]);
|
|
11566
|
+
const [bloomLevels, setBloomLevels] = useState([]);
|
|
11567
|
+
const [filteredTopics, setFilteredTopics] = useState([]);
|
|
11568
|
+
const [isPending, startTransition] = useTransition();
|
|
11569
|
+
const { toast: toast2 } = useToast();
|
|
11570
|
+
useEffect(() => {
|
|
11571
|
+
var _a, _b, _c;
|
|
11572
|
+
const subjectsData = MetadataService.getSubjects();
|
|
11573
|
+
const topicsData = MetadataService.getTopics();
|
|
11574
|
+
const gradeLevelsData = MetadataService.getGradeLevels();
|
|
11575
|
+
const bloomLevelsData = MetadataService.getBloomLevels();
|
|
11576
|
+
setSubjects(subjectsData);
|
|
11577
|
+
setTopics(topicsData);
|
|
11578
|
+
setGradeLevels(gradeLevelsData);
|
|
11579
|
+
setBloomLevels(bloomLevelsData);
|
|
11580
|
+
if (isOpen) {
|
|
11581
|
+
if (questionToEdit) {
|
|
11582
|
+
setCode(questionToEdit.code);
|
|
11583
|
+
setSubjectCode(questionToEdit.subjectCode);
|
|
11584
|
+
setTopicCode(questionToEdit.topicCode);
|
|
11585
|
+
setGradeLevelCode(questionToEdit.gradeLevelCode);
|
|
11586
|
+
setBloomLevelCode(questionToEdit.bloomLevelCode);
|
|
11587
|
+
setQuestionConfig(questionToEdit.questionConfig);
|
|
11588
|
+
} else {
|
|
11589
|
+
const defaultSubject = ((_a = subjectsData[0]) == null ? void 0 : _a.code) || "";
|
|
11590
|
+
const defaultGrade = ((_b = gradeLevelsData[0]) == null ? void 0 : _b.code) || "";
|
|
11591
|
+
const defaultBloom = ((_c = bloomLevelsData[0]) == null ? void 0 : _c.code) || "";
|
|
11592
|
+
setCode("");
|
|
11593
|
+
setSubjectCode(defaultSubject);
|
|
11594
|
+
setGradeLevelCode(defaultGrade);
|
|
11595
|
+
setBloomLevelCode(defaultBloom);
|
|
11596
|
+
setTopicCode("");
|
|
11597
|
+
setQuestionConfig(null);
|
|
11598
|
+
}
|
|
11599
|
+
}
|
|
11600
|
+
}, [questionToEdit, isOpen]);
|
|
11601
|
+
useEffect(() => {
|
|
11602
|
+
var _a;
|
|
11603
|
+
if (subjectCode) {
|
|
11604
|
+
const relatedTopics = topics.filter((t) => t.subjectCode === subjectCode);
|
|
11605
|
+
setFilteredTopics(relatedTopics);
|
|
11606
|
+
if (topicCode && !relatedTopics.find((t) => t.code === topicCode)) {
|
|
11607
|
+
setTopicCode(((_a = relatedTopics[0]) == null ? void 0 : _a.code) || "");
|
|
11608
|
+
}
|
|
11609
|
+
} else {
|
|
11610
|
+
setFilteredTopics([]);
|
|
11611
|
+
setTopicCode("");
|
|
11612
|
+
}
|
|
11613
|
+
}, [subjectCode, topics, topicCode]);
|
|
11614
|
+
const handleOpenQuestionEditor = (type) => {
|
|
11615
|
+
if (questionToEdit && questionConfig) {
|
|
11616
|
+
setIsQuestionEditorOpen(true);
|
|
11617
|
+
} else if (type) {
|
|
11618
|
+
const newTemplate = QuizEditorService.createNewQuestionTemplate(type);
|
|
11619
|
+
setQuestionConfig(newTemplate);
|
|
11620
|
+
setIsQuestionEditorOpen(true);
|
|
11621
|
+
}
|
|
11622
|
+
};
|
|
11623
|
+
const handleQuestionConfigSave = (savedConfig) => {
|
|
11624
|
+
setQuestionConfig(savedConfig);
|
|
11625
|
+
setIsQuestionEditorOpen(false);
|
|
11626
|
+
};
|
|
11627
|
+
const handleSubmit = () => {
|
|
11628
|
+
if (!code.trim() || !subjectCode || !topicCode || !gradeLevelCode || !bloomLevelCode) {
|
|
11629
|
+
toast2({ title: "Validation Error", description: "Please fill all metadata fields (Code, Subject, Topic, etc.).", variant: "destructive" });
|
|
11630
|
+
return;
|
|
11631
|
+
}
|
|
11632
|
+
if (!questionConfig) {
|
|
11633
|
+
toast2({ title: "Validation Error", description: "Question content is missing. Please create or edit the question details.", variant: "destructive" });
|
|
11634
|
+
return;
|
|
11635
|
+
}
|
|
11636
|
+
startTransition(() => {
|
|
11637
|
+
try {
|
|
11638
|
+
const finalDataPayload = {
|
|
11639
|
+
code,
|
|
11640
|
+
text: questionConfig.prompt.replace(/<[^>]*>?/gm, "").substring(0, 200),
|
|
11641
|
+
subjectCode,
|
|
11642
|
+
topicCode,
|
|
11643
|
+
gradeLevelCode,
|
|
11644
|
+
bloomLevelCode,
|
|
11645
|
+
questionTypeCode: questionConfig.questionType,
|
|
11646
|
+
difficulty: questionConfig.difficulty,
|
|
11647
|
+
questionConfig: __spreadProps(__spreadValues({}, questionConfig), {
|
|
11648
|
+
subject: subjectCode,
|
|
11649
|
+
topic: topicCode,
|
|
11650
|
+
gradeBand: gradeLevelCode,
|
|
11651
|
+
bloomLevel: bloomLevelCode
|
|
11652
|
+
})
|
|
11653
|
+
};
|
|
11654
|
+
if (questionToEdit) {
|
|
11655
|
+
QuestionBankService.updateQuestion(questionToEdit.id, finalDataPayload);
|
|
11656
|
+
toast2({ title: "Success", description: "Question updated." });
|
|
11657
|
+
} else {
|
|
11658
|
+
QuestionBankService.addQuestion(finalDataPayload);
|
|
11659
|
+
toast2({ title: "Success", description: "Question added." });
|
|
11660
|
+
}
|
|
11661
|
+
onSave();
|
|
11662
|
+
onOpenChange(false);
|
|
11663
|
+
} catch (error) {
|
|
11664
|
+
toast2({ title: "Error", description: error.message || "Failed to save question.", variant: "destructive" });
|
|
11665
|
+
}
|
|
11666
|
+
});
|
|
11667
|
+
};
|
|
11668
|
+
const dialogTitle = questionToEdit ? "Edit Question Metadata" : "Create New Question";
|
|
11669
|
+
const dialogDescription = "First, define the metadata for the question. Then, edit the question's content and logic.";
|
|
11670
|
+
return /* @__PURE__ */ React53__default.createElement(React53__default.Fragment, null, /* @__PURE__ */ React53__default.createElement(Dialog, { open: isOpen, onOpenChange }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-xl md:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, dialogTitle), /* @__PURE__ */ React53__default.createElement(DialogDescription, null, dialogDescription)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "code" }, "Question Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "code", value: code, onChange: (e) => setCode(e.target.value.toUpperCase()), placeholder: "Unique code (e.g., MATH-ALG-001)", disabled: !!questionToEdit })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "gradeLevelCode" }, "Grade Level"), /* @__PURE__ */ React53__default.createElement(Select, { value: gradeLevelCode, onValueChange: setGradeLevelCode }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "gradeLevelCode" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select Grade Level" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, gradeLevels.map((gl) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: gl.code, value: gl.code }, gl.name)))))), /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React53__default.createElement(Select, { value: subjectCode, onValueChange: setSubjectCode }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "subjectCode" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select Subject" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, subjects.map((s) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: s.code, value: s.code }, s.name))))), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "topicCode" }, "Topic"), /* @__PURE__ */ React53__default.createElement(Select, { value: topicCode, onValueChange: setTopicCode, disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "topicCode" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select Topic" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, filteredTopics.map((t) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: t.code, value: t.code }, t.name)))))), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "bloomLevelCode" }, "Bloom's Level"), /* @__PURE__ */ React53__default.createElement(Select, { value: bloomLevelCode, onValueChange: setBloomLevelCode }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "bloomLevelCode" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select Bloom's Level" })), /* @__PURE__ */ React53__default.createElement(SelectContent, null, bloomLevels.map((bl) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: bl.code, value: bl.code }, bl.name))))), /* @__PURE__ */ React53__default.createElement("div", { className: "pt-4 border-t" }, /* @__PURE__ */ React53__default.createElement(Label, { className: "font-semibold" }, "Question Content & Logic"), questionConfig ? /* @__PURE__ */ React53__default.createElement("div", { className: "p-3 mt-2 border rounded-md bg-muted/30 flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement("p", { className: "font-medium" }, "Type: ", /* @__PURE__ */ React53__default.createElement("span", { className: "font-normal" }, questionConfig.questionType)), /* @__PURE__ */ React53__default.createElement("p", { className: "text-sm text-muted-foreground truncate max-w-md" }, "Prompt: ", questionConfig.prompt.replace(/<[^>]*>?/gm, "") || "Not set")), /* @__PURE__ */ React53__default.createElement(Button, { variant: "outline", onClick: () => handleOpenQuestionEditor() }, /* @__PURE__ */ React53__default.createElement(Edit, { className: "mr-2 h-4 w-4" }), " Edit Content")) : /* @__PURE__ */ React53__default.createElement("div", { className: "p-3 mt-2 border-dashed border-2 rounded-md text-center" }, /* @__PURE__ */ React53__default.createElement("p", { className: "text-muted-foreground mb-2" }, "No content has been created yet."), /* @__PURE__ */ React53__default.createElement(Button, { variant: "default", onClick: () => handleOpenQuestionEditor("multiple_choice") }, /* @__PURE__ */ React53__default.createElement(BookCopy, { className: "mr-2 h-4 w-4" }), " Create Question Content")))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !questionConfig }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Save Question")))), questionConfig && /* @__PURE__ */ React53__default.createElement(
|
|
11671
|
+
EditQuestionModal,
|
|
11672
|
+
{
|
|
11673
|
+
isOpen: isQuestionEditorOpen,
|
|
11674
|
+
onClose: () => setIsQuestionEditorOpen(false),
|
|
11675
|
+
questionData: questionConfig,
|
|
11676
|
+
onSave: handleQuestionConfigSave
|
|
11677
|
+
}
|
|
11678
|
+
));
|
|
11679
|
+
}
|
|
11680
|
+
var AlertDialog = AlertDialogPrimitive.Root;
|
|
11681
|
+
var AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
11682
|
+
var AlertDialogOverlay = React53.forwardRef((_a, ref) => {
|
|
11683
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11684
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11685
|
+
AlertDialogPrimitive.Overlay,
|
|
11686
|
+
__spreadProps(__spreadValues({
|
|
11687
|
+
className: cn(
|
|
11688
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
11689
|
+
className
|
|
11690
|
+
)
|
|
11691
|
+
}, props), {
|
|
11692
|
+
ref
|
|
11693
|
+
})
|
|
11694
|
+
);
|
|
11695
|
+
});
|
|
11696
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
11697
|
+
var AlertDialogContent = React53.forwardRef((_a, ref) => {
|
|
11698
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11699
|
+
return /* @__PURE__ */ React53.createElement(AlertDialogPortal, null, /* @__PURE__ */ React53.createElement(AlertDialogOverlay, null), /* @__PURE__ */ React53.createElement(
|
|
11700
|
+
AlertDialogPrimitive.Content,
|
|
11701
|
+
__spreadValues({
|
|
11702
|
+
ref,
|
|
11703
|
+
className: cn(
|
|
11704
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
11705
|
+
className
|
|
11706
|
+
)
|
|
11707
|
+
}, props)
|
|
11708
|
+
));
|
|
11709
|
+
});
|
|
11710
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
11711
|
+
var AlertDialogHeader = (_a) => {
|
|
11712
|
+
var _b = _a, {
|
|
11713
|
+
className
|
|
11714
|
+
} = _b, props = __objRest(_b, [
|
|
11715
|
+
"className"
|
|
11716
|
+
]);
|
|
11717
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11718
|
+
"div",
|
|
11719
|
+
__spreadValues({
|
|
11720
|
+
className: cn(
|
|
11721
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
11722
|
+
className
|
|
11723
|
+
)
|
|
11724
|
+
}, props)
|
|
11725
|
+
);
|
|
11726
|
+
};
|
|
11727
|
+
AlertDialogHeader.displayName = "AlertDialogHeader";
|
|
11728
|
+
var AlertDialogFooter = (_a) => {
|
|
11729
|
+
var _b = _a, {
|
|
11730
|
+
className
|
|
11731
|
+
} = _b, props = __objRest(_b, [
|
|
11732
|
+
"className"
|
|
11733
|
+
]);
|
|
11734
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11735
|
+
"div",
|
|
11736
|
+
__spreadValues({
|
|
11737
|
+
className: cn(
|
|
11738
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
11739
|
+
className
|
|
11740
|
+
)
|
|
11741
|
+
}, props)
|
|
11742
|
+
);
|
|
11743
|
+
};
|
|
11744
|
+
AlertDialogFooter.displayName = "AlertDialogFooter";
|
|
11745
|
+
var AlertDialogTitle = React53.forwardRef((_a, ref) => {
|
|
11746
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11747
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11748
|
+
AlertDialogPrimitive.Title,
|
|
11749
|
+
__spreadValues({
|
|
11750
|
+
ref,
|
|
11751
|
+
className: cn("text-lg font-semibold", className)
|
|
11752
|
+
}, props)
|
|
11753
|
+
);
|
|
11754
|
+
});
|
|
11755
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
|
11756
|
+
var AlertDialogDescription = React53.forwardRef((_a, ref) => {
|
|
11757
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11758
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11759
|
+
AlertDialogPrimitive.Description,
|
|
11760
|
+
__spreadValues({
|
|
11761
|
+
ref,
|
|
11762
|
+
className: cn("text-sm text-muted-foreground", className)
|
|
11763
|
+
}, props)
|
|
11764
|
+
);
|
|
11765
|
+
});
|
|
11766
|
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
|
|
11767
|
+
var AlertDialogAction = React53.forwardRef((_a, ref) => {
|
|
11768
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11769
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11770
|
+
AlertDialogPrimitive.Action,
|
|
11771
|
+
__spreadValues({
|
|
11772
|
+
ref,
|
|
11773
|
+
className: cn(buttonVariants(), className)
|
|
11774
|
+
}, props)
|
|
11775
|
+
);
|
|
11776
|
+
});
|
|
11777
|
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
|
11778
|
+
var AlertDialogCancel = React53.forwardRef((_a, ref) => {
|
|
11779
|
+
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
11780
|
+
return /* @__PURE__ */ React53.createElement(
|
|
11781
|
+
AlertDialogPrimitive.Cancel,
|
|
11782
|
+
__spreadValues({
|
|
11783
|
+
ref,
|
|
11784
|
+
className: cn(
|
|
11785
|
+
buttonVariants({ variant: "outline" }),
|
|
11786
|
+
"mt-2 sm:mt-0",
|
|
11787
|
+
className
|
|
11788
|
+
)
|
|
11789
|
+
}, props)
|
|
11790
|
+
);
|
|
11791
|
+
});
|
|
11792
|
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
|
11793
|
+
|
|
11794
|
+
// src/react-ui/components/elements/skeleton.tsx
|
|
11795
|
+
function Skeleton(_a) {
|
|
11796
|
+
var _b = _a, {
|
|
11797
|
+
className
|
|
11798
|
+
} = _b, props = __objRest(_b, [
|
|
11799
|
+
"className"
|
|
11800
|
+
]);
|
|
11801
|
+
return /* @__PURE__ */ React.createElement(
|
|
11802
|
+
"div",
|
|
11803
|
+
__spreadValues({
|
|
11804
|
+
className: cn("animate-pulse rounded-md bg-muted", className)
|
|
11805
|
+
}, props)
|
|
11806
|
+
);
|
|
11807
|
+
}
|
|
11808
|
+
function SubjectManager() {
|
|
11809
|
+
const [subjects, setSubjects] = useState([]);
|
|
11810
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11811
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
11812
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
11813
|
+
const [currentSubject, setCurrentSubject] = useState(null);
|
|
11814
|
+
const [subjectName, setSubjectName] = useState("");
|
|
11815
|
+
const [subjectCode, setSubjectCode] = useState("");
|
|
11816
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
11817
|
+
const [isPending, startTransition] = useTransition();
|
|
11818
|
+
const { toast: toast2 } = useToast();
|
|
11819
|
+
useEffect(() => {
|
|
11820
|
+
setIsLoading(true);
|
|
11821
|
+
try {
|
|
11822
|
+
const data = MetadataService.getSubjects();
|
|
11823
|
+
setSubjects(data);
|
|
11824
|
+
} catch (error) {
|
|
11825
|
+
toast2({ title: "Error", description: "Failed to fetch subjects from local storage.", variant: "destructive" });
|
|
11826
|
+
} finally {
|
|
11827
|
+
setIsLoading(false);
|
|
11828
|
+
}
|
|
11829
|
+
}, []);
|
|
11830
|
+
const refreshData = () => {
|
|
11831
|
+
setSubjects(MetadataService.getSubjects());
|
|
11832
|
+
};
|
|
11833
|
+
const handleAddItem = () => {
|
|
11834
|
+
setCurrentSubject(null);
|
|
11835
|
+
setSubjectName("");
|
|
11836
|
+
setSubjectCode("");
|
|
11837
|
+
setIsDialogOpen(true);
|
|
11838
|
+
};
|
|
11839
|
+
const handleEditItem = (subject) => {
|
|
11840
|
+
setCurrentSubject(subject);
|
|
11841
|
+
setSubjectName(subject.name);
|
|
11842
|
+
setSubjectCode(subject.code);
|
|
11843
|
+
setIsDialogOpen(true);
|
|
11844
|
+
};
|
|
11845
|
+
const handleDeleteItem = (subject) => {
|
|
11846
|
+
setItemToDelete(subject);
|
|
11847
|
+
setIsAlertOpen(true);
|
|
11848
|
+
};
|
|
11849
|
+
const confirmDelete = () => {
|
|
11850
|
+
if (!itemToDelete) return;
|
|
11851
|
+
startTransition(() => {
|
|
11852
|
+
try {
|
|
11853
|
+
MetadataService.deleteSubject(itemToDelete.code);
|
|
11854
|
+
toast2({ title: "Success", description: `Subject "${itemToDelete.name}" deleted.` });
|
|
11855
|
+
refreshData();
|
|
11856
|
+
} catch (error) {
|
|
11857
|
+
toast2({ title: "Error", description: error.message || "Failed to delete subject.", variant: "destructive" });
|
|
11858
|
+
} finally {
|
|
11859
|
+
setIsAlertOpen(false);
|
|
11860
|
+
setItemToDelete(null);
|
|
11861
|
+
}
|
|
11862
|
+
});
|
|
11863
|
+
};
|
|
11864
|
+
const handleSubmit = () => {
|
|
11865
|
+
if (!subjectName.trim() || !subjectCode.trim()) {
|
|
11866
|
+
toast2({ title: "Validation Error", description: "Please enter Subject Name and Subject Code.", variant: "destructive" });
|
|
11867
|
+
return;
|
|
11868
|
+
}
|
|
11869
|
+
startTransition(() => {
|
|
11870
|
+
try {
|
|
11871
|
+
if (currentSubject) {
|
|
11872
|
+
MetadataService.updateSubject(currentSubject.id, subjectName, subjectCode);
|
|
11873
|
+
toast2({ title: "Success", description: "Subject updated." });
|
|
11874
|
+
} else {
|
|
11875
|
+
MetadataService.addSubject(subjectName, subjectCode);
|
|
11876
|
+
toast2({ title: "Success", description: "Subject added." });
|
|
11877
|
+
}
|
|
11878
|
+
refreshData();
|
|
11879
|
+
setIsDialogOpen(false);
|
|
11880
|
+
} catch (error) {
|
|
11881
|
+
toast2({ title: "Error", description: error.message || "Failed to save subject.", variant: "destructive" });
|
|
11882
|
+
}
|
|
11883
|
+
});
|
|
11884
|
+
};
|
|
11885
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Subjects"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Subject"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : subjects.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No subjects found. Add one to get started!") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Created At"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Updated At"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, subjects.map((subject) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: subject.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, subject.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, subject.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, format(new Date(subject.createdAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React53__default.createElement(TableCell, null, format(new Date(subject.updatedAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(subject), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(subject), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentSubject ? "Edit Subject" : "Add New Subject"), /* @__PURE__ */ React53__default.createElement(DialogDescription, null, currentSubject ? "Update the details of the subject." : "Enter details for the new subject.")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React53__default.createElement(
|
|
11886
|
+
Input,
|
|
11887
|
+
{
|
|
11888
|
+
id: "subjectCode",
|
|
11889
|
+
value: subjectCode,
|
|
11890
|
+
onChange: (e) => setSubjectCode(e.target.value.toUpperCase()),
|
|
11891
|
+
placeholder: "e.g., MATH",
|
|
11892
|
+
disabled: !!currentSubject
|
|
11893
|
+
}
|
|
11894
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "subjectName" }, "Subject Name"), /* @__PURE__ */ React53__default.createElement(
|
|
11895
|
+
Input,
|
|
11896
|
+
{
|
|
11897
|
+
id: "subjectName",
|
|
11898
|
+
value: subjectName,
|
|
11899
|
+
onChange: (e) => setSubjectName(e.target.value),
|
|
11900
|
+
placeholder: "e.g., Mathematics"
|
|
11901
|
+
}
|
|
11902
|
+
))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !subjectName.trim() || !subjectCode.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This action cannot be undone. This will permanently delete the subject "', itemToDelete == null ? void 0 : itemToDelete.name, '" (Code: ', itemToDelete == null ? void 0 : itemToDelete.code, "). Ensure no topics, questions, or learning objectives reference this subject.")), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
11903
|
+
}
|
|
11904
|
+
function GradeLevelManager() {
|
|
11905
|
+
const [items, setItems] = useState([]);
|
|
11906
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11907
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
11908
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
11909
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
11910
|
+
const [itemName, setItemName] = useState("");
|
|
11911
|
+
const [itemCode, setItemCode] = useState("");
|
|
11912
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
11913
|
+
const [isPending, startTransition] = useTransition();
|
|
11914
|
+
const { toast: toast2 } = useToast();
|
|
11915
|
+
useEffect(() => {
|
|
11916
|
+
fetchItems();
|
|
11917
|
+
}, []);
|
|
11918
|
+
const fetchItems = () => {
|
|
11919
|
+
setIsLoading(true);
|
|
11920
|
+
try {
|
|
11921
|
+
setItems(MetadataService.getGradeLevels());
|
|
11922
|
+
} catch (error) {
|
|
11923
|
+
toast2({ title: "Error", description: "Failed to fetch grade levels.", variant: "destructive" });
|
|
11924
|
+
} finally {
|
|
11925
|
+
setIsLoading(false);
|
|
11926
|
+
}
|
|
11927
|
+
};
|
|
11928
|
+
const handleAddItem = () => {
|
|
11929
|
+
setCurrentItem(null);
|
|
11930
|
+
setItemName("");
|
|
11931
|
+
setItemCode("");
|
|
11932
|
+
setIsDialogOpen(true);
|
|
11933
|
+
};
|
|
11934
|
+
const handleEditItem = (item) => {
|
|
11935
|
+
setCurrentItem(item);
|
|
11936
|
+
setItemName(item.name);
|
|
11937
|
+
setItemCode(item.code);
|
|
11938
|
+
setIsDialogOpen(true);
|
|
11939
|
+
};
|
|
11940
|
+
const handleDeleteItem = (item) => {
|
|
11941
|
+
setItemToDelete(item);
|
|
11942
|
+
setIsAlertOpen(true);
|
|
11943
|
+
};
|
|
11944
|
+
const confirmDelete = () => {
|
|
11945
|
+
if (!itemToDelete) return;
|
|
11946
|
+
startTransition(() => {
|
|
11947
|
+
try {
|
|
11948
|
+
MetadataService.deleteGradeLevel(itemToDelete.code);
|
|
11949
|
+
toast2({ title: "Success", description: `Grade Level "${itemToDelete.name}" deleted.` });
|
|
11950
|
+
fetchItems();
|
|
11951
|
+
} catch (error) {
|
|
11952
|
+
toast2({ title: "Error", description: "Failed to delete grade level.", variant: "destructive" });
|
|
11953
|
+
} finally {
|
|
11954
|
+
setIsAlertOpen(false);
|
|
11955
|
+
setItemToDelete(null);
|
|
11956
|
+
}
|
|
11957
|
+
});
|
|
11958
|
+
};
|
|
11959
|
+
const handleSubmit = () => {
|
|
11960
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
11961
|
+
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
11962
|
+
return;
|
|
11963
|
+
}
|
|
11964
|
+
startTransition(() => {
|
|
11965
|
+
try {
|
|
11966
|
+
if (currentItem) {
|
|
11967
|
+
MetadataService.updateGradeLevel(currentItem.id, itemName, itemCode);
|
|
11968
|
+
} else {
|
|
11969
|
+
MetadataService.addGradeLevel(itemName, itemCode);
|
|
11970
|
+
}
|
|
11971
|
+
toast2({ title: "Success", description: `Grade Level saved.` });
|
|
11972
|
+
fetchItems();
|
|
11973
|
+
setIsDialogOpen(false);
|
|
11974
|
+
} catch (error) {
|
|
11975
|
+
toast2({ title: "Error", description: "Failed to save grade level.", variant: "destructive" });
|
|
11976
|
+
}
|
|
11977
|
+
});
|
|
11978
|
+
};
|
|
11979
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Award, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Grade Levels"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Grade Level"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No grade levels found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Grade Level" : "Add New Grade Level")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e) => setItemCode(e.target.value.toUpperCase()), placeholder: "e.g., G9", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e) => setItemName(e.target.value), placeholder: "e.g., Grade 9" }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
11980
|
+
}
|
|
11981
|
+
function TopicManager() {
|
|
11982
|
+
const [topics, setTopics] = useState([]);
|
|
11983
|
+
const [subjects, setSubjects] = useState([]);
|
|
11984
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11985
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
11986
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
11987
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
11988
|
+
const [itemName, setItemName] = useState("");
|
|
11989
|
+
const [itemCode, setItemCode] = useState("");
|
|
11990
|
+
const [selectedSubjectCode, setSelectedSubjectCode] = useState("");
|
|
11991
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
11992
|
+
const [isPending, startTransition] = useTransition();
|
|
11993
|
+
const { toast: toast2 } = useToast();
|
|
11994
|
+
const fetchData = () => {
|
|
11995
|
+
setIsLoading(true);
|
|
11996
|
+
try {
|
|
11997
|
+
const topicsData = MetadataService.getTopics();
|
|
11998
|
+
const subjectsData = MetadataService.getSubjects();
|
|
11999
|
+
setTopics(topicsData);
|
|
12000
|
+
setSubjects(subjectsData);
|
|
12001
|
+
if (subjectsData.length > 0 && !selectedSubjectCode) {
|
|
12002
|
+
setSelectedSubjectCode(subjectsData[0].code);
|
|
12003
|
+
}
|
|
12004
|
+
} catch (error) {
|
|
12005
|
+
toast2({
|
|
12006
|
+
title: "Error",
|
|
12007
|
+
description: "Failed to fetch topics or subjects.",
|
|
12008
|
+
variant: "destructive"
|
|
12009
|
+
});
|
|
12010
|
+
} finally {
|
|
12011
|
+
setIsLoading(false);
|
|
12012
|
+
}
|
|
12013
|
+
};
|
|
12014
|
+
useEffect(() => {
|
|
12015
|
+
fetchData();
|
|
12016
|
+
}, []);
|
|
12017
|
+
const handleAddItem = () => {
|
|
12018
|
+
setCurrentItem(null);
|
|
12019
|
+
setItemName("");
|
|
12020
|
+
setItemCode("");
|
|
12021
|
+
if (subjects.length > 0) {
|
|
12022
|
+
setSelectedSubjectCode(subjects[0].code);
|
|
12023
|
+
}
|
|
12024
|
+
setIsDialogOpen(true);
|
|
12025
|
+
};
|
|
12026
|
+
const handleEditItem = (topic) => {
|
|
12027
|
+
setCurrentItem(topic);
|
|
12028
|
+
setItemName(topic.name);
|
|
12029
|
+
setItemCode(topic.code);
|
|
12030
|
+
setSelectedSubjectCode(topic.subjectCode);
|
|
12031
|
+
setIsDialogOpen(true);
|
|
12032
|
+
};
|
|
12033
|
+
const handleDeleteItem = (topic) => {
|
|
12034
|
+
setItemToDelete(topic);
|
|
12035
|
+
setIsAlertOpen(true);
|
|
12036
|
+
};
|
|
12037
|
+
const confirmDelete = () => {
|
|
12038
|
+
if (!itemToDelete) return;
|
|
12039
|
+
startTransition(() => {
|
|
12040
|
+
try {
|
|
12041
|
+
MetadataService.deleteTopic(itemToDelete.code);
|
|
12042
|
+
toast2({
|
|
12043
|
+
title: "Success",
|
|
12044
|
+
description: `Topic "${itemToDelete.name}" deleted.`
|
|
12045
|
+
});
|
|
12046
|
+
fetchData();
|
|
12047
|
+
} catch (error) {
|
|
12048
|
+
toast2({
|
|
12049
|
+
title: "Error",
|
|
12050
|
+
description: "Failed to delete topic.",
|
|
12051
|
+
variant: "destructive"
|
|
12052
|
+
});
|
|
12053
|
+
} finally {
|
|
12054
|
+
setIsAlertOpen(false);
|
|
12055
|
+
setItemToDelete(null);
|
|
12056
|
+
}
|
|
12057
|
+
});
|
|
12058
|
+
};
|
|
12059
|
+
const handleSubmit = () => {
|
|
12060
|
+
if (!itemName.trim() || !itemCode.trim() || !selectedSubjectCode) {
|
|
12061
|
+
toast2({
|
|
12062
|
+
title: "Validation Error",
|
|
12063
|
+
description: "Name, Code, and Subject are required.",
|
|
12064
|
+
variant: "destructive"
|
|
12065
|
+
});
|
|
12066
|
+
return;
|
|
12067
|
+
}
|
|
12068
|
+
startTransition(() => {
|
|
12069
|
+
try {
|
|
12070
|
+
if (currentItem) {
|
|
12071
|
+
MetadataService.updateTopic(
|
|
12072
|
+
currentItem.id,
|
|
12073
|
+
itemName,
|
|
12074
|
+
itemCode,
|
|
12075
|
+
selectedSubjectCode
|
|
12076
|
+
);
|
|
12077
|
+
} else {
|
|
12078
|
+
MetadataService.addTopic(
|
|
12079
|
+
itemName,
|
|
12080
|
+
itemCode,
|
|
12081
|
+
selectedSubjectCode
|
|
12082
|
+
);
|
|
12083
|
+
}
|
|
12084
|
+
toast2({ title: "Success", description: "Topic saved." });
|
|
12085
|
+
fetchData();
|
|
12086
|
+
setIsDialogOpen(false);
|
|
12087
|
+
} catch (error) {
|
|
12088
|
+
toast2({
|
|
12089
|
+
title: "Error",
|
|
12090
|
+
description: "Failed to save topic.",
|
|
12091
|
+
variant: "destructive"
|
|
12092
|
+
});
|
|
12093
|
+
}
|
|
12094
|
+
});
|
|
12095
|
+
};
|
|
12096
|
+
const getSubjectName = (subjectCode) => {
|
|
12097
|
+
var _a;
|
|
12098
|
+
return ((_a = subjects.find((s) => s.code === subjectCode)) == null ? void 0 : _a.name) || "N/A";
|
|
12099
|
+
};
|
|
12100
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Tag, { className: "mr-2 h-5 w-5 text-primary" }), "Manage Topics"), /* @__PURE__ */ React53__default.createElement(
|
|
12101
|
+
Button,
|
|
12102
|
+
{
|
|
12103
|
+
onClick: handleAddItem,
|
|
12104
|
+
size: "sm",
|
|
12105
|
+
disabled: subjects.length === 0
|
|
12106
|
+
},
|
|
12107
|
+
/* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }),
|
|
12108
|
+
" Add Topic"
|
|
12109
|
+
)), subjects.length === 0 && !isLoading && /* @__PURE__ */ React53__default.createElement("p", { className: "text-sm text-destructive" }, "Please add subjects before adding topics.")), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : topics.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No topics found. Add one to get started!") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, topics.map((topic) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: topic.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, topic.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, topic.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, getSubjectName(topic.subjectCode), " ", "(", topic.subjectCode, ")"), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(
|
|
12110
|
+
Button,
|
|
12111
|
+
{
|
|
12112
|
+
variant: "ghost",
|
|
12113
|
+
size: "icon",
|
|
12114
|
+
onClick: () => handleEditItem(topic),
|
|
12115
|
+
className: "mr-2"
|
|
12116
|
+
},
|
|
12117
|
+
/* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })
|
|
12118
|
+
), /* @__PURE__ */ React53__default.createElement(
|
|
12119
|
+
Button,
|
|
12120
|
+
{
|
|
12121
|
+
variant: "ghost",
|
|
12122
|
+
size: "icon",
|
|
12123
|
+
onClick: () => handleDeleteItem(topic),
|
|
12124
|
+
className: "text-destructive hover:text-destructive"
|
|
12125
|
+
},
|
|
12126
|
+
/* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })
|
|
12127
|
+
))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Topic" : "Add New Topic")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Topic Code"), /* @__PURE__ */ React53__default.createElement(
|
|
12128
|
+
Input,
|
|
12129
|
+
{
|
|
12130
|
+
id: "itemCode",
|
|
12131
|
+
value: itemCode,
|
|
12132
|
+
onChange: (e) => setItemCode(
|
|
12133
|
+
e.target.value.toUpperCase()
|
|
12134
|
+
),
|
|
12135
|
+
placeholder: "e.g., ALG-BASICS",
|
|
12136
|
+
disabled: !!currentItem
|
|
12137
|
+
}
|
|
12138
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Topic Name"), /* @__PURE__ */ React53__default.createElement(
|
|
12139
|
+
Input,
|
|
12140
|
+
{
|
|
12141
|
+
id: "itemName",
|
|
12142
|
+
value: itemName,
|
|
12143
|
+
onChange: (e) => setItemName(e.target.value),
|
|
12144
|
+
placeholder: "e.g., Algebra Basics"
|
|
12145
|
+
}
|
|
12146
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React53__default.createElement(
|
|
12147
|
+
Select,
|
|
12148
|
+
{
|
|
12149
|
+
value: selectedSubjectCode,
|
|
12150
|
+
onValueChange: setSelectedSubjectCode,
|
|
12151
|
+
disabled: subjects.length === 0
|
|
12152
|
+
},
|
|
12153
|
+
/* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "subjectCode" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select a subject" })),
|
|
12154
|
+
/* @__PURE__ */ React53__default.createElement(SelectContent, null, subjects.map((subject) => /* @__PURE__ */ React53__default.createElement(
|
|
12155
|
+
SelectItem,
|
|
12156
|
+
{
|
|
12157
|
+
key: subject.code,
|
|
12158
|
+
value: subject.code
|
|
12159
|
+
},
|
|
12160
|
+
subject.name,
|
|
12161
|
+
" (",
|
|
12162
|
+
subject.code,
|
|
12163
|
+
")"
|
|
12164
|
+
)))
|
|
12165
|
+
))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(
|
|
12166
|
+
Button,
|
|
12167
|
+
{
|
|
12168
|
+
type: "button",
|
|
12169
|
+
variant: "outline",
|
|
12170
|
+
onClick: () => setIsDialogOpen(false),
|
|
12171
|
+
disabled: isPending
|
|
12172
|
+
},
|
|
12173
|
+
"Cancel"
|
|
12174
|
+
), /* @__PURE__ */ React53__default.createElement(
|
|
12175
|
+
Button,
|
|
12176
|
+
{
|
|
12177
|
+
type: "submit",
|
|
12178
|
+
onClick: handleSubmit,
|
|
12179
|
+
disabled: isPending || !itemName.trim() || !itemCode.trim() || !selectedSubjectCode
|
|
12180
|
+
},
|
|
12181
|
+
isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
12182
|
+
" ",
|
|
12183
|
+
"Save"
|
|
12184
|
+
)))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete topic "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(
|
|
12185
|
+
AlertDialogAction,
|
|
12186
|
+
{
|
|
12187
|
+
onClick: confirmDelete,
|
|
12188
|
+
disabled: isPending,
|
|
12189
|
+
className: "bg-destructive hover:bg-destructive/90"
|
|
12190
|
+
},
|
|
12191
|
+
isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
12192
|
+
" ",
|
|
12193
|
+
"Delete"
|
|
12194
|
+
))))));
|
|
12195
|
+
}
|
|
12196
|
+
function CategoryManager() {
|
|
12197
|
+
const [items, setItems] = useState([]);
|
|
12198
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12199
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12200
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12201
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12202
|
+
const [itemName, setItemName] = useState("");
|
|
12203
|
+
const [itemCode, setItemCode] = useState("");
|
|
12204
|
+
const [itemDescription, setItemDescription] = useState("");
|
|
12205
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12206
|
+
const [isPending, startTransition] = useTransition();
|
|
12207
|
+
const { toast: toast2 } = useToast();
|
|
12208
|
+
useEffect(() => {
|
|
12209
|
+
fetchItems();
|
|
12210
|
+
}, []);
|
|
12211
|
+
const fetchItems = () => {
|
|
12212
|
+
setIsLoading(true);
|
|
12213
|
+
try {
|
|
12214
|
+
setItems(MetadataService.getCategories());
|
|
12215
|
+
} catch (error) {
|
|
12216
|
+
toast2({ title: "Error", description: "Failed to fetch categories.", variant: "destructive" });
|
|
12217
|
+
} finally {
|
|
12218
|
+
setIsLoading(false);
|
|
12219
|
+
}
|
|
12220
|
+
};
|
|
12221
|
+
const handleAddItem = () => {
|
|
12222
|
+
setCurrentItem(null);
|
|
12223
|
+
setItemName("");
|
|
12224
|
+
setItemCode("");
|
|
12225
|
+
setItemDescription("");
|
|
12226
|
+
setIsDialogOpen(true);
|
|
12227
|
+
};
|
|
12228
|
+
const handleEditItem = (item) => {
|
|
12229
|
+
setCurrentItem(item);
|
|
12230
|
+
setItemName(item.name);
|
|
12231
|
+
setItemCode(item.code);
|
|
12232
|
+
setItemDescription(item.description || "");
|
|
12233
|
+
setIsDialogOpen(true);
|
|
12234
|
+
};
|
|
12235
|
+
const handleDeleteItem = (item) => {
|
|
12236
|
+
setItemToDelete(item);
|
|
12237
|
+
setIsAlertOpen(true);
|
|
12238
|
+
};
|
|
12239
|
+
const confirmDelete = () => {
|
|
12240
|
+
if (!itemToDelete) return;
|
|
12241
|
+
startTransition(() => {
|
|
12242
|
+
try {
|
|
12243
|
+
MetadataService.deleteCategory(itemToDelete.code);
|
|
12244
|
+
toast2({ title: "Success", description: `Category "${itemToDelete.name}" deleted.` });
|
|
12245
|
+
fetchItems();
|
|
12246
|
+
} catch (error) {
|
|
12247
|
+
toast2({ title: "Error", description: "Failed to delete category.", variant: "destructive" });
|
|
12248
|
+
} finally {
|
|
12249
|
+
setIsAlertOpen(false);
|
|
12250
|
+
setItemToDelete(null);
|
|
12251
|
+
}
|
|
12252
|
+
});
|
|
12253
|
+
};
|
|
12254
|
+
const handleSubmit = () => {
|
|
12255
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
12256
|
+
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
12257
|
+
return;
|
|
12258
|
+
}
|
|
12259
|
+
startTransition(() => {
|
|
12260
|
+
try {
|
|
12261
|
+
if (currentItem) {
|
|
12262
|
+
MetadataService.updateCategory(currentItem.id, itemName, itemCode, itemDescription);
|
|
12263
|
+
} else {
|
|
12264
|
+
MetadataService.addCategory(itemName, itemCode, itemDescription);
|
|
12265
|
+
}
|
|
12266
|
+
toast2({ title: "Success", description: "Category saved." });
|
|
12267
|
+
fetchItems();
|
|
12268
|
+
setIsDialogOpen(false);
|
|
12269
|
+
} catch (error) {
|
|
12270
|
+
toast2({ title: "Error", description: "Failed to save category.", variant: "destructive" });
|
|
12271
|
+
}
|
|
12272
|
+
});
|
|
12273
|
+
};
|
|
12274
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Layers, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Categories"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Category"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No categories found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Category" : "Add New Category")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e) => setItemCode(e.target.value.toUpperCase()), placeholder: "e.g., CORE_CONCEPT", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e) => setItemName(e.target.value), placeholder: "e.g., Core Concept" })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e) => setItemDescription(e.target.value), placeholder: "e.g., Fundamental ideas within a subject." }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
12275
|
+
}
|
|
12276
|
+
function BloomLevelManager() {
|
|
12277
|
+
const [items, setItems] = useState([]);
|
|
12278
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12279
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12280
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12281
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12282
|
+
const [itemCode, setItemCode] = useState("");
|
|
12283
|
+
const [itemName, setItemName] = useState("");
|
|
12284
|
+
const [itemDescription, setItemDescription] = useState("");
|
|
12285
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12286
|
+
const [isPending, startTransition] = useTransition();
|
|
12287
|
+
const { toast: toast2 } = useToast();
|
|
12288
|
+
useEffect(() => {
|
|
12289
|
+
fetchItems();
|
|
12290
|
+
}, []);
|
|
12291
|
+
const fetchItems = () => {
|
|
12292
|
+
setIsLoading(true);
|
|
12293
|
+
try {
|
|
12294
|
+
setItems(MetadataService.getBloomLevels());
|
|
12295
|
+
} catch (error) {
|
|
12296
|
+
toast2({ title: "Error", description: "Failed to fetch Bloom's Levels.", variant: "destructive" });
|
|
12297
|
+
} finally {
|
|
12298
|
+
setIsLoading(false);
|
|
12299
|
+
}
|
|
12300
|
+
};
|
|
12301
|
+
const handleAddItem = () => {
|
|
12302
|
+
setCurrentItem(null);
|
|
12303
|
+
setItemCode("");
|
|
12304
|
+
setItemName("");
|
|
12305
|
+
setItemDescription("");
|
|
12306
|
+
setIsDialogOpen(true);
|
|
12307
|
+
};
|
|
12308
|
+
const handleEditItem = (item) => {
|
|
12309
|
+
setCurrentItem(item);
|
|
12310
|
+
setItemCode(item.code);
|
|
12311
|
+
setItemName(item.name);
|
|
12312
|
+
setItemDescription(item.description || "");
|
|
12313
|
+
setIsDialogOpen(true);
|
|
12314
|
+
};
|
|
12315
|
+
const handleDeleteItem = (item) => {
|
|
12316
|
+
setItemToDelete(item);
|
|
12317
|
+
setIsAlertOpen(true);
|
|
12318
|
+
};
|
|
12319
|
+
const confirmDelete = () => {
|
|
12320
|
+
if (!itemToDelete) return;
|
|
12321
|
+
startTransition(() => {
|
|
12322
|
+
try {
|
|
12323
|
+
MetadataService.deleteBloomLevel(itemToDelete.code);
|
|
12324
|
+
toast2({ title: "Success", description: `Bloom's Level "${itemToDelete.name}" deleted.` });
|
|
12325
|
+
fetchItems();
|
|
12326
|
+
} catch (error) {
|
|
12327
|
+
toast2({ title: "Error", description: "Failed to delete Bloom's Level.", variant: "destructive" });
|
|
12328
|
+
} finally {
|
|
12329
|
+
setIsAlertOpen(false);
|
|
12330
|
+
setItemToDelete(null);
|
|
12331
|
+
}
|
|
12332
|
+
});
|
|
12333
|
+
};
|
|
12334
|
+
const handleSubmit = () => {
|
|
12335
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
12336
|
+
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
12337
|
+
return;
|
|
12338
|
+
}
|
|
12339
|
+
startTransition(() => {
|
|
12340
|
+
try {
|
|
12341
|
+
if (currentItem) {
|
|
12342
|
+
MetadataService.updateBloomLevel(currentItem.id, itemName, itemCode, itemDescription);
|
|
12343
|
+
} else {
|
|
12344
|
+
MetadataService.addBloomLevel(itemName, itemCode, itemDescription);
|
|
12345
|
+
}
|
|
12346
|
+
toast2({ title: "Success", description: "Bloom's Level saved." });
|
|
12347
|
+
fetchItems();
|
|
12348
|
+
setIsDialogOpen(false);
|
|
12349
|
+
} catch (error) {
|
|
12350
|
+
toast2({ title: "Error", description: "Failed to save Bloom's Level.", variant: "destructive" });
|
|
12351
|
+
}
|
|
12352
|
+
});
|
|
12353
|
+
};
|
|
12354
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Brain, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Bloom's Levels"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Bloom's Level"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Bloom's Levels found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Bloom's Level" : "Add New Bloom's Level")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e) => setItemCode(e.target.value.toUpperCase()), placeholder: "e.g., REMEMBER", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e) => setItemName(e.target.value), placeholder: "e.g., Remembering" })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e) => setItemDescription(e.target.value), placeholder: "e.g., Recall facts and basic concepts." }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
12355
|
+
}
|
|
12356
|
+
function QuestionTypeManager() {
|
|
12357
|
+
const [items, setItems] = useState([]);
|
|
12358
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12359
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12360
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12361
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12362
|
+
const [itemCode, setItemCode] = useState("");
|
|
12363
|
+
const [itemName, setItemName] = useState("");
|
|
12364
|
+
const [itemDescription, setItemDescription] = useState("");
|
|
12365
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12366
|
+
const [isPending, startTransition] = useTransition();
|
|
12367
|
+
const { toast: toast2 } = useToast();
|
|
12368
|
+
useEffect(() => {
|
|
12369
|
+
fetchItems();
|
|
12370
|
+
}, []);
|
|
12371
|
+
const fetchItems = () => {
|
|
12372
|
+
setIsLoading(true);
|
|
12373
|
+
try {
|
|
12374
|
+
setItems(MetadataService.getQuestionTypes());
|
|
12375
|
+
} catch (error) {
|
|
12376
|
+
toast2({ title: "Error", description: "Failed to fetch Question Types.", variant: "destructive" });
|
|
12377
|
+
} finally {
|
|
12378
|
+
setIsLoading(false);
|
|
12379
|
+
}
|
|
12380
|
+
};
|
|
12381
|
+
const handleAddItem = () => {
|
|
12382
|
+
setCurrentItem(null);
|
|
12383
|
+
setItemCode("");
|
|
12384
|
+
setItemName("");
|
|
12385
|
+
setItemDescription("");
|
|
12386
|
+
setIsDialogOpen(true);
|
|
12387
|
+
};
|
|
12388
|
+
const handleEditItem = (item) => {
|
|
12389
|
+
setCurrentItem(item);
|
|
12390
|
+
setItemCode(item.code);
|
|
12391
|
+
setItemName(item.name);
|
|
12392
|
+
setItemDescription(item.description || "");
|
|
12393
|
+
setIsDialogOpen(true);
|
|
12394
|
+
};
|
|
12395
|
+
const handleDeleteItem = (item) => {
|
|
12396
|
+
setItemToDelete(item);
|
|
12397
|
+
setIsAlertOpen(true);
|
|
12398
|
+
};
|
|
12399
|
+
const confirmDelete = () => {
|
|
12400
|
+
if (!itemToDelete) return;
|
|
12401
|
+
startTransition(() => {
|
|
12402
|
+
try {
|
|
12403
|
+
MetadataService.deleteQuestionType(itemToDelete.code);
|
|
12404
|
+
toast2({ title: "Success", description: `Question Type "${itemToDelete.name}" deleted.` });
|
|
12405
|
+
fetchItems();
|
|
12406
|
+
} catch (error) {
|
|
12407
|
+
toast2({ title: "Error", description: "Failed to delete Question Type.", variant: "destructive" });
|
|
12408
|
+
} finally {
|
|
12409
|
+
setIsAlertOpen(false);
|
|
12410
|
+
setItemToDelete(null);
|
|
12411
|
+
}
|
|
12412
|
+
});
|
|
12413
|
+
};
|
|
12414
|
+
const handleSubmit = () => {
|
|
12415
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
12416
|
+
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
12417
|
+
return;
|
|
12418
|
+
}
|
|
12419
|
+
startTransition(() => {
|
|
12420
|
+
try {
|
|
12421
|
+
if (currentItem) {
|
|
12422
|
+
MetadataService.updateQuestionType(currentItem.id, itemName, itemCode, itemDescription);
|
|
12423
|
+
} else {
|
|
12424
|
+
MetadataService.addQuestionType(itemName, itemCode, itemDescription);
|
|
12425
|
+
}
|
|
12426
|
+
toast2({ title: "Success", description: "Question Type saved." });
|
|
12427
|
+
fetchItems();
|
|
12428
|
+
setIsDialogOpen(false);
|
|
12429
|
+
} catch (error) {
|
|
12430
|
+
toast2({ title: "Error", description: "Failed to save Question Type.", variant: "destructive" });
|
|
12431
|
+
}
|
|
12432
|
+
});
|
|
12433
|
+
};
|
|
12434
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(HelpCircle, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Question Types"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Question Type"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Question Types found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Question Type" : "Add New Question Type")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e) => setItemCode(e.target.value.toUpperCase()), placeholder: "e.g., MCQ", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e) => setItemName(e.target.value), placeholder: "e.g., Multiple Choice" })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e) => setItemDescription(e.target.value), placeholder: "e.g., Select one answer from a list of options." }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
12435
|
+
}
|
|
12436
|
+
function LearningObjectiveManager() {
|
|
12437
|
+
const [items, setItems] = useState([]);
|
|
12438
|
+
const [subjects, setSubjects] = useState([]);
|
|
12439
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12440
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12441
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12442
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12443
|
+
const [itemName, setItemName] = useState("");
|
|
12444
|
+
const [itemCode, setItemCode] = useState("");
|
|
12445
|
+
const [itemDescription, setItemDescription] = useState("");
|
|
12446
|
+
const [selectedSubjectCode, setSelectedSubjectCode] = useState(void 0);
|
|
12447
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12448
|
+
const [isPending, startTransition] = useTransition();
|
|
12449
|
+
const { toast: toast2 } = useToast();
|
|
12450
|
+
const fetchData = () => {
|
|
12451
|
+
setIsLoading(true);
|
|
12452
|
+
try {
|
|
12453
|
+
const loData = MetadataService.getLearningObjectives();
|
|
12454
|
+
const subjectsData = MetadataService.getSubjects();
|
|
12455
|
+
setItems(loData);
|
|
12456
|
+
setSubjects(subjectsData);
|
|
12457
|
+
} catch (error) {
|
|
12458
|
+
toast2({
|
|
12459
|
+
title: "Error",
|
|
12460
|
+
description: "Failed to fetch Learning Objectives or Subjects.",
|
|
12461
|
+
variant: "destructive"
|
|
12462
|
+
});
|
|
12463
|
+
} finally {
|
|
12464
|
+
setIsLoading(false);
|
|
12465
|
+
}
|
|
12466
|
+
};
|
|
12467
|
+
useEffect(() => {
|
|
12468
|
+
fetchData();
|
|
12469
|
+
}, []);
|
|
12470
|
+
const handleAddItem = () => {
|
|
12471
|
+
setCurrentItem(null);
|
|
12472
|
+
setItemName("");
|
|
12473
|
+
setItemCode("");
|
|
12474
|
+
setItemDescription("");
|
|
12475
|
+
setSelectedSubjectCode(
|
|
12476
|
+
subjects.length > 0 ? subjects[0].code : void 0
|
|
12477
|
+
);
|
|
12478
|
+
setIsDialogOpen(true);
|
|
12479
|
+
};
|
|
12480
|
+
const handleEditItem = (item) => {
|
|
12481
|
+
setCurrentItem(item);
|
|
12482
|
+
setItemName(item.name);
|
|
12483
|
+
setItemCode(item.code);
|
|
12484
|
+
setItemDescription(item.description || "");
|
|
12485
|
+
setSelectedSubjectCode(item.subjectCode);
|
|
12486
|
+
setIsDialogOpen(true);
|
|
12487
|
+
};
|
|
12488
|
+
const handleDeleteItem = (item) => {
|
|
12489
|
+
setItemToDelete(item);
|
|
12490
|
+
setIsAlertOpen(true);
|
|
12491
|
+
};
|
|
12492
|
+
const confirmDelete = () => {
|
|
12493
|
+
if (!itemToDelete) return;
|
|
12494
|
+
startTransition(() => {
|
|
12495
|
+
try {
|
|
12496
|
+
MetadataService.deleteLearningObjective(itemToDelete.code);
|
|
12497
|
+
toast2({
|
|
12498
|
+
title: "Success",
|
|
12499
|
+
description: `Learning Objective "${itemToDelete.name}" deleted.`
|
|
12500
|
+
});
|
|
12501
|
+
fetchData();
|
|
12502
|
+
} catch (error) {
|
|
12503
|
+
toast2({
|
|
12504
|
+
title: "Error",
|
|
12505
|
+
description: "Failed to delete Learning Objective.",
|
|
12506
|
+
variant: "destructive"
|
|
12507
|
+
});
|
|
12508
|
+
} finally {
|
|
12509
|
+
setIsAlertOpen(false);
|
|
12510
|
+
setItemToDelete(null);
|
|
12511
|
+
}
|
|
12512
|
+
});
|
|
12513
|
+
};
|
|
12514
|
+
const handleSubmit = () => {
|
|
12515
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
12516
|
+
toast2({
|
|
12517
|
+
title: "Validation Error",
|
|
12518
|
+
description: "Code and Name are required.",
|
|
12519
|
+
variant: "destructive"
|
|
12520
|
+
});
|
|
12521
|
+
return;
|
|
12522
|
+
}
|
|
12523
|
+
startTransition(() => {
|
|
12524
|
+
try {
|
|
12525
|
+
if (currentItem) {
|
|
12526
|
+
MetadataService.updateLearningObjective(
|
|
12527
|
+
currentItem.id,
|
|
12528
|
+
itemName,
|
|
12529
|
+
itemCode,
|
|
12530
|
+
selectedSubjectCode,
|
|
12531
|
+
itemDescription
|
|
12532
|
+
);
|
|
12533
|
+
} else {
|
|
12534
|
+
MetadataService.addLearningObjective(
|
|
12535
|
+
itemName,
|
|
12536
|
+
itemCode,
|
|
12537
|
+
selectedSubjectCode,
|
|
12538
|
+
itemDescription
|
|
12539
|
+
);
|
|
12540
|
+
}
|
|
12541
|
+
toast2({
|
|
12542
|
+
title: "Success",
|
|
12543
|
+
description: "Learning Objective saved."
|
|
12544
|
+
});
|
|
12545
|
+
fetchData();
|
|
12546
|
+
setIsDialogOpen(false);
|
|
12547
|
+
} catch (error) {
|
|
12548
|
+
toast2({
|
|
12549
|
+
title: "Error",
|
|
12550
|
+
description: "Failed to save Learning Objective.",
|
|
12551
|
+
variant: "destructive"
|
|
12552
|
+
});
|
|
12553
|
+
}
|
|
12554
|
+
});
|
|
12555
|
+
};
|
|
12556
|
+
const getSubjectName = (subjectCode) => {
|
|
12557
|
+
var _a;
|
|
12558
|
+
if (!subjectCode) return "N/A";
|
|
12559
|
+
return ((_a = subjects.find((s) => s.code === subjectCode)) == null ? void 0 : _a.name) || "N/A";
|
|
12560
|
+
};
|
|
12561
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " ", "Manage Learning Objectives"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Learning Objective"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, getSubjectName(item.subjectCode)), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(
|
|
12562
|
+
Button,
|
|
12563
|
+
{
|
|
12564
|
+
variant: "ghost",
|
|
12565
|
+
size: "icon",
|
|
12566
|
+
onClick: () => handleEditItem(item),
|
|
12567
|
+
className: "mr-2"
|
|
12568
|
+
},
|
|
12569
|
+
/* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })
|
|
12570
|
+
), /* @__PURE__ */ React53__default.createElement(
|
|
12571
|
+
Button,
|
|
12572
|
+
{
|
|
12573
|
+
variant: "ghost",
|
|
12574
|
+
size: "icon",
|
|
12575
|
+
onClick: () => handleDeleteItem(item),
|
|
12576
|
+
className: "text-destructive hover:text-destructive"
|
|
12577
|
+
},
|
|
12578
|
+
/* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })
|
|
12579
|
+
))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(
|
|
12580
|
+
Input,
|
|
12581
|
+
{
|
|
12582
|
+
id: "itemCode",
|
|
12583
|
+
value: itemCode,
|
|
12584
|
+
onChange: (e) => setItemCode(
|
|
12585
|
+
e.target.value.toUpperCase()
|
|
12586
|
+
),
|
|
12587
|
+
disabled: !!currentItem
|
|
12588
|
+
}
|
|
12589
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(
|
|
12590
|
+
Input,
|
|
12591
|
+
{
|
|
12592
|
+
id: "itemName",
|
|
12593
|
+
value: itemName,
|
|
12594
|
+
onChange: (e) => setItemName(e.target.value)
|
|
12595
|
+
}
|
|
12596
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemSubject" }, "Subject (Optional)"), /* @__PURE__ */ React53__default.createElement(
|
|
12597
|
+
Select,
|
|
12598
|
+
{
|
|
12599
|
+
value: selectedSubjectCode || "",
|
|
12600
|
+
onValueChange: (value) => setSelectedSubjectCode(
|
|
12601
|
+
value === "_NONE_" ? void 0 : value
|
|
12602
|
+
)
|
|
12603
|
+
},
|
|
12604
|
+
/* @__PURE__ */ React53__default.createElement(SelectTrigger, { id: "itemSubject" }, /* @__PURE__ */ React53__default.createElement(SelectValue, { placeholder: "Select a subject" })),
|
|
12605
|
+
/* @__PURE__ */ React53__default.createElement(SelectContent, null, /* @__PURE__ */ React53__default.createElement(SelectItem, { value: "_NONE_" }, "No Specific Subject"), subjects.map((subject) => /* @__PURE__ */ React53__default.createElement(
|
|
12606
|
+
SelectItem,
|
|
12607
|
+
{
|
|
12608
|
+
key: subject.id,
|
|
12609
|
+
value: subject.code
|
|
12610
|
+
},
|
|
12611
|
+
subject.name
|
|
12612
|
+
)))
|
|
12613
|
+
)), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React53__default.createElement(
|
|
12614
|
+
Textarea,
|
|
12615
|
+
{
|
|
12616
|
+
id: "itemDescription",
|
|
12617
|
+
value: itemDescription,
|
|
12618
|
+
onChange: (e) => setItemDescription(e.target.value)
|
|
12619
|
+
}
|
|
12620
|
+
))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(
|
|
12621
|
+
Button,
|
|
12622
|
+
{
|
|
12623
|
+
type: "button",
|
|
12624
|
+
variant: "outline",
|
|
12625
|
+
onClick: () => setIsDialogOpen(false),
|
|
12626
|
+
disabled: isPending
|
|
12627
|
+
},
|
|
12628
|
+
"Cancel"
|
|
12629
|
+
), /* @__PURE__ */ React53__default.createElement(
|
|
12630
|
+
Button,
|
|
12631
|
+
{
|
|
12632
|
+
type: "submit",
|
|
12633
|
+
onClick: handleSubmit,
|
|
12634
|
+
disabled: isPending || !itemName.trim() || !itemCode.trim()
|
|
12635
|
+
},
|
|
12636
|
+
isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
12637
|
+
" ",
|
|
12638
|
+
"Save"
|
|
12639
|
+
)))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(
|
|
12640
|
+
AlertDialogAction,
|
|
12641
|
+
{
|
|
12642
|
+
onClick: confirmDelete,
|
|
12643
|
+
disabled: isPending,
|
|
12644
|
+
className: "bg-destructive hover:bg-destructive/90"
|
|
12645
|
+
},
|
|
12646
|
+
isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
12647
|
+
" ",
|
|
12648
|
+
"Delete"
|
|
12649
|
+
))))));
|
|
12650
|
+
}
|
|
12651
|
+
function ContextManager() {
|
|
12652
|
+
const [items, setItems] = useState([]);
|
|
12653
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12654
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12655
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12656
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12657
|
+
const [itemName, setItemName] = useState("");
|
|
12658
|
+
const [itemCode, setItemCode] = useState("");
|
|
12659
|
+
const [itemDescription, setItemDescription] = useState("");
|
|
12660
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12661
|
+
const [isPending, startTransition] = useTransition();
|
|
12662
|
+
const { toast: toast2 } = useToast();
|
|
12663
|
+
useEffect(() => {
|
|
12664
|
+
fetchItems();
|
|
12665
|
+
}, []);
|
|
12666
|
+
const fetchItems = () => {
|
|
12667
|
+
setIsLoading(true);
|
|
12668
|
+
try {
|
|
12669
|
+
setItems(MetadataService.getContexts());
|
|
12670
|
+
} catch (error) {
|
|
12671
|
+
toast2({ title: "Error", description: "Failed to fetch Contexts.", variant: "destructive" });
|
|
12672
|
+
} finally {
|
|
12673
|
+
setIsLoading(false);
|
|
12674
|
+
}
|
|
12675
|
+
};
|
|
12676
|
+
const handleAddItem = () => {
|
|
12677
|
+
setCurrentItem(null);
|
|
12678
|
+
setItemName("");
|
|
12679
|
+
setItemCode("");
|
|
12680
|
+
setItemDescription("");
|
|
12681
|
+
setIsDialogOpen(true);
|
|
12682
|
+
};
|
|
12683
|
+
const handleEditItem = (item) => {
|
|
12684
|
+
setCurrentItem(item);
|
|
12685
|
+
setItemName(item.name);
|
|
12686
|
+
setItemCode(item.code);
|
|
12687
|
+
setItemDescription(item.description || "");
|
|
12688
|
+
setIsDialogOpen(true);
|
|
12689
|
+
};
|
|
12690
|
+
const handleDeleteItem = (item) => {
|
|
12691
|
+
setItemToDelete(item);
|
|
12692
|
+
setIsAlertOpen(true);
|
|
12693
|
+
};
|
|
12694
|
+
const confirmDelete = () => {
|
|
12695
|
+
if (!itemToDelete) return;
|
|
12696
|
+
startTransition(() => {
|
|
12697
|
+
try {
|
|
12698
|
+
MetadataService.deleteContext(itemToDelete.code);
|
|
12699
|
+
toast2({ title: "Success", description: `Context "${itemToDelete.name}" deleted.` });
|
|
12700
|
+
fetchItems();
|
|
12701
|
+
} catch (error) {
|
|
12702
|
+
toast2({ title: "Error", description: "Failed to delete Context.", variant: "destructive" });
|
|
12703
|
+
} finally {
|
|
12704
|
+
setIsAlertOpen(false);
|
|
12705
|
+
setItemToDelete(null);
|
|
12706
|
+
}
|
|
12707
|
+
});
|
|
12708
|
+
};
|
|
12709
|
+
const handleSubmit = () => {
|
|
12710
|
+
if (!itemName.trim() || !itemCode.trim()) {
|
|
12711
|
+
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
12712
|
+
return;
|
|
12713
|
+
}
|
|
12714
|
+
startTransition(() => {
|
|
12715
|
+
try {
|
|
12716
|
+
if (currentItem) {
|
|
12717
|
+
MetadataService.updateContext(currentItem.id, itemName, itemCode, itemDescription);
|
|
12718
|
+
} else {
|
|
12719
|
+
MetadataService.addContext(itemName, itemCode, itemDescription);
|
|
12720
|
+
}
|
|
12721
|
+
toast2({ title: "Success", description: "Context saved." });
|
|
12722
|
+
fetchItems();
|
|
12723
|
+
setIsDialogOpen(false);
|
|
12724
|
+
} catch (error) {
|
|
12725
|
+
toast2({ title: "Error", description: "Failed to save Context.", variant: "destructive" });
|
|
12726
|
+
}
|
|
12727
|
+
});
|
|
12728
|
+
};
|
|
12729
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(ScanText, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Contexts"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Context"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Contexts found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-md" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Context" : "Add New Context")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e) => setItemCode(e.target.value.toUpperCase()), placeholder: "e.g., HIST_INQ", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React53__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e) => setItemName(e.target.value), placeholder: "e.g., Historical Inquiry" })), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e) => setItemDescription(e.target.value), placeholder: "e.g., Analyzing primary and secondary sources." }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.name, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Delete"))))));
|
|
12730
|
+
}
|
|
12731
|
+
var knowledgeDimensions = ["Factual", "Conceptual", "Procedural"];
|
|
12732
|
+
var rawDifficultyLevels = ["E", "E~M", "M", "M~H", "H"];
|
|
12733
|
+
function ApproachManager() {
|
|
12734
|
+
var _a, _b, _c;
|
|
12735
|
+
const [items, setItems] = useState([]);
|
|
12736
|
+
const [bloomLevels, setBloomLevels] = useState([]);
|
|
12737
|
+
const [iSpringQuizTypeOptions, setISpringQuizTypeOptions] = useState([]);
|
|
12738
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12739
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12740
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
12741
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
12742
|
+
const [formState, setFormState] = useState({});
|
|
12743
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
12744
|
+
const [isPending, startTransition] = useTransition();
|
|
12745
|
+
const { toast: toast2 } = useToast();
|
|
12746
|
+
const fetchItems = () => {
|
|
12747
|
+
setIsLoading(true);
|
|
12748
|
+
try {
|
|
12749
|
+
const approaches = MetadataService.getApproaches();
|
|
12750
|
+
const bloomLevelsData = MetadataService.getBloomLevels();
|
|
12751
|
+
const questionTypesData = MetadataService.getQuestionTypes();
|
|
12752
|
+
setItems(approaches);
|
|
12753
|
+
setBloomLevels(bloomLevelsData);
|
|
12754
|
+
setISpringQuizTypeOptions(questionTypesData.map((qt) => ({ label: qt.name, value: qt.code })));
|
|
12755
|
+
} catch (error) {
|
|
12756
|
+
toast2({ title: "Error", description: "Failed to fetch metadata.", variant: "destructive" });
|
|
12757
|
+
} finally {
|
|
12758
|
+
setIsLoading(false);
|
|
12759
|
+
}
|
|
12760
|
+
};
|
|
12761
|
+
useEffect(() => {
|
|
12762
|
+
fetchItems();
|
|
12763
|
+
}, []);
|
|
12764
|
+
const resetForm = () => {
|
|
12765
|
+
setFormState({
|
|
12766
|
+
knowledgeDimension: "Factual",
|
|
12767
|
+
rawDifficulty: "E",
|
|
12768
|
+
bloomLevelCode: bloomLevels.length > 0 ? bloomLevels[0].code : "",
|
|
12769
|
+
iSpringQuizType: iSpringQuizTypeOptions.length > 0 ? iSpringQuizTypeOptions[0].value : "multiple_choice"
|
|
12770
|
+
});
|
|
12771
|
+
};
|
|
12772
|
+
const handleAddItem = () => {
|
|
12773
|
+
setCurrentItem(null);
|
|
12774
|
+
resetForm();
|
|
12775
|
+
setIsDialogOpen(true);
|
|
12776
|
+
};
|
|
12777
|
+
const handleEditItem = (item) => {
|
|
12778
|
+
setCurrentItem(item);
|
|
12779
|
+
setFormState({
|
|
12780
|
+
code: item.code,
|
|
12781
|
+
verbEn: item.verbEn,
|
|
12782
|
+
verbVi: item.verbVi,
|
|
12783
|
+
bloomLevelCode: item.bloomLevelCode,
|
|
12784
|
+
knowledgeDimension: item.knowledgeDimension,
|
|
12785
|
+
iSpringQuizType: item.iSpringQuizType,
|
|
12786
|
+
rawDifficulty: item.rawDifficulty,
|
|
12787
|
+
suggestContext: item.suggestContext,
|
|
12788
|
+
exampleEn: item.exampleEn,
|
|
12789
|
+
exampleVi: item.exampleVi
|
|
12790
|
+
});
|
|
12791
|
+
setIsDialogOpen(true);
|
|
12792
|
+
};
|
|
12793
|
+
const handleDeleteItem = (item) => {
|
|
12794
|
+
setItemToDelete(item);
|
|
12795
|
+
setIsAlertOpen(true);
|
|
12796
|
+
};
|
|
12797
|
+
const confirmDelete = () => {
|
|
12798
|
+
if (!itemToDelete) return;
|
|
12799
|
+
startTransition(() => {
|
|
12800
|
+
try {
|
|
12801
|
+
MetadataService.deleteApproach(itemToDelete.code);
|
|
12802
|
+
toast2({ title: "Success", description: `Approach "${itemToDelete.code}" deleted.` });
|
|
12803
|
+
fetchItems();
|
|
12804
|
+
} catch (error) {
|
|
12805
|
+
toast2({ title: "Error", description: "Failed to delete Approach.", variant: "destructive" });
|
|
12806
|
+
} finally {
|
|
12807
|
+
setIsAlertOpen(false);
|
|
12808
|
+
setItemToDelete(null);
|
|
12809
|
+
}
|
|
12810
|
+
});
|
|
12811
|
+
};
|
|
12812
|
+
const handleSubmit = () => {
|
|
12813
|
+
const { code, verbEn, verbVi } = formState;
|
|
12814
|
+
if (!(code == null ? void 0 : code.trim()) || !(verbEn == null ? void 0 : verbEn.trim()) || !(verbVi == null ? void 0 : verbVi.trim())) {
|
|
12815
|
+
toast2({ title: "Validation Error", description: "Code, Verb (EN), and Verb (VI) are required.", variant: "destructive" });
|
|
12816
|
+
return;
|
|
12817
|
+
}
|
|
12818
|
+
startTransition(() => {
|
|
12819
|
+
try {
|
|
12820
|
+
if (currentItem) {
|
|
12821
|
+
MetadataService.updateApproach(currentItem.id, formState);
|
|
12822
|
+
toast2({ title: "Success", description: "Approach updated." });
|
|
12823
|
+
} else {
|
|
12824
|
+
MetadataService.addApproach(formState);
|
|
12825
|
+
toast2({ title: "Success", description: "Approach added." });
|
|
12826
|
+
}
|
|
12827
|
+
fetchItems();
|
|
12828
|
+
setIsDialogOpen(false);
|
|
12829
|
+
} catch (error) {
|
|
12830
|
+
toast2({ title: "Error", description: error.message || "Failed to save Approach.", variant: "destructive" });
|
|
12831
|
+
}
|
|
12832
|
+
});
|
|
12833
|
+
};
|
|
12834
|
+
return /* @__PURE__ */ React53__default.createElement(Card, null, /* @__PURE__ */ React53__default.createElement(CardHeader, null, /* @__PURE__ */ React53__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React53__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React53__default.createElement(Settings2, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Approaches"), /* @__PURE__ */ React53__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React53__default.createElement(PlusCircle, { className: "mr-2 h-4 w-4" }), " Add Approach"))), /* @__PURE__ */ React53__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React53__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React53__default.createElement(Loader2, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React53__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Approaches found.") : /* @__PURE__ */ React53__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React53__default.createElement(Table2, null, /* @__PURE__ */ React53__default.createElement(TableHeader, null, /* @__PURE__ */ React53__default.createElement(TableRow, null, /* @__PURE__ */ React53__default.createElement(TableHead, null, "Approach ID"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Verb (VI)"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "Cognitive Level"), /* @__PURE__ */ React53__default.createElement(TableHead, null, "iSpring Type"), /* @__PURE__ */ React53__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React53__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React53__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React53__default.createElement(TableCell, { className: "font-medium" }, item.code), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.verbVi), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.bloomLevelCode), /* @__PURE__ */ React53__default.createElement(TableCell, null, item.iSpringQuizType), /* @__PURE__ */ React53__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React53__default.createElement(Edit3, { className: "h-4 w-4" })), /* @__PURE__ */ React53__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React53__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React53__default.createElement(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React53__default.createElement(DialogContent, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React53__default.createElement(DialogHeader, null, /* @__PURE__ */ React53__default.createElement(DialogTitle, null, currentItem ? "Edit Approach" : "Add New Approach")), /* @__PURE__ */ React53__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "code" }, "Approach Code"), /* @__PURE__ */ React53__default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { code: e.target.value.toUpperCase() })), placeholder: "e.g., REM-FAC-IDT-MCQ", disabled: !!currentItem })), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "verbEn" }, "Verb (English)"), /* @__PURE__ */ React53__default.createElement(Input, { id: "verbEn", value: formState.verbEn || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { verbEn: e.target.value })), placeholder: "e.g., Identify" }))), /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "verbVi" }, "Verb (Vietnamese)"), /* @__PURE__ */ React53__default.createElement(Input, { id: "verbVi", value: formState.verbVi || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { verbVi: e.target.value })), placeholder: "e.g., Nh\u1EADn d\u1EA1ng" })), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "bloomLevelCode" }, "Cognitive Level"), /* @__PURE__ */ React53__default.createElement(Select, { value: formState.bloomLevelCode, onValueChange: (v) => setFormState((s) => __spreadProps(__spreadValues({}, s), { bloomLevelCode: v })) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, null)), /* @__PURE__ */ React53__default.createElement(SelectContent, null, bloomLevels.map((level) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: level.code, value: level.code }, level.name)))))), /* @__PURE__ */ React53__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4" }, /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "knowledgeDimension" }, "Knowledge Dimension"), /* @__PURE__ */ React53__default.createElement(Select, { value: formState.knowledgeDimension, onValueChange: (v) => setFormState((s) => __spreadProps(__spreadValues({}, s), { knowledgeDimension: v })) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, null)), /* @__PURE__ */ React53__default.createElement(SelectContent, null, knowledgeDimensions.map((kd) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: kd, value: kd }, kd))))), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "iSpringQuizType" }, "iSpring Quiz Type"), /* @__PURE__ */ React53__default.createElement(Select, { value: formState.iSpringQuizType, onValueChange: (v) => setFormState((s) => __spreadProps(__spreadValues({}, s), { iSpringQuizType: v })) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, null)), /* @__PURE__ */ React53__default.createElement(SelectContent, null, iSpringQuizTypeOptions.map((qt) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: qt.value, value: qt.value }, qt.label))))), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "rawDifficulty" }, "Raw Difficulty"), /* @__PURE__ */ React53__default.createElement(Select, { value: formState.rawDifficulty, onValueChange: (v) => setFormState((s) => __spreadProps(__spreadValues({}, s), { rawDifficulty: v })) }, /* @__PURE__ */ React53__default.createElement(SelectTrigger, null, /* @__PURE__ */ React53__default.createElement(SelectValue, null)), /* @__PURE__ */ React53__default.createElement(SelectContent, null, rawDifficultyLevels.map((rd) => /* @__PURE__ */ React53__default.createElement(SelectItem, { key: rd, value: rd }, rd)))))), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "suggestContext" }, "Suggest Context (comma-separated codes)"), /* @__PURE__ */ React53__default.createElement(Input, { id: "suggestContext", value: formState.suggestContext || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { suggestContext: e.target.value })), placeholder: "e.g., A, B, D, G, H" })), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "exampleEn" }, "Example (English)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "exampleEn", value: formState.exampleEn || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { exampleEn: e.target.value })), placeholder: "English example prompt..." })), /* @__PURE__ */ React53__default.createElement("div", null, /* @__PURE__ */ React53__default.createElement(Label, { htmlFor: "exampleVi" }, "Example (Vietnamese)"), /* @__PURE__ */ React53__default.createElement(Textarea, { id: "exampleVi", value: formState.exampleVi || "", onChange: (e) => setFormState((s) => __spreadProps(__spreadValues({}, s), { exampleVi: e.target.value })), placeholder: "Vietnamese example prompt..." }))), /* @__PURE__ */ React53__default.createElement(DialogFooter, null, /* @__PURE__ */ React53__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !((_a = formState.code) == null ? void 0 : _a.trim()) || !((_b = formState.verbEn) == null ? void 0 : _b.trim()) || !((_c = formState.verbVi) == null ? void 0 : _c.trim()) }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React53__default.createElement(AlertDialog, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React53__default.createElement(AlertDialogContent, null, /* @__PURE__ */ React53__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React53__default.createElement(AlertDialogTitle, null, "Are you sure?"), /* @__PURE__ */ React53__default.createElement(AlertDialogDescription, null, 'This will permanently delete "', itemToDelete == null ? void 0 : itemToDelete.code, '".')), /* @__PURE__ */ React53__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React53__default.createElement(AlertDialogCancel, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React53__default.createElement(AlertDialogAction, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React53__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
12835
|
+
}
|
|
12836
|
+
|
|
12837
|
+
// src/react-ui/components/metadata/MetadataTabs.tsx
|
|
12838
|
+
function MetadataTabs() {
|
|
12839
|
+
const tabGridCols = "grid-cols-3 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-9";
|
|
12840
|
+
return /* @__PURE__ */ React53__default.createElement(Tabs, { defaultValue: "subjects", className: "w-full" }, /* @__PURE__ */ React53__default.createElement(TabsList, { className: `grid w-full ${tabGridCols}` }, /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "subjects" }, "Subjects"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "gradeLevels" }, "Grade Levels"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "topics" }, "Topics"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "categories" }, "Categories"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "bloomLevels" }, "Bloom Levels"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "questionTypes" }, "Question Types"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "learningObjectives" }, "Learning Objectives"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "contexts" }, "Contexts"), /* @__PURE__ */ React53__default.createElement(TabsTrigger, { value: "approaches" }, "Approaches")), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "subjects" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(SubjectManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "gradeLevels" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(GradeLevelManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "topics" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(TopicManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "categories" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(CategoryManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "bloomLevels" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(BloomLevelManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "questionTypes" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(QuestionTypeManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "learningObjectives" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(LearningObjectiveManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "contexts" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(ContextManager, null))), /* @__PURE__ */ React53__default.createElement(TabsContent, { value: "approaches" }, /* @__PURE__ */ React53__default.createElement(Suspense, { fallback: /* @__PURE__ */ React53__default.createElement(Skeleton, { className: "h-64 w-full" }) }, /* @__PURE__ */ React53__default.createElement(ApproachManager, null))));
|
|
12841
|
+
}
|
|
11094
12842
|
var ToastProvider = ToastPrimitives.Provider;
|
|
11095
12843
|
var ToastViewport = React53.forwardRef((_a, ref) => {
|
|
11096
12844
|
var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
|
|
@@ -11193,4 +12941,4 @@ function Toaster() {
|
|
|
11193
12941
|
}), /* @__PURE__ */ React.createElement(ToastViewport, null));
|
|
11194
12942
|
}
|
|
11195
12943
|
|
|
11196
|
-
export { AIFullQuizGeneratorModal, AIQuestionGeneratorModal, APIKeyService, AchievementService, EditQuestionModal, GEMINI_API_KEY_SERVICE_NAME, ImportQuestionsModal, KnowledgeCardService, PracticeHistoryService, QuestionImportService, QuizAuthoringTool, QuizEditorService, QuizEngine, QuoteService, SCORMExportModal, SCORMService, Toaster, UserConfigService, assessAndMapDocument, cn, emptyQuiz, exportQuizAsSCORMZip, generateFillInTheBlanksQuestion, generateLauncherHTML, generateLearningAnalysis, generateMCQQuestion, generateMRQQuestion, generateMatchingQuestion, generateMotivationalQuote, generateNumericQuestion, generatePracticeSuggestion, generateQuestionsFromQuizPlan, generateQuizFromText, generateQuizPlan, generateQuizReview, generateSCORMManifest, generateSequenceQuestion, generateShortAnswerQuestion, generateSingleKnowledgeCard, generateTrueFalseQuestion, generateUniqueId, planKnowledgeCards, sampleQuiz, toast, useToast };
|
|
12944
|
+
export { AIFullQuizGeneratorModal, AIQuestionGeneratorModal, APIKeyManagerModal, APIKeyService, AchievementService, ApproachManager, BloomLevelManager, CategoryManager, ContextManager, EditQuestionModal, GEMINI_API_KEY_SERVICE_NAME, GradeLevelManager, ImportQuestionsModal, KnowledgeCardService, LearningObjectiveManager, MetadataService, MetadataTabs, PracticeHistoryService, QuestionBankService, QuestionFilters, QuestionFormDialog, QuestionImportService, QuestionList, QuestionTypeManager, QuizAuthoringTool, QuizEditorService, QuizEngine, QuoteService, SCORMExportModal, SCORMService, SubjectManager, Toaster, TopicManager, UserConfigService, assessAndMapDocument, cn, emptyQuiz, exportQuizAsSCORMZip, generateFillInTheBlanksQuestion, generateLauncherHTML, generateLearningAnalysis, generateMCQQuestion, generateMRQQuestion, generateMatchingQuestion, generateMotivationalQuote, generateNumericQuestion, generatePracticeSuggestion, generateQuestionsFromQuizPlan, generateQuizFromText, generateQuizPlan, generateQuizReview, generateSCORMManifest, generateSequenceQuestion, generateShortAnswerQuestion, generateSingleKnowledgeCard, generateTrueFalseQuestion, generateUniqueId, planKnowledgeCards, sampleQuiz, toast, useToast };
|