@thanh01.pmt/interactive-quiz-kit 1.0.69 → 1.0.71

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/ai.cjs CHANGED
@@ -2496,6 +2496,7 @@ function validateConsecutiveTypes(quizPlan) {
2496
2496
 
2497
2497
  // src/services/TopicDataService.ts
2498
2498
  var TopicDataService = class {
2499
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
2499
2500
  static saveData(data) {
2500
2501
  try {
2501
2502
  if (typeof window === "undefined") return;
@@ -2540,47 +2541,41 @@ var TopicDataService = class {
2540
2541
  }
2541
2542
  const headerLine = lines.shift();
2542
2543
  const headers = headerLine.split(" ").map((h) => h.trim());
2543
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h, i) => h === headers[i])) {
2544
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
2545
- return { data: [], errors: [errorMsg] };
2546
- }
2544
+ const headerMap = headers.map((h) => this.HEADER_MAP[h] || null);
2547
2545
  const data = [];
2548
2546
  const errors = [];
2549
2547
  lines.forEach((line, index) => {
2550
- const values = line.split(" ").map((v) => v.trim());
2551
- if (values.length !== this.EXPECTED_HEADERS.length) {
2552
- errors.push(`Line ${index + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
2548
+ const values = line.split(" ");
2549
+ const rowObject = {};
2550
+ headerMap.forEach((propName, i) => {
2551
+ if (propName) {
2552
+ const value = values[i]?.trim() || "";
2553
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
2554
+ rowObject[propName] = value.split(",").map((s) => s.trim()).filter(Boolean);
2555
+ } else {
2556
+ rowObject[propName] = value;
2557
+ }
2558
+ }
2559
+ });
2560
+ if (!rowObject.code) {
2561
+ errors.push(`Line ${index + 2}: Missing required value for 'LO ID'.`);
2553
2562
  return;
2554
2563
  }
2555
- const [
2556
- loId,
2557
- name,
2558
- loDescription,
2559
- subject,
2560
- category,
2561
- topic,
2562
- keywordsStr,
2563
- grade,
2564
- stemElementsStr,
2565
- bloomLevelsStr
2566
- ] = values;
2567
- if (!loId || !loDescription || !subject || !category || !topic) {
2568
- errors.push(`Line ${index + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
2569
- return;
2564
+ if (!rowObject.name) {
2565
+ rowObject.name = rowObject.code;
2570
2566
  }
2571
2567
  const learningObjective = {
2572
2568
  id: generateUniqueId("lo_"),
2573
- code: loId,
2574
- name,
2575
- description: loDescription,
2576
- // Can be the same as name or enhanced later
2577
- subject,
2578
- category,
2579
- topic,
2580
- grade,
2581
- keywords: keywordsStr.split(",").map((k) => k.trim()).filter(Boolean),
2582
- stemElements: stemElementsStr.split(",").map((s) => s.trim()).filter(Boolean),
2583
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b) => b.trim()).filter(Boolean)
2569
+ code: rowObject.code,
2570
+ name: rowObject.name,
2571
+ description: rowObject.description,
2572
+ subject: rowObject.subject || "",
2573
+ category: rowObject.category || "",
2574
+ topic: rowObject.topic || "",
2575
+ grade: rowObject.grade || "",
2576
+ keywords: rowObject.keywords || [],
2577
+ stemElements: rowObject.stemElements || [],
2578
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
2584
2579
  };
2585
2580
  data.push(learningObjective);
2586
2581
  });
@@ -2608,17 +2603,20 @@ var TopicDataService = class {
2608
2603
  }
2609
2604
  };
2610
2605
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
2611
- TopicDataService.EXPECTED_HEADERS = [
2612
- "LO ID",
2613
- "LO Description",
2614
- "Subject",
2615
- "Category",
2616
- "Topic",
2617
- "Keywords",
2618
- "Grade",
2619
- "STEM Element(s)",
2620
- "Bloom\u2019s Level(s) Guideline"
2621
- ];
2606
+ // Define a map for flexible header mapping
2607
+ TopicDataService.HEADER_MAP = {
2608
+ "LO ID": "code",
2609
+ "LO Name": "name",
2610
+ // Ready for future addition
2611
+ "LO Description": "description",
2612
+ "Subject": "subject",
2613
+ "Category": "category",
2614
+ "Topic": "topic",
2615
+ "Keywords": "keywords",
2616
+ "Grade": "grade",
2617
+ "STEM Element(s)": "stemElements",
2618
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
2619
+ };
2622
2620
 
2623
2621
  // src/ai/flows/generate-questions-from-quiz-plan.ts
2624
2622
  var MAX_ATTEMPTS = 3;
package/dist/ai.mjs CHANGED
@@ -2494,6 +2494,7 @@ function validateConsecutiveTypes(quizPlan) {
2494
2494
 
2495
2495
  // src/services/TopicDataService.ts
2496
2496
  var TopicDataService = class {
2497
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
2497
2498
  static saveData(data) {
2498
2499
  try {
2499
2500
  if (typeof window === "undefined") return;
@@ -2538,47 +2539,41 @@ var TopicDataService = class {
2538
2539
  }
2539
2540
  const headerLine = lines.shift();
2540
2541
  const headers = headerLine.split(" ").map((h) => h.trim());
2541
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h, i) => h === headers[i])) {
2542
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
2543
- return { data: [], errors: [errorMsg] };
2544
- }
2542
+ const headerMap = headers.map((h) => this.HEADER_MAP[h] || null);
2545
2543
  const data = [];
2546
2544
  const errors = [];
2547
2545
  lines.forEach((line, index) => {
2548
- const values = line.split(" ").map((v) => v.trim());
2549
- if (values.length !== this.EXPECTED_HEADERS.length) {
2550
- errors.push(`Line ${index + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
2546
+ const values = line.split(" ");
2547
+ const rowObject = {};
2548
+ headerMap.forEach((propName, i) => {
2549
+ if (propName) {
2550
+ const value = values[i]?.trim() || "";
2551
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
2552
+ rowObject[propName] = value.split(",").map((s) => s.trim()).filter(Boolean);
2553
+ } else {
2554
+ rowObject[propName] = value;
2555
+ }
2556
+ }
2557
+ });
2558
+ if (!rowObject.code) {
2559
+ errors.push(`Line ${index + 2}: Missing required value for 'LO ID'.`);
2551
2560
  return;
2552
2561
  }
2553
- const [
2554
- loId,
2555
- name,
2556
- loDescription,
2557
- subject,
2558
- category,
2559
- topic,
2560
- keywordsStr,
2561
- grade,
2562
- stemElementsStr,
2563
- bloomLevelsStr
2564
- ] = values;
2565
- if (!loId || !loDescription || !subject || !category || !topic) {
2566
- errors.push(`Line ${index + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
2567
- return;
2562
+ if (!rowObject.name) {
2563
+ rowObject.name = rowObject.code;
2568
2564
  }
2569
2565
  const learningObjective = {
2570
2566
  id: generateUniqueId("lo_"),
2571
- code: loId,
2572
- name,
2573
- description: loDescription,
2574
- // Can be the same as name or enhanced later
2575
- subject,
2576
- category,
2577
- topic,
2578
- grade,
2579
- keywords: keywordsStr.split(",").map((k) => k.trim()).filter(Boolean),
2580
- stemElements: stemElementsStr.split(",").map((s) => s.trim()).filter(Boolean),
2581
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b) => b.trim()).filter(Boolean)
2567
+ code: rowObject.code,
2568
+ name: rowObject.name,
2569
+ description: rowObject.description,
2570
+ subject: rowObject.subject || "",
2571
+ category: rowObject.category || "",
2572
+ topic: rowObject.topic || "",
2573
+ grade: rowObject.grade || "",
2574
+ keywords: rowObject.keywords || [],
2575
+ stemElements: rowObject.stemElements || [],
2576
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
2582
2577
  };
2583
2578
  data.push(learningObjective);
2584
2579
  });
@@ -2606,17 +2601,20 @@ var TopicDataService = class {
2606
2601
  }
2607
2602
  };
2608
2603
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
2609
- TopicDataService.EXPECTED_HEADERS = [
2610
- "LO ID",
2611
- "LO Description",
2612
- "Subject",
2613
- "Category",
2614
- "Topic",
2615
- "Keywords",
2616
- "Grade",
2617
- "STEM Element(s)",
2618
- "Bloom\u2019s Level(s) Guideline"
2619
- ];
2604
+ // Define a map for flexible header mapping
2605
+ TopicDataService.HEADER_MAP = {
2606
+ "LO ID": "code",
2607
+ "LO Name": "name",
2608
+ // Ready for future addition
2609
+ "LO Description": "description",
2610
+ "Subject": "subject",
2611
+ "Category": "category",
2612
+ "Topic": "topic",
2613
+ "Keywords": "keywords",
2614
+ "Grade": "grade",
2615
+ "STEM Element(s)": "stemElements",
2616
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
2617
+ };
2620
2618
 
2621
2619
  // src/ai/flows/generate-questions-from-quiz-plan.ts
2622
2620
  var MAX_ATTEMPTS = 3;
@@ -8832,6 +8832,7 @@ init_react_shim();
8832
8832
  // src/services/TopicDataService.ts
8833
8833
  init_react_shim();
8834
8834
  var TopicDataService = class {
8835
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
8835
8836
  static saveData(data) {
8836
8837
  try {
8837
8838
  if (typeof window === "undefined") return;
@@ -8876,47 +8877,41 @@ var TopicDataService = class {
8876
8877
  }
8877
8878
  const headerLine = lines.shift();
8878
8879
  const headers = headerLine.split(" ").map((h2) => h2.trim());
8879
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h2, i) => h2 === headers[i])) {
8880
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
8881
- return { data: [], errors: [errorMsg] };
8882
- }
8880
+ const headerMap = headers.map((h2) => this.HEADER_MAP[h2] || null);
8883
8881
  const data = [];
8884
8882
  const errors2 = [];
8885
8883
  lines.forEach((line, index3) => {
8886
- const values = line.split(" ").map((v) => v.trim());
8887
- if (values.length !== this.EXPECTED_HEADERS.length) {
8888
- errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
8884
+ const values = line.split(" ");
8885
+ const rowObject = {};
8886
+ headerMap.forEach((propName, i) => {
8887
+ if (propName) {
8888
+ const value = values[i]?.trim() || "";
8889
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
8890
+ rowObject[propName] = value.split(",").map((s2) => s2.trim()).filter(Boolean);
8891
+ } else {
8892
+ rowObject[propName] = value;
8893
+ }
8894
+ }
8895
+ });
8896
+ if (!rowObject.code) {
8897
+ errors2.push(`Line ${index3 + 2}: Missing required value for 'LO ID'.`);
8889
8898
  return;
8890
8899
  }
8891
- const [
8892
- loId,
8893
- name3,
8894
- loDescription,
8895
- subject,
8896
- category,
8897
- topic,
8898
- keywordsStr,
8899
- grade,
8900
- stemElementsStr,
8901
- bloomLevelsStr
8902
- ] = values;
8903
- if (!loId || !loDescription || !subject || !category || !topic) {
8904
- errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
8905
- return;
8900
+ if (!rowObject.name) {
8901
+ rowObject.name = rowObject.code;
8906
8902
  }
8907
8903
  const learningObjective = {
8908
8904
  id: generateUniqueId("lo_"),
8909
- code: loId,
8910
- name: name3,
8911
- description: loDescription,
8912
- // Can be the same as name or enhanced later
8913
- subject,
8914
- category,
8915
- topic,
8916
- grade,
8917
- keywords: keywordsStr.split(",").map((k) => k.trim()).filter(Boolean),
8918
- stemElements: stemElementsStr.split(",").map((s2) => s2.trim()).filter(Boolean),
8919
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b) => b.trim()).filter(Boolean)
8905
+ code: rowObject.code,
8906
+ name: rowObject.name,
8907
+ description: rowObject.description,
8908
+ subject: rowObject.subject || "",
8909
+ category: rowObject.category || "",
8910
+ topic: rowObject.topic || "",
8911
+ grade: rowObject.grade || "",
8912
+ keywords: rowObject.keywords || [],
8913
+ stemElements: rowObject.stemElements || [],
8914
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
8920
8915
  };
8921
8916
  data.push(learningObjective);
8922
8917
  });
@@ -8944,17 +8939,20 @@ var TopicDataService = class {
8944
8939
  }
8945
8940
  };
8946
8941
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
8947
- TopicDataService.EXPECTED_HEADERS = [
8948
- "LO ID",
8949
- "LO Description",
8950
- "Subject",
8951
- "Category",
8952
- "Topic",
8953
- "Keywords",
8954
- "Grade",
8955
- "STEM Element(s)",
8956
- "Bloom\u2019s Level(s) Guideline"
8957
- ];
8942
+ // Define a map for flexible header mapping
8943
+ TopicDataService.HEADER_MAP = {
8944
+ "LO ID": "code",
8945
+ "LO Name": "name",
8946
+ // Ready for future addition
8947
+ "LO Description": "description",
8948
+ "Subject": "subject",
8949
+ "Category": "category",
8950
+ "Topic": "topic",
8951
+ "Keywords": "keywords",
8952
+ "Grade": "grade",
8953
+ "STEM Element(s)": "stemElements",
8954
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
8955
+ };
8958
8956
 
8959
8957
  // src/ai/flows/generate-questions-from-quiz-plan.ts
8960
8958
  var MAX_ATTEMPTS = 3;
@@ -76432,11 +76430,17 @@ function MetadataImportControls({ metadataName, onImport }) {
76432
76430
  const fileInputRef = React119.useRef(null);
76433
76431
  const { toast: toast2 } = useToast();
76434
76432
  const processAndImportRecords = async (records, importSource) => {
76433
+ console.log(`[ImportControls] Processing ${records.length} records from ${importSource} for ${metadataName}.`);
76435
76434
  if (records.length === 0) {
76436
76435
  toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
76437
76436
  return;
76438
76437
  }
76439
- await onImport(records);
76438
+ try {
76439
+ await onImport(records);
76440
+ } catch (error) {
76441
+ console.error(`[ImportControls] Error during onImport callback for ${metadataName}:`, error);
76442
+ toast2({ title: "Import Failed", description: `An error occurred while saving the data: ${error instanceof Error ? error.message : String(error)}`, variant: "destructive" });
76443
+ }
76440
76444
  setIsOpen(false);
76441
76445
  setJsonString("");
76442
76446
  };
@@ -76468,8 +76472,10 @@ function MetadataImportControls({ metadataName, onImport }) {
76468
76472
  return record;
76469
76473
  });
76470
76474
  }
76475
+ console.log(`[ImportControls] File parsed successfully. Found ${records.length} records.`);
76471
76476
  await processAndImportRecords(records, "file");
76472
76477
  } catch (err) {
76478
+ console.error("[ImportControls] Error parsing file:", err);
76473
76479
  toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
76474
76480
  }
76475
76481
  });
@@ -77389,6 +77395,53 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
77389
77395
  });
77390
77396
  };
77391
77397
  const handleImport = async (records) => {
77398
+ console.log(`[LO Manager] handleImport called with ${records.length} raw records.`);
77399
+ if (!onBulkAdd) {
77400
+ console.error("[LO Manager] onBulkAdd handler is not provided.");
77401
+ return;
77402
+ }
77403
+ const parseStringToArray = (input) => {
77404
+ if (Array.isArray(input)) return input;
77405
+ if (typeof input === "string") return input.split(",").map((s2) => s2.trim()).filter(Boolean);
77406
+ return [];
77407
+ };
77408
+ const validationResult = records.reduce((acc, rec) => {
77409
+ if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
77410
+ acc.valid.push({
77411
+ code: rec.code,
77412
+ name: rec.name,
77413
+ description: rec.description || rec.name,
77414
+ subject: rec.subject || "",
77415
+ subjectCode: rec.subjectCode,
77416
+ category: rec.category || "",
77417
+ categoryCode: rec.categoryCode,
77418
+ topic: rec.topic || "",
77419
+ topicCode: rec.topicCode,
77420
+ grade: rec.grade || "",
77421
+ gradeCode: rec.gradeCode,
77422
+ keywords: parseStringToArray(rec.keywords),
77423
+ stemElements: parseStringToArray(rec.stemElements),
77424
+ bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
77425
+ });
77426
+ } else {
77427
+ acc.invalidCount++;
77428
+ }
77429
+ return acc;
77430
+ }, { valid: [], invalidCount: 0 });
77431
+ console.log(`[LO Manager] Validation complete. ${validationResult.valid.length} valid records found.`);
77432
+ if (validationResult.invalidCount > 0) {
77433
+ toast2({
77434
+ title: "Import Warning",
77435
+ description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
77436
+ variant: "destructive"
77437
+ });
77438
+ }
77439
+ if (validationResult.valid.length > 0) {
77440
+ console.log("[LO Manager] Calling onBulkAdd prop with validated data...");
77441
+ await onBulkAdd(validationResult.valid);
77442
+ } else {
77443
+ console.log("[LO Manager] No valid records to import.");
77444
+ }
77392
77445
  };
77393
77446
  return /* @__PURE__ */ React119__namespace.default.createElement(Card, null, /* @__PURE__ */ React119__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React119__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React119__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React119__namespace.default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React119__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React119__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React119__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React119__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React119__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React119__namespace.default.createElement(Table2, null, /* @__PURE__ */ React119__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React119__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React119__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React119__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React119__namespace.default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React119__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React119__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React119__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React119__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React119__namespace.default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React119__namespace.default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React119__namespace.default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React119__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React119__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React119__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React119__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React119__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React119__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React119__namespace.default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React119__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e2) => handleFormChange("code", e2.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e2) => handleFormChange("name", e2.target.value) }))), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "subject" }, "Subject Name"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "subject", value: formState.subject || "", onChange: (e2) => handleFormChange("subject", e2.target.value) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React119__namespace.default.createElement(EditableCombobox, { options: subjects.map((s2) => ({ value: s2.code, label: s2.name })), value: formState.subjectCode || "", onChange: (val) => handleFormChange("subjectCode", val), placeholder: "Select a subject..." }))), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "category" }, "Category Name"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "category", value: formState.category || "", onChange: (e2) => handleFormChange("category", e2.target.value) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e2) => handleFormChange("categoryCode", e2.target.value) }))), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "topic" }, "Topic Name"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "topic", value: formState.topic || "", onChange: (e2) => handleFormChange("topic", e2.target.value) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e2) => handleFormChange("topicCode", e2.target.value) }))), /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "grade" }, "Grade Name"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "grade", value: formState.grade || "", onChange: (e2) => handleFormChange("grade", e2.target.value) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React119__namespace.default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e2) => handleFormChange("gradeCode", e2.target.value) }))), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React119__namespace.default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e2) => handleFormChange("keywords", e2.target.value.split(",").map((s2) => s2.trim())) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "stemElements" }, "STEM Elements (comma-separated)"), /* @__PURE__ */ React119__namespace.default.createElement(Textarea, { id: "stemElements", value: formState.stemElements?.join(", ") || "", onChange: (e2) => handleFormChange("stemElements", e2.target.value.split(",").map((s2) => s2.trim())) })), /* @__PURE__ */ React119__namespace.default.createElement("div", null, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "bloomLevelsGuideline" }, "Bloom's Guideline (comma-separated)"), /* @__PURE__ */ React119__namespace.default.createElement(Textarea, { id: "bloomLevelsGuideline", value: formState.bloomLevelsGuideline?.join(", ") || "", onChange: (e2) => handleFormChange("bloomLevelsGuideline", e2.target.value.split(",").map((s2) => s2.trim())) }))), /* @__PURE__ */ React119__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React119__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React119__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React119__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React119__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React119__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React119__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
77394
77447
  }
@@ -8767,6 +8767,7 @@ init_react_shim();
8767
8767
  // src/services/TopicDataService.ts
8768
8768
  init_react_shim();
8769
8769
  var TopicDataService = class {
8770
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
8770
8771
  static saveData(data) {
8771
8772
  try {
8772
8773
  if (typeof window === "undefined") return;
@@ -8811,47 +8812,41 @@ var TopicDataService = class {
8811
8812
  }
8812
8813
  const headerLine = lines.shift();
8813
8814
  const headers = headerLine.split(" ").map((h2) => h2.trim());
8814
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h2, i) => h2 === headers[i])) {
8815
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
8816
- return { data: [], errors: [errorMsg] };
8817
- }
8815
+ const headerMap = headers.map((h2) => this.HEADER_MAP[h2] || null);
8818
8816
  const data = [];
8819
8817
  const errors2 = [];
8820
8818
  lines.forEach((line, index3) => {
8821
- const values = line.split(" ").map((v) => v.trim());
8822
- if (values.length !== this.EXPECTED_HEADERS.length) {
8823
- errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
8819
+ const values = line.split(" ");
8820
+ const rowObject = {};
8821
+ headerMap.forEach((propName, i) => {
8822
+ if (propName) {
8823
+ const value = values[i]?.trim() || "";
8824
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
8825
+ rowObject[propName] = value.split(",").map((s2) => s2.trim()).filter(Boolean);
8826
+ } else {
8827
+ rowObject[propName] = value;
8828
+ }
8829
+ }
8830
+ });
8831
+ if (!rowObject.code) {
8832
+ errors2.push(`Line ${index3 + 2}: Missing required value for 'LO ID'.`);
8824
8833
  return;
8825
8834
  }
8826
- const [
8827
- loId,
8828
- name3,
8829
- loDescription,
8830
- subject,
8831
- category,
8832
- topic,
8833
- keywordsStr,
8834
- grade,
8835
- stemElementsStr,
8836
- bloomLevelsStr
8837
- ] = values;
8838
- if (!loId || !loDescription || !subject || !category || !topic) {
8839
- errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
8840
- return;
8835
+ if (!rowObject.name) {
8836
+ rowObject.name = rowObject.code;
8841
8837
  }
8842
8838
  const learningObjective = {
8843
8839
  id: generateUniqueId("lo_"),
8844
- code: loId,
8845
- name: name3,
8846
- description: loDescription,
8847
- // Can be the same as name or enhanced later
8848
- subject,
8849
- category,
8850
- topic,
8851
- grade,
8852
- keywords: keywordsStr.split(",").map((k) => k.trim()).filter(Boolean),
8853
- stemElements: stemElementsStr.split(",").map((s2) => s2.trim()).filter(Boolean),
8854
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b) => b.trim()).filter(Boolean)
8840
+ code: rowObject.code,
8841
+ name: rowObject.name,
8842
+ description: rowObject.description,
8843
+ subject: rowObject.subject || "",
8844
+ category: rowObject.category || "",
8845
+ topic: rowObject.topic || "",
8846
+ grade: rowObject.grade || "",
8847
+ keywords: rowObject.keywords || [],
8848
+ stemElements: rowObject.stemElements || [],
8849
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
8855
8850
  };
8856
8851
  data.push(learningObjective);
8857
8852
  });
@@ -8879,17 +8874,20 @@ var TopicDataService = class {
8879
8874
  }
8880
8875
  };
8881
8876
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
8882
- TopicDataService.EXPECTED_HEADERS = [
8883
- "LO ID",
8884
- "LO Description",
8885
- "Subject",
8886
- "Category",
8887
- "Topic",
8888
- "Keywords",
8889
- "Grade",
8890
- "STEM Element(s)",
8891
- "Bloom\u2019s Level(s) Guideline"
8892
- ];
8877
+ // Define a map for flexible header mapping
8878
+ TopicDataService.HEADER_MAP = {
8879
+ "LO ID": "code",
8880
+ "LO Name": "name",
8881
+ // Ready for future addition
8882
+ "LO Description": "description",
8883
+ "Subject": "subject",
8884
+ "Category": "category",
8885
+ "Topic": "topic",
8886
+ "Keywords": "keywords",
8887
+ "Grade": "grade",
8888
+ "STEM Element(s)": "stemElements",
8889
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
8890
+ };
8893
8891
 
8894
8892
  // src/ai/flows/generate-questions-from-quiz-plan.ts
8895
8893
  var MAX_ATTEMPTS = 3;
@@ -76367,11 +76365,17 @@ function MetadataImportControls({ metadataName, onImport }) {
76367
76365
  const fileInputRef = useRef(null);
76368
76366
  const { toast: toast2 } = useToast();
76369
76367
  const processAndImportRecords = async (records, importSource) => {
76368
+ console.log(`[ImportControls] Processing ${records.length} records from ${importSource} for ${metadataName}.`);
76370
76369
  if (records.length === 0) {
76371
76370
  toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
76372
76371
  return;
76373
76372
  }
76374
- await onImport(records);
76373
+ try {
76374
+ await onImport(records);
76375
+ } catch (error) {
76376
+ console.error(`[ImportControls] Error during onImport callback for ${metadataName}:`, error);
76377
+ toast2({ title: "Import Failed", description: `An error occurred while saving the data: ${error instanceof Error ? error.message : String(error)}`, variant: "destructive" });
76378
+ }
76375
76379
  setIsOpen(false);
76376
76380
  setJsonString("");
76377
76381
  };
@@ -76403,8 +76407,10 @@ function MetadataImportControls({ metadataName, onImport }) {
76403
76407
  return record;
76404
76408
  });
76405
76409
  }
76410
+ console.log(`[ImportControls] File parsed successfully. Found ${records.length} records.`);
76406
76411
  await processAndImportRecords(records, "file");
76407
76412
  } catch (err) {
76413
+ console.error("[ImportControls] Error parsing file:", err);
76408
76414
  toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
76409
76415
  }
76410
76416
  });
@@ -77324,6 +77330,53 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
77324
77330
  });
77325
77331
  };
77326
77332
  const handleImport = async (records) => {
77333
+ console.log(`[LO Manager] handleImport called with ${records.length} raw records.`);
77334
+ if (!onBulkAdd) {
77335
+ console.error("[LO Manager] onBulkAdd handler is not provided.");
77336
+ return;
77337
+ }
77338
+ const parseStringToArray = (input) => {
77339
+ if (Array.isArray(input)) return input;
77340
+ if (typeof input === "string") return input.split(",").map((s2) => s2.trim()).filter(Boolean);
77341
+ return [];
77342
+ };
77343
+ const validationResult = records.reduce((acc, rec) => {
77344
+ if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
77345
+ acc.valid.push({
77346
+ code: rec.code,
77347
+ name: rec.name,
77348
+ description: rec.description || rec.name,
77349
+ subject: rec.subject || "",
77350
+ subjectCode: rec.subjectCode,
77351
+ category: rec.category || "",
77352
+ categoryCode: rec.categoryCode,
77353
+ topic: rec.topic || "",
77354
+ topicCode: rec.topicCode,
77355
+ grade: rec.grade || "",
77356
+ gradeCode: rec.gradeCode,
77357
+ keywords: parseStringToArray(rec.keywords),
77358
+ stemElements: parseStringToArray(rec.stemElements),
77359
+ bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
77360
+ });
77361
+ } else {
77362
+ acc.invalidCount++;
77363
+ }
77364
+ return acc;
77365
+ }, { valid: [], invalidCount: 0 });
77366
+ console.log(`[LO Manager] Validation complete. ${validationResult.valid.length} valid records found.`);
77367
+ if (validationResult.invalidCount > 0) {
77368
+ toast2({
77369
+ title: "Import Warning",
77370
+ description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
77371
+ variant: "destructive"
77372
+ });
77373
+ }
77374
+ if (validationResult.valid.length > 0) {
77375
+ console.log("[LO Manager] Calling onBulkAdd prop with validated data...");
77376
+ await onBulkAdd(validationResult.valid);
77377
+ } else {
77378
+ console.log("[LO Manager] No valid records to import.");
77379
+ }
77327
77380
  };
77328
77381
  return /* @__PURE__ */ React119__default.createElement(Card, null, /* @__PURE__ */ React119__default.createElement(CardHeader, null, /* @__PURE__ */ React119__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React119__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React119__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React119__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React119__default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React119__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React119__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React119__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React119__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React119__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React119__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React119__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React119__default.createElement(Table2, null, /* @__PURE__ */ React119__default.createElement(TableHeader, null, /* @__PURE__ */ React119__default.createElement(TableRow, null, /* @__PURE__ */ React119__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React119__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React119__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React119__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React119__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React119__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React119__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React119__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React119__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React119__default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React119__default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React119__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React119__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React119__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React119__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React119__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React119__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React119__default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React119__default.createElement(DialogHeader, null, /* @__PURE__ */ React119__default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React119__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React119__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React119__default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e2) => handleFormChange("code", e2.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React119__default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e2) => handleFormChange("name", e2.target.value) }))), /* @__PURE__ */ React119__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "subject" }, "Subject Name"), /* @__PURE__ */ React119__default.createElement(Input, { id: "subject", value: formState.subject || "", onChange: (e2) => handleFormChange("subject", e2.target.value) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React119__default.createElement(EditableCombobox, { options: subjects.map((s2) => ({ value: s2.code, label: s2.name })), value: formState.subjectCode || "", onChange: (val) => handleFormChange("subjectCode", val), placeholder: "Select a subject..." }))), /* @__PURE__ */ React119__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "category" }, "Category Name"), /* @__PURE__ */ React119__default.createElement(Input, { id: "category", value: formState.category || "", onChange: (e2) => handleFormChange("category", e2.target.value) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React119__default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e2) => handleFormChange("categoryCode", e2.target.value) }))), /* @__PURE__ */ React119__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "topic" }, "Topic Name"), /* @__PURE__ */ React119__default.createElement(Input, { id: "topic", value: formState.topic || "", onChange: (e2) => handleFormChange("topic", e2.target.value) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React119__default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e2) => handleFormChange("topicCode", e2.target.value) }))), /* @__PURE__ */ React119__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "grade" }, "Grade Name"), /* @__PURE__ */ React119__default.createElement(Input, { id: "grade", value: formState.grade || "", onChange: (e2) => handleFormChange("grade", e2.target.value) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React119__default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e2) => handleFormChange("gradeCode", e2.target.value) }))), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React119__default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e2) => handleFormChange("keywords", e2.target.value.split(",").map((s2) => s2.trim())) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "stemElements" }, "STEM Elements (comma-separated)"), /* @__PURE__ */ React119__default.createElement(Textarea, { id: "stemElements", value: formState.stemElements?.join(", ") || "", onChange: (e2) => handleFormChange("stemElements", e2.target.value.split(",").map((s2) => s2.trim())) })), /* @__PURE__ */ React119__default.createElement("div", null, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "bloomLevelsGuideline" }, "Bloom's Guideline (comma-separated)"), /* @__PURE__ */ React119__default.createElement(Textarea, { id: "bloomLevelsGuideline", value: formState.bloomLevelsGuideline?.join(", ") || "", onChange: (e2) => handleFormChange("bloomLevelsGuideline", e2.target.value.split(",").map((s2) => s2.trim())) }))), /* @__PURE__ */ React119__default.createElement(DialogFooter, null, /* @__PURE__ */ React119__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React119__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React119__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React119__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React119__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React119__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React119__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React119__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React119__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React119__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React119__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React119__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
77329
77382
  }
package/dist/react-ui.cjs CHANGED
@@ -101917,6 +101917,7 @@ init_react_shim();
101917
101917
  // src/services/TopicDataService.ts
101918
101918
  init_react_shim();
101919
101919
  var TopicDataService = class {
101920
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
101920
101921
  static saveData(data) {
101921
101922
  try {
101922
101923
  if (typeof window === "undefined") return;
@@ -101961,47 +101962,41 @@ var TopicDataService = class {
101961
101962
  }
101962
101963
  const headerLine = lines.shift();
101963
101964
  const headers = headerLine.split(" ").map((h3) => h3.trim());
101964
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
101965
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
101966
- return { data: [], errors: [errorMsg] };
101967
- }
101965
+ const headerMap = headers.map((h3) => this.HEADER_MAP[h3] || null);
101968
101966
  const data = [];
101969
101967
  const errors2 = [];
101970
101968
  lines.forEach((line, index3) => {
101971
- const values = line.split(" ").map((v) => v.trim());
101972
- if (values.length !== this.EXPECTED_HEADERS.length) {
101973
- errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
101969
+ const values = line.split(" ");
101970
+ const rowObject = {};
101971
+ headerMap.forEach((propName, i2) => {
101972
+ if (propName) {
101973
+ const value = values[i2]?.trim() || "";
101974
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
101975
+ rowObject[propName] = value.split(",").map((s4) => s4.trim()).filter(Boolean);
101976
+ } else {
101977
+ rowObject[propName] = value;
101978
+ }
101979
+ }
101980
+ });
101981
+ if (!rowObject.code) {
101982
+ errors2.push(`Line ${index3 + 2}: Missing required value for 'LO ID'.`);
101974
101983
  return;
101975
101984
  }
101976
- const [
101977
- loId,
101978
- name3,
101979
- loDescription,
101980
- subject,
101981
- category,
101982
- topic,
101983
- keywordsStr,
101984
- grade,
101985
- stemElementsStr,
101986
- bloomLevelsStr
101987
- ] = values;
101988
- if (!loId || !loDescription || !subject || !category || !topic) {
101989
- errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
101990
- return;
101985
+ if (!rowObject.name) {
101986
+ rowObject.name = rowObject.code;
101991
101987
  }
101992
101988
  const learningObjective = {
101993
101989
  id: generateUniqueId("lo_"),
101994
- code: loId,
101995
- name: name3,
101996
- description: loDescription,
101997
- // Can be the same as name or enhanced later
101998
- subject,
101999
- category,
102000
- topic,
102001
- grade,
102002
- keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
102003
- stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
102004
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
101990
+ code: rowObject.code,
101991
+ name: rowObject.name,
101992
+ description: rowObject.description,
101993
+ subject: rowObject.subject || "",
101994
+ category: rowObject.category || "",
101995
+ topic: rowObject.topic || "",
101996
+ grade: rowObject.grade || "",
101997
+ keywords: rowObject.keywords || [],
101998
+ stemElements: rowObject.stemElements || [],
101999
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
102005
102000
  };
102006
102001
  data.push(learningObjective);
102007
102002
  });
@@ -102029,17 +102024,20 @@ var TopicDataService = class {
102029
102024
  }
102030
102025
  };
102031
102026
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
102032
- TopicDataService.EXPECTED_HEADERS = [
102033
- "LO ID",
102034
- "LO Description",
102035
- "Subject",
102036
- "Category",
102037
- "Topic",
102038
- "Keywords",
102039
- "Grade",
102040
- "STEM Element(s)",
102041
- "Bloom\u2019s Level(s) Guideline"
102042
- ];
102027
+ // Define a map for flexible header mapping
102028
+ TopicDataService.HEADER_MAP = {
102029
+ "LO ID": "code",
102030
+ "LO Name": "name",
102031
+ // Ready for future addition
102032
+ "LO Description": "description",
102033
+ "Subject": "subject",
102034
+ "Category": "category",
102035
+ "Topic": "topic",
102036
+ "Keywords": "keywords",
102037
+ "Grade": "grade",
102038
+ "STEM Element(s)": "stemElements",
102039
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
102040
+ };
102043
102041
 
102044
102042
  // src/ai/flows/question-gen/generate-coding-question.ts
102045
102043
  init_react_shim();
@@ -138728,11 +138726,17 @@ function MetadataImportControls({ metadataName, onImport }) {
138728
138726
  const fileInputRef = React169.useRef(null);
138729
138727
  const { toast: toast2 } = useToast();
138730
138728
  const processAndImportRecords = async (records, importSource) => {
138729
+ console.log(`[ImportControls] Processing ${records.length} records from ${importSource} for ${metadataName}.`);
138731
138730
  if (records.length === 0) {
138732
138731
  toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
138733
138732
  return;
138734
138733
  }
138735
- await onImport(records);
138734
+ try {
138735
+ await onImport(records);
138736
+ } catch (error) {
138737
+ console.error(`[ImportControls] Error during onImport callback for ${metadataName}:`, error);
138738
+ toast2({ title: "Import Failed", description: `An error occurred while saving the data: ${error instanceof Error ? error.message : String(error)}`, variant: "destructive" });
138739
+ }
138736
138740
  setIsOpen(false);
138737
138741
  setJsonString("");
138738
138742
  };
@@ -138764,8 +138768,10 @@ function MetadataImportControls({ metadataName, onImport }) {
138764
138768
  return record;
138765
138769
  });
138766
138770
  }
138771
+ console.log(`[ImportControls] File parsed successfully. Found ${records.length} records.`);
138767
138772
  await processAndImportRecords(records, "file");
138768
138773
  } catch (err) {
138774
+ console.error("[ImportControls] Error parsing file:", err);
138769
138775
  toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
138770
138776
  }
138771
138777
  });
@@ -139685,6 +139691,53 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
139685
139691
  });
139686
139692
  };
139687
139693
  const handleImport = async (records) => {
139694
+ console.log(`[LO Manager] handleImport called with ${records.length} raw records.`);
139695
+ if (!onBulkAdd) {
139696
+ console.error("[LO Manager] onBulkAdd handler is not provided.");
139697
+ return;
139698
+ }
139699
+ const parseStringToArray = (input) => {
139700
+ if (Array.isArray(input)) return input;
139701
+ if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
139702
+ return [];
139703
+ };
139704
+ const validationResult = records.reduce((acc, rec) => {
139705
+ if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
139706
+ acc.valid.push({
139707
+ code: rec.code,
139708
+ name: rec.name,
139709
+ description: rec.description || rec.name,
139710
+ subject: rec.subject || "",
139711
+ subjectCode: rec.subjectCode,
139712
+ category: rec.category || "",
139713
+ categoryCode: rec.categoryCode,
139714
+ topic: rec.topic || "",
139715
+ topicCode: rec.topicCode,
139716
+ grade: rec.grade || "",
139717
+ gradeCode: rec.gradeCode,
139718
+ keywords: parseStringToArray(rec.keywords),
139719
+ stemElements: parseStringToArray(rec.stemElements),
139720
+ bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
139721
+ });
139722
+ } else {
139723
+ acc.invalidCount++;
139724
+ }
139725
+ return acc;
139726
+ }, { valid: [], invalidCount: 0 });
139727
+ console.log(`[LO Manager] Validation complete. ${validationResult.valid.length} valid records found.`);
139728
+ if (validationResult.invalidCount > 0) {
139729
+ toast2({
139730
+ title: "Import Warning",
139731
+ description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
139732
+ variant: "destructive"
139733
+ });
139734
+ }
139735
+ if (validationResult.valid.length > 0) {
139736
+ console.log("[LO Manager] Calling onBulkAdd prop with validated data...");
139737
+ await onBulkAdd(validationResult.valid);
139738
+ } else {
139739
+ console.log("[LO Manager] No valid records to import.");
139740
+ }
139688
139741
  };
139689
139742
  return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => handleFormChange("code", e3.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e3) => handleFormChange("name", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subject" }, "Subject Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "subject", value: formState.subject || "", onChange: (e3) => handleFormChange("subject", e3.target.value) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: subjects.map((s4) => ({ value: s4.code, label: s4.name })), value: formState.subjectCode || "", onChange: (val) => handleFormChange("subjectCode", val), placeholder: "Select a subject..." }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "category" }, "Category Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "category", value: formState.category || "", onChange: (e3) => handleFormChange("category", e3.target.value) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e3) => handleFormChange("categoryCode", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "topic" }, "Topic Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "topic", value: formState.topic || "", onChange: (e3) => handleFormChange("topic", e3.target.value) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e3) => handleFormChange("topicCode", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "grade" }, "Grade Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "grade", value: formState.grade || "", onChange: (e3) => handleFormChange("grade", e3.target.value) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e3) => handleFormChange("gradeCode", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e3) => handleFormChange("keywords", e3.target.value.split(",").map((s4) => s4.trim())) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "stemElements" }, "STEM Elements (comma-separated)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "stemElements", value: formState.stemElements?.join(", ") || "", onChange: (e3) => handleFormChange("stemElements", e3.target.value.split(",").map((s4) => s4.trim())) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "bloomLevelsGuideline" }, "Bloom's Guideline (comma-separated)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "bloomLevelsGuideline", value: formState.bloomLevelsGuideline?.join(", ") || "", onChange: (e3) => handleFormChange("bloomLevelsGuideline", e3.target.value.split(",").map((s4) => s4.trim())) }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
139690
139743
  }
package/dist/react-ui.mjs CHANGED
@@ -101851,6 +101851,7 @@ init_react_shim();
101851
101851
  // src/services/TopicDataService.ts
101852
101852
  init_react_shim();
101853
101853
  var TopicDataService = class {
101854
+ // ... saveData, mergeData, getData, clearData methods remain the same ...
101854
101855
  static saveData(data) {
101855
101856
  try {
101856
101857
  if (typeof window === "undefined") return;
@@ -101895,47 +101896,41 @@ var TopicDataService = class {
101895
101896
  }
101896
101897
  const headerLine = lines.shift();
101897
101898
  const headers = headerLine.split(" ").map((h3) => h3.trim());
101898
- if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
101899
- const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
101900
- return { data: [], errors: [errorMsg] };
101901
- }
101899
+ const headerMap = headers.map((h3) => this.HEADER_MAP[h3] || null);
101902
101900
  const data = [];
101903
101901
  const errors2 = [];
101904
101902
  lines.forEach((line, index3) => {
101905
- const values = line.split(" ").map((v) => v.trim());
101906
- if (values.length !== this.EXPECTED_HEADERS.length) {
101907
- errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
101903
+ const values = line.split(" ");
101904
+ const rowObject = {};
101905
+ headerMap.forEach((propName, i2) => {
101906
+ if (propName) {
101907
+ const value = values[i2]?.trim() || "";
101908
+ if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
101909
+ rowObject[propName] = value.split(",").map((s4) => s4.trim()).filter(Boolean);
101910
+ } else {
101911
+ rowObject[propName] = value;
101912
+ }
101913
+ }
101914
+ });
101915
+ if (!rowObject.code) {
101916
+ errors2.push(`Line ${index3 + 2}: Missing required value for 'LO ID'.`);
101908
101917
  return;
101909
101918
  }
101910
- const [
101911
- loId,
101912
- name3,
101913
- loDescription,
101914
- subject,
101915
- category,
101916
- topic,
101917
- keywordsStr,
101918
- grade,
101919
- stemElementsStr,
101920
- bloomLevelsStr
101921
- ] = values;
101922
- if (!loId || !loDescription || !subject || !category || !topic) {
101923
- errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
101924
- return;
101919
+ if (!rowObject.name) {
101920
+ rowObject.name = rowObject.code;
101925
101921
  }
101926
101922
  const learningObjective = {
101927
101923
  id: generateUniqueId("lo_"),
101928
- code: loId,
101929
- name: name3,
101930
- description: loDescription,
101931
- // Can be the same as name or enhanced later
101932
- subject,
101933
- category,
101934
- topic,
101935
- grade,
101936
- keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
101937
- stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
101938
- bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
101924
+ code: rowObject.code,
101925
+ name: rowObject.name,
101926
+ description: rowObject.description,
101927
+ subject: rowObject.subject || "",
101928
+ category: rowObject.category || "",
101929
+ topic: rowObject.topic || "",
101930
+ grade: rowObject.grade || "",
101931
+ keywords: rowObject.keywords || [],
101932
+ stemElements: rowObject.stemElements || [],
101933
+ bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
101939
101934
  };
101940
101935
  data.push(learningObjective);
101941
101936
  });
@@ -101963,17 +101958,20 @@ var TopicDataService = class {
101963
101958
  }
101964
101959
  };
101965
101960
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
101966
- TopicDataService.EXPECTED_HEADERS = [
101967
- "LO ID",
101968
- "LO Description",
101969
- "Subject",
101970
- "Category",
101971
- "Topic",
101972
- "Keywords",
101973
- "Grade",
101974
- "STEM Element(s)",
101975
- "Bloom\u2019s Level(s) Guideline"
101976
- ];
101961
+ // Define a map for flexible header mapping
101962
+ TopicDataService.HEADER_MAP = {
101963
+ "LO ID": "code",
101964
+ "LO Name": "name",
101965
+ // Ready for future addition
101966
+ "LO Description": "description",
101967
+ "Subject": "subject",
101968
+ "Category": "category",
101969
+ "Topic": "topic",
101970
+ "Keywords": "keywords",
101971
+ "Grade": "grade",
101972
+ "STEM Element(s)": "stemElements",
101973
+ "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
101974
+ };
101977
101975
 
101978
101976
  // src/ai/flows/question-gen/generate-coding-question.ts
101979
101977
  init_react_shim();
@@ -138662,11 +138660,17 @@ function MetadataImportControls({ metadataName, onImport }) {
138662
138660
  const fileInputRef = useRef(null);
138663
138661
  const { toast: toast2 } = useToast();
138664
138662
  const processAndImportRecords = async (records, importSource) => {
138663
+ console.log(`[ImportControls] Processing ${records.length} records from ${importSource} for ${metadataName}.`);
138665
138664
  if (records.length === 0) {
138666
138665
  toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
138667
138666
  return;
138668
138667
  }
138669
- await onImport(records);
138668
+ try {
138669
+ await onImport(records);
138670
+ } catch (error) {
138671
+ console.error(`[ImportControls] Error during onImport callback for ${metadataName}:`, error);
138672
+ toast2({ title: "Import Failed", description: `An error occurred while saving the data: ${error instanceof Error ? error.message : String(error)}`, variant: "destructive" });
138673
+ }
138670
138674
  setIsOpen(false);
138671
138675
  setJsonString("");
138672
138676
  };
@@ -138698,8 +138702,10 @@ function MetadataImportControls({ metadataName, onImport }) {
138698
138702
  return record;
138699
138703
  });
138700
138704
  }
138705
+ console.log(`[ImportControls] File parsed successfully. Found ${records.length} records.`);
138701
138706
  await processAndImportRecords(records, "file");
138702
138707
  } catch (err) {
138708
+ console.error("[ImportControls] Error parsing file:", err);
138703
138709
  toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
138704
138710
  }
138705
138711
  });
@@ -139619,6 +139625,53 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
139619
139625
  });
139620
139626
  };
139621
139627
  const handleImport = async (records) => {
139628
+ console.log(`[LO Manager] handleImport called with ${records.length} raw records.`);
139629
+ if (!onBulkAdd) {
139630
+ console.error("[LO Manager] onBulkAdd handler is not provided.");
139631
+ return;
139632
+ }
139633
+ const parseStringToArray = (input) => {
139634
+ if (Array.isArray(input)) return input;
139635
+ if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
139636
+ return [];
139637
+ };
139638
+ const validationResult = records.reduce((acc, rec) => {
139639
+ if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
139640
+ acc.valid.push({
139641
+ code: rec.code,
139642
+ name: rec.name,
139643
+ description: rec.description || rec.name,
139644
+ subject: rec.subject || "",
139645
+ subjectCode: rec.subjectCode,
139646
+ category: rec.category || "",
139647
+ categoryCode: rec.categoryCode,
139648
+ topic: rec.topic || "",
139649
+ topicCode: rec.topicCode,
139650
+ grade: rec.grade || "",
139651
+ gradeCode: rec.gradeCode,
139652
+ keywords: parseStringToArray(rec.keywords),
139653
+ stemElements: parseStringToArray(rec.stemElements),
139654
+ bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
139655
+ });
139656
+ } else {
139657
+ acc.invalidCount++;
139658
+ }
139659
+ return acc;
139660
+ }, { valid: [], invalidCount: 0 });
139661
+ console.log(`[LO Manager] Validation complete. ${validationResult.valid.length} valid records found.`);
139662
+ if (validationResult.invalidCount > 0) {
139663
+ toast2({
139664
+ title: "Import Warning",
139665
+ description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
139666
+ variant: "destructive"
139667
+ });
139668
+ }
139669
+ if (validationResult.valid.length > 0) {
139670
+ console.log("[LO Manager] Calling onBulkAdd prop with validated data...");
139671
+ await onBulkAdd(validationResult.valid);
139672
+ } else {
139673
+ console.log("[LO Manager] No valid records to import.");
139674
+ }
139622
139675
  };
139623
139676
  return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => handleFormChange("code", e3.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e3) => handleFormChange("name", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subject" }, "Subject Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "subject", value: formState.subject || "", onChange: (e3) => handleFormChange("subject", e3.target.value) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: subjects.map((s4) => ({ value: s4.code, label: s4.name })), value: formState.subjectCode || "", onChange: (val) => handleFormChange("subjectCode", val), placeholder: "Select a subject..." }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "category" }, "Category Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "category", value: formState.category || "", onChange: (e3) => handleFormChange("category", e3.target.value) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e3) => handleFormChange("categoryCode", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "topic" }, "Topic Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "topic", value: formState.topic || "", onChange: (e3) => handleFormChange("topic", e3.target.value) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e3) => handleFormChange("topicCode", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "grade" }, "Grade Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "grade", value: formState.grade || "", onChange: (e3) => handleFormChange("grade", e3.target.value) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e3) => handleFormChange("gradeCode", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e3) => handleFormChange("keywords", e3.target.value.split(",").map((s4) => s4.trim())) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "stemElements" }, "STEM Elements (comma-separated)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "stemElements", value: formState.stemElements?.join(", ") || "", onChange: (e3) => handleFormChange("stemElements", e3.target.value.split(",").map((s4) => s4.trim())) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "bloomLevelsGuideline" }, "Bloom's Guideline (comma-separated)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "bloomLevelsGuideline", value: formState.bloomLevelsGuideline?.join(", ") || "", onChange: (e3) => handleFormChange("bloomLevelsGuideline", e3.target.value.split(",").map((s4) => s4.trim())) }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
139624
139677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.69",
3
+ "version": "1.0.71",
4
4
  "description": "A comprehensive library for creating, managing, and playing interactive quizzes, with AI generation and SCORM support.",
5
5
  "keywords": [
6
6
  "react",