@thanh01.pmt/interactive-quiz-kit 1.0.68 → 1.0.70

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;
@@ -76443,8 +76441,8 @@ function MetadataImportControls({ metadataName, onImport }) {
76443
76441
  const handleFileSelected = (event) => {
76444
76442
  const file = event.target.files?.[0];
76445
76443
  if (!file) return;
76446
- if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
76447
- toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
76444
+ if (!file.type.includes("json") && !file.type.includes("tab-separated-values") && !file.name.endsWith(".tsv") && !file.name.endsWith(".txt")) {
76445
+ toast2({ title: "Invalid File Type", description: "Please select a JSON or TSV/TXT file.", variant: "destructive" });
76448
76446
  if (event.target) event.target.value = "";
76449
76447
  return;
76450
76448
  }
@@ -76452,18 +76450,18 @@ function MetadataImportControls({ metadataName, onImport }) {
76452
76450
  try {
76453
76451
  const fileContent = await file.text();
76454
76452
  let records = [];
76455
- if (file.type === "application/json") {
76453
+ if (file.type.includes("json")) {
76456
76454
  records = JSON.parse(fileContent);
76457
76455
  if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
76458
- } else if (file.name.endsWith(".csv")) {
76459
- const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
76460
- if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
76461
- const headers = lines[0].split(",").map((h2) => h2.trim());
76456
+ } else {
76457
+ const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "");
76458
+ if (lines.length < 2) throw new Error("TSV file must have a header and at least one data row.");
76459
+ const headers = lines[0].split(" ").map((h2) => h2.trim());
76462
76460
  records = lines.slice(1).map((line) => {
76463
- const values = line.split(",").map((v) => v.trim());
76461
+ const values = line.split(" ");
76464
76462
  const record = {};
76465
76463
  headers.forEach((header, index3) => {
76466
- record[header] = values[index3];
76464
+ record[header] = values[index3]?.trim() || "";
76467
76465
  });
76468
76466
  return record;
76469
76467
  });
@@ -76490,7 +76488,7 @@ function MetadataImportControls({ metadataName, onImport }) {
76490
76488
  }
76491
76489
  });
76492
76490
  };
76493
- return /* @__PURE__ */ React119__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React119__namespace.default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React119__namespace.default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React119__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React119__namespace.default.createElement(DialogContent2, null, /* @__PURE__ */ React119__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React119__namespace.default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React119__namespace.default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React119__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React119__namespace.default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React119__namespace.default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React119__namespace.default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React119__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React119__namespace.default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or CSV file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subject_code')."), /* @__PURE__ */ React119__namespace.default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React119__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React119__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React119__namespace.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React119__namespace.default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React119__namespace.default.createElement(
76491
+ return /* @__PURE__ */ React119__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React119__namespace.default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React119__namespace.default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React119__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React119__namespace.default.createElement(DialogContent2, null, /* @__PURE__ */ React119__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React119__namespace.default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React119__namespace.default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React119__namespace.default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React119__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React119__namespace.default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React119__namespace.default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React119__namespace.default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React119__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React119__namespace.default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or TSV/TXT file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subjectCode')."), /* @__PURE__ */ React119__namespace.default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React119__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React119__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React119__namespace.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.tsv,.txt,text/tab-separated-values", className: "hidden" }))), /* @__PURE__ */ React119__namespace.default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React119__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__namespace.default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React119__namespace.default.createElement(
76494
76492
  Textarea,
76495
76493
  {
76496
76494
  id: "jsonInput",
@@ -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;
@@ -76378,8 +76376,8 @@ function MetadataImportControls({ metadataName, onImport }) {
76378
76376
  const handleFileSelected = (event) => {
76379
76377
  const file = event.target.files?.[0];
76380
76378
  if (!file) return;
76381
- if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
76382
- toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
76379
+ if (!file.type.includes("json") && !file.type.includes("tab-separated-values") && !file.name.endsWith(".tsv") && !file.name.endsWith(".txt")) {
76380
+ toast2({ title: "Invalid File Type", description: "Please select a JSON or TSV/TXT file.", variant: "destructive" });
76383
76381
  if (event.target) event.target.value = "";
76384
76382
  return;
76385
76383
  }
@@ -76387,18 +76385,18 @@ function MetadataImportControls({ metadataName, onImport }) {
76387
76385
  try {
76388
76386
  const fileContent = await file.text();
76389
76387
  let records = [];
76390
- if (file.type === "application/json") {
76388
+ if (file.type.includes("json")) {
76391
76389
  records = JSON.parse(fileContent);
76392
76390
  if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
76393
- } else if (file.name.endsWith(".csv")) {
76394
- const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
76395
- if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
76396
- const headers = lines[0].split(",").map((h2) => h2.trim());
76391
+ } else {
76392
+ const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "");
76393
+ if (lines.length < 2) throw new Error("TSV file must have a header and at least one data row.");
76394
+ const headers = lines[0].split(" ").map((h2) => h2.trim());
76397
76395
  records = lines.slice(1).map((line) => {
76398
- const values = line.split(",").map((v) => v.trim());
76396
+ const values = line.split(" ");
76399
76397
  const record = {};
76400
76398
  headers.forEach((header, index3) => {
76401
- record[header] = values[index3];
76399
+ record[header] = values[index3]?.trim() || "";
76402
76400
  });
76403
76401
  return record;
76404
76402
  });
@@ -76425,7 +76423,7 @@ function MetadataImportControls({ metadataName, onImport }) {
76425
76423
  }
76426
76424
  });
76427
76425
  };
76428
- return /* @__PURE__ */ React119__default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React119__default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React119__default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React119__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React119__default.createElement(DialogContent2, null, /* @__PURE__ */ React119__default.createElement(DialogHeader, null, /* @__PURE__ */ React119__default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React119__default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React119__default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React119__default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React119__default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React119__default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React119__default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React119__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React119__default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React119__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or CSV file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subject_code')."), /* @__PURE__ */ React119__default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React119__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React119__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React119__default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React119__default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React119__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React119__default.createElement(
76426
+ return /* @__PURE__ */ React119__default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React119__default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React119__default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React119__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React119__default.createElement(DialogContent2, null, /* @__PURE__ */ React119__default.createElement(DialogHeader, null, /* @__PURE__ */ React119__default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React119__default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React119__default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React119__default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React119__default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React119__default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React119__default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React119__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React119__default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React119__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or TSV/TXT file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subjectCode')."), /* @__PURE__ */ React119__default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React119__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React119__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React119__default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.tsv,.txt,text/tab-separated-values", className: "hidden" }))), /* @__PURE__ */ React119__default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React119__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React119__default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React119__default.createElement(
76429
76427
  Textarea,
76430
76428
  {
76431
76429
  id: "jsonInput",
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();
@@ -138739,8 +138737,8 @@ function MetadataImportControls({ metadataName, onImport }) {
138739
138737
  const handleFileSelected = (event) => {
138740
138738
  const file = event.target.files?.[0];
138741
138739
  if (!file) return;
138742
- if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
138743
- toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
138740
+ if (!file.type.includes("json") && !file.type.includes("tab-separated-values") && !file.name.endsWith(".tsv") && !file.name.endsWith(".txt")) {
138741
+ toast2({ title: "Invalid File Type", description: "Please select a JSON or TSV/TXT file.", variant: "destructive" });
138744
138742
  if (event.target) event.target.value = "";
138745
138743
  return;
138746
138744
  }
@@ -138748,18 +138746,18 @@ function MetadataImportControls({ metadataName, onImport }) {
138748
138746
  try {
138749
138747
  const fileContent = await file.text();
138750
138748
  let records = [];
138751
- if (file.type === "application/json") {
138749
+ if (file.type.includes("json")) {
138752
138750
  records = JSON.parse(fileContent);
138753
138751
  if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
138754
- } else if (file.name.endsWith(".csv")) {
138755
- const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
138756
- if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
138757
- const headers = lines[0].split(",").map((h3) => h3.trim());
138752
+ } else {
138753
+ const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "");
138754
+ if (lines.length < 2) throw new Error("TSV file must have a header and at least one data row.");
138755
+ const headers = lines[0].split(" ").map((h3) => h3.trim());
138758
138756
  records = lines.slice(1).map((line) => {
138759
- const values = line.split(",").map((v) => v.trim());
138757
+ const values = line.split(" ");
138760
138758
  const record = {};
138761
138759
  headers.forEach((header, index3) => {
138762
- record[header] = values[index3];
138760
+ record[header] = values[index3]?.trim() || "";
138763
138761
  });
138764
138762
  return record;
138765
138763
  });
@@ -138786,7 +138784,7 @@ function MetadataImportControls({ metadataName, onImport }) {
138786
138784
  }
138787
138785
  });
138788
138786
  };
138789
- return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__namespace.default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or CSV file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subject_code')."), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__namespace.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__namespace.default.createElement(
138787
+ return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__namespace.default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or TSV/TXT file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subjectCode')."), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__namespace.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.tsv,.txt,text/tab-separated-values", className: "hidden" }))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__namespace.default.createElement(
138790
138788
  Textarea,
138791
138789
  {
138792
138790
  id: "jsonInput",
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();
@@ -138673,8 +138671,8 @@ function MetadataImportControls({ metadataName, onImport }) {
138673
138671
  const handleFileSelected = (event) => {
138674
138672
  const file = event.target.files?.[0];
138675
138673
  if (!file) return;
138676
- if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
138677
- toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
138674
+ if (!file.type.includes("json") && !file.type.includes("tab-separated-values") && !file.name.endsWith(".tsv") && !file.name.endsWith(".txt")) {
138675
+ toast2({ title: "Invalid File Type", description: "Please select a JSON or TSV/TXT file.", variant: "destructive" });
138678
138676
  if (event.target) event.target.value = "";
138679
138677
  return;
138680
138678
  }
@@ -138682,18 +138680,18 @@ function MetadataImportControls({ metadataName, onImport }) {
138682
138680
  try {
138683
138681
  const fileContent = await file.text();
138684
138682
  let records = [];
138685
- if (file.type === "application/json") {
138683
+ if (file.type.includes("json")) {
138686
138684
  records = JSON.parse(fileContent);
138687
138685
  if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
138688
- } else if (file.name.endsWith(".csv")) {
138689
- const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
138690
- if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
138691
- const headers = lines[0].split(",").map((h3) => h3.trim());
138686
+ } else {
138687
+ const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "");
138688
+ if (lines.length < 2) throw new Error("TSV file must have a header and at least one data row.");
138689
+ const headers = lines[0].split(" ").map((h3) => h3.trim());
138692
138690
  records = lines.slice(1).map((line) => {
138693
- const values = line.split(",").map((v) => v.trim());
138691
+ const values = line.split(" ");
138694
138692
  const record = {};
138695
138693
  headers.forEach((header, index3) => {
138696
- record[header] = values[index3];
138694
+ record[header] = values[index3]?.trim() || "";
138697
138695
  });
138698
138696
  return record;
138699
138697
  });
@@ -138720,7 +138718,7 @@ function MetadataImportControls({ metadataName, onImport }) {
138720
138718
  }
138721
138719
  });
138722
138720
  };
138723
- return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__default.createElement(DialogContent2, null, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or CSV file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subject_code')."), /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__default.createElement(
138721
+ return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__default.createElement(DialogContent2, null, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or TSV/TXT file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subjectCode')."), /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.tsv,.txt,text/tab-separated-values", className: "hidden" }))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__default.createElement(
138724
138722
  Textarea,
138725
138723
  {
138726
138724
  id: "jsonInput",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
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",