@thanh01.pmt/interactive-quiz-kit 1.0.44 → 1.0.46

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/react-ui.cjs CHANGED
@@ -10,6 +10,7 @@ var default3 = require('process');
10
10
  var url = require('url');
11
11
  var JSZip = require('jszip');
12
12
  var navigation = require('next/navigation');
13
+ var Link3 = require('next/link');
13
14
 
14
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
16
 
@@ -36,6 +37,7 @@ var ReactDOM4__namespace = /*#__PURE__*/_interopNamespace(ReactDOM4);
36
37
  var default2__default = /*#__PURE__*/_interopDefault(default2);
37
38
  var default3__default = /*#__PURE__*/_interopDefault(default3);
38
39
  var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
40
+ var Link3__default = /*#__PURE__*/_interopDefault(Link3);
39
41
 
40
42
  var __create = Object.create;
41
43
  var __defProp = Object.defineProperty;
@@ -167223,7 +167225,7 @@ var ClientTranslation = ({ tKey, options, fallback: fallback2 }) => {
167223
167225
  };
167224
167226
 
167225
167227
  // src/react-ui/components/app/PersonalPracticeDashboard.tsx
167226
- var PersonalPracticeDashboard = () => {
167228
+ var PersonalPracticeDashboard = ({ settingsPath }) => {
167227
167229
  const router = navigation.useRouter();
167228
167230
  const { toast: toast2 } = useToast();
167229
167231
  const { t: t4, i18n } = useTranslation();
@@ -167240,7 +167242,10 @@ var PersonalPracticeDashboard = () => {
167240
167242
  const [isChatbotOpen, setIsChatbotOpen] = React163.useState(false);
167241
167243
  const [isUploadModalOpen, setIsUploadModalOpen] = React163.useState(false);
167242
167244
  const [isEditMode, setIsEditMode] = React163.useState(false);
167243
- const [dashboardLayout, setDashboardLayout] = React163.useState(DashboardLayoutService.getLayout());
167245
+ const [dashboardLayout, setDashboardLayout] = React163.useState(
167246
+ DashboardLayoutService.getLayout()
167247
+ );
167248
+ const [isSettingsModalOpen, setIsSettingsModalOpen] = React163.useState(false);
167244
167249
  const loadDashboardData = React163.useCallback(() => {
167245
167250
  setIsLoading(true);
167246
167251
  setDashboardLayout(DashboardLayoutService.getLayout());
@@ -167248,9 +167253,17 @@ var PersonalPracticeDashboard = () => {
167248
167253
  const practiceStats = PracticeHistoryService.getPracticeStats();
167249
167254
  const name3 = UserConfigService.getFullName();
167250
167255
  if (practiceStats && practiceHistory.length > 0) {
167251
- const newlyUnlocked = AchievementService.checkAndUnlockAchievements(practiceHistory, practiceStats);
167256
+ const newlyUnlocked = AchievementService.checkAndUnlockAchievements(
167257
+ practiceHistory,
167258
+ practiceStats
167259
+ );
167252
167260
  if (newlyUnlocked.length > 0) {
167253
- newlyUnlocked.forEach((ach) => toast2({ title: "\u{1F3C6} Th\xE0nh t\xEDch M\u1EDBi!", description: `B\u1EA1n \u0111\xE3 m\u1EDF kh\xF3a: ${t4(ach.nameKey)}` }));
167261
+ newlyUnlocked.forEach(
167262
+ (ach) => toast2({
167263
+ title: "\u{1F3C6} Th\xE0nh t\xEDch M\u1EDBi!",
167264
+ description: `B\u1EA1n \u0111\xE3 m\u1EDF kh\xF3a: ${t4(ach.nameKey)}`
167265
+ })
167266
+ );
167254
167267
  }
167255
167268
  }
167256
167269
  const achievementsWithStatus = AchievementService.getAllAchievementsWithStatus();
@@ -167272,32 +167285,55 @@ var PersonalPracticeDashboard = () => {
167272
167285
  const apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
167273
167286
  if (!apiKey || !stats) return;
167274
167287
  try {
167275
- const weakestTopic = stats.performanceByTopic.sort((a4, b2) => a4.averageScore - b2.averageScore)[0]?.name;
167276
- const aiQuote = await generateMotivationalQuote({
167277
- language: i18n.language === "vi" ? "Vietnamese" : "English",
167278
- userName: UserConfigService.getFullName() || void 0,
167279
- currentStreak: stats.currentStreak,
167280
- weakestTopic
167281
- }, apiKey);
167288
+ const weakestTopic = stats.performanceByTopic.sort(
167289
+ (a4, b2) => a4.averageScore - b2.averageScore
167290
+ )[0]?.name;
167291
+ const aiQuote = await generateMotivationalQuote(
167292
+ {
167293
+ language: i18n.language === "vi" ? "Vietnamese" : "English",
167294
+ userName: UserConfigService.getFullName() || void 0,
167295
+ currentStreak: stats.currentStreak,
167296
+ weakestTopic
167297
+ },
167298
+ apiKey
167299
+ );
167282
167300
  if (aiQuote) setMotivationalQuote(aiQuote);
167283
167301
  } catch (e3) {
167284
167302
  console.warn("Could not fetch AI-powered quote.", e3);
167285
167303
  }
167286
167304
  };
167287
167305
  if (stats) fetchAIQuote();
167288
- const intervalId = setInterval(() => setMotivationalQuote(QuoteService.getRandomQuote()), 10 * 60 * 1e3);
167306
+ const intervalId = setInterval(
167307
+ () => setMotivationalQuote(QuoteService.getRandomQuote()),
167308
+ 10 * 60 * 1e3
167309
+ );
167289
167310
  return () => clearInterval(intervalId);
167290
167311
  }, [stats, i18n.language]);
167291
167312
  const historySummaryForTable = React163.useMemo(() => {
167292
- return history2.map((session) => ({ ...session.summary, id: session.id, timestamp: session.timestamp, quizTitle: session.quizConfig.title }));
167313
+ return history2.map((session) => ({
167314
+ ...session.summary,
167315
+ id: session.id,
167316
+ timestamp: session.timestamp,
167317
+ quizTitle: session.quizConfig.title
167318
+ }));
167293
167319
  }, [history2]);
167294
- const handleStartPractice = React163.useCallback(() => router.push("/practice"), [router]);
167320
+ const handleStartPractice = React163.useCallback(
167321
+ () => router.push("/practice"),
167322
+ [router]
167323
+ );
167295
167324
  const handleGenerateRoadmap = React163.useCallback(async () => {
167296
167325
  setIsGeneratingRoadmap(true);
167297
- toast2({ title: "AI Tutor \u0111ang suy ngh\u0129...", description: "\u0110ang t\u1EA1o l\u1ED9 tr\xECnh h\u1ECDc t\u1EADp \u0111\u01B0\u1EE3c c\xE1 nh\xE2n h\xF3a cho b\u1EA1n." });
167326
+ toast2({
167327
+ title: "AI Tutor \u0111ang suy ngh\u0129...",
167328
+ description: "\u0110ang t\u1EA1o l\u1ED9 tr\xECnh h\u1ECDc t\u1EADp \u0111\u01B0\u1EE3c c\xE1 nh\xE2n h\xF3a cho b\u1EA1n."
167329
+ });
167298
167330
  const apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
167299
167331
  if (!apiKey) {
167300
- toast2({ title: "API Key Missing", description: "Vui l\xF2ng c\u1EA5u h\xECnh Gemini API Key trong C\xE0i \u0111\u1EB7t.", variant: "destructive" });
167332
+ toast2({
167333
+ title: "API Key Missing",
167334
+ description: "Vui l\xF2ng c\u1EA5u h\xECnh Gemini API Key trong C\xE0i \u0111\u1EB7t.",
167335
+ variant: "destructive"
167336
+ });
167301
167337
  setIsGeneratingRoadmap(false);
167302
167338
  return;
167303
167339
  }
@@ -167309,28 +167345,42 @@ var PersonalPracticeDashboard = () => {
167309
167345
  topic: lo.topic
167310
167346
  }));
167311
167347
  if (allAvailableTopics.length === 0) {
167312
- throw new Error("No topic data found. Please import curriculum data in Settings first.");
167348
+ throw new Error(
167349
+ "No topic data found. Please import curriculum data in Settings first."
167350
+ );
167313
167351
  }
167314
- const result = await generateLearningAnalysis({
167315
- language: i18n.language === "vi" ? "Vietnamese" : "English",
167316
- userName: UserConfigService.getFullName() || void 0,
167317
- weeklyGoal: UserConfigService.getWeeklyGoal() || void 0,
167318
- unlockedAchievements: AchievementService.getAllAchievementsWithStatus().filter((a4) => a4.unlockedAt),
167319
- stats: PracticeHistoryService.getPracticeStats(),
167320
- history: PracticeHistoryService.getPracticeHistory(),
167321
- startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
167322
- endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
167323
- allAvailableTopics
167324
- }, apiKey);
167352
+ const result = await generateLearningAnalysis(
167353
+ {
167354
+ language: i18n.language === "vi" ? "Vietnamese" : "English",
167355
+ userName: UserConfigService.getFullName() || void 0,
167356
+ weeklyGoal: UserConfigService.getWeeklyGoal() || void 0,
167357
+ unlockedAchievements: AchievementService.getAllAchievementsWithStatus().filter(
167358
+ (a4) => a4.unlockedAt
167359
+ ),
167360
+ stats: PracticeHistoryService.getPracticeStats(),
167361
+ history: PracticeHistoryService.getPracticeHistory(),
167362
+ startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
167363
+ endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
167364
+ allAvailableTopics
167365
+ },
167366
+ apiKey
167367
+ );
167325
167368
  if (result.weeklyRoadmap) {
167326
167369
  RoadmapService.saveRoadmap(result.weeklyRoadmap);
167327
- toast2({ title: "L\u1ED9 tr\xECnh M\u1EDBi \u0111\xE3 S\u1EB5n s\xE0ng!", description: "L\u1ED9 tr\xECnh h\u1ECDc t\u1EADp cho tu\u1EA7n n\xE0y \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt." });
167370
+ toast2({
167371
+ title: "L\u1ED9 tr\xECnh M\u1EDBi \u0111\xE3 S\u1EB5n s\xE0ng!",
167372
+ description: "L\u1ED9 tr\xECnh h\u1ECDc t\u1EADp cho tu\u1EA7n n\xE0y \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167373
+ });
167328
167374
  } else {
167329
167375
  throw new Error("AI \u0111\xE3 kh\xF4ng tr\u1EA3 v\u1EC1 m\u1ED9t l\u1ED9 tr\xECnh h\u1EE3p l\u1EC7.");
167330
167376
  }
167331
167377
  } catch (error) {
167332
167378
  const msg = error instanceof Error ? error.message : "\u0110\xE3 x\u1EA3y ra l\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh.";
167333
- toast2({ title: "T\u1EA1o L\u1ED9 tr\xECnh Th\u1EA5t b\u1EA1i", description: msg, variant: "destructive" });
167379
+ toast2({
167380
+ title: "T\u1EA1o L\u1ED9 tr\xECnh Th\u1EA5t b\u1EA1i",
167381
+ description: msg,
167382
+ variant: "destructive"
167383
+ });
167334
167384
  } finally {
167335
167385
  setIsGeneratingRoadmap(false);
167336
167386
  }
@@ -167354,14 +167404,26 @@ var PersonalPracticeDashboard = () => {
167354
167404
  if (!over || active.id === over.id) return;
167355
167405
  setDashboardLayout((prev) => {
167356
167406
  const newLayout = JSON.parse(JSON.stringify(prev));
167357
- const activeColKey = newLayout.column1.some((c4) => c4.id === active.id) ? "column1" : "column2";
167358
- const overColKey = newLayout.column1.some((c4) => c4.id === over.id) ? "column1" : "column2";
167407
+ const activeColKey = newLayout.column1.some(
167408
+ (c4) => c4.id === active.id
167409
+ ) ? "column1" : "column2";
167410
+ const overColKey = newLayout.column1.some(
167411
+ (c4) => c4.id === over.id
167412
+ ) ? "column1" : "column2";
167359
167413
  const activeCol = newLayout[activeColKey];
167360
167414
  const overCol = newLayout[overColKey];
167361
- const activeIndex = activeCol.findIndex((c4) => c4.id === active.id);
167362
- const overIndex = overCol.findIndex((c4) => c4.id === over.id);
167415
+ const activeIndex = activeCol.findIndex(
167416
+ (c4) => c4.id === active.id
167417
+ );
167418
+ const overIndex = overCol.findIndex(
167419
+ (c4) => c4.id === over.id
167420
+ );
167363
167421
  if (activeColKey === overColKey) {
167364
- newLayout[activeColKey] = arrayMove(activeCol, activeIndex, overIndex);
167422
+ newLayout[activeColKey] = arrayMove(
167423
+ activeCol,
167424
+ activeIndex,
167425
+ overIndex
167426
+ );
167365
167427
  } else {
167366
167428
  const [movedItem] = activeCol.splice(activeIndex, 1);
167367
167429
  overCol.splice(overIndex, 0, movedItem);
@@ -167372,7 +167434,10 @@ var PersonalPracticeDashboard = () => {
167372
167434
  const handleSaveLayout = () => {
167373
167435
  DashboardLayoutService.saveLayout(dashboardLayout);
167374
167436
  setIsEditMode(false);
167375
- toast2({ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u", description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt." });
167437
+ toast2({
167438
+ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u",
167439
+ description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167440
+ });
167376
167441
  };
167377
167442
  const handleCancelEdit = () => {
167378
167443
  setDashboardLayout(DashboardLayoutService.getLayout());
@@ -167393,7 +167458,218 @@ var PersonalPracticeDashboard = () => {
167393
167458
  const column2Items = dashboardLayout.column2.map((c4) => c4.id);
167394
167459
  const quoteText = isMounted ? motivationalQuote ? `"${motivationalQuote.text}" \u2014 ${motivationalQuote.author}` : t4("dashboard.motivationalQuoteFallback") : '"The best way to predict the future is to create it." \u2014 Peter Drucker';
167395
167460
  const welcomeMessage = isMounted ? userName ? t4("dashboard.welcomeMessage", { name: userName }) : t4("dashboard.welcomeMessageGuest") : userName ? `Welcome back, ${userName}!` : "Welcome to your Practice Dashboard";
167396
- return /* @__PURE__ */ React163__namespace.default.createElement(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd }, /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React163__namespace.default.createElement(Card, { className: "text-center shadow-xl bg-gradient-to-br from-primary to-primary/80 text-primary-foreground border-none" }, /* @__PURE__ */ React163__namespace.default.createElement(CardHeader, { className: "p-8 relative" }, /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "absolute top-4 right-4" }, !isEditMode ? /* @__PURE__ */ React163__namespace.default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => setIsEditMode(true) }, /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "common.customize", fallback: "Customize" })) : /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__namespace.default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleCancelEdit, className: "bg-background/20 hover:bg-background/40 text-white" }, /* @__PURE__ */ React163__namespace.default.createElement(X, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "common.cancel", fallback: "Cancel" })), /* @__PURE__ */ React163__namespace.default.createElement(Button, { variant: "secondary", size: "sm", onClick: handleSaveLayout }, /* @__PURE__ */ React163__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "common.saveLayout", fallback: "Save Layout" })))), /* @__PURE__ */ React163__namespace.default.createElement(CardTitle, { className: "text-3xl md:text-4xl font-bold tracking-tight" }, welcomeMessage), /* @__PURE__ */ React163__namespace.default.createElement(CardDescription, { className: "text-lg md:text-xl text-primary-foreground/80 max-w-3xl mx-auto mt-2 h-14 flex items-center justify-center" }, quoteText)), /* @__PURE__ */ React163__namespace.default.createElement(CardContent, { className: "pb-8 flex flex-wrap justify-center items-center gap-4" }, /* @__PURE__ */ React163__namespace.default.createElement(Button, { size: "lg", onClick: () => setIsUploadModalOpen(true), className: "bg-indigo-600 hover:bg-indigo-700 text-white text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__namespace.default.createElement(FileUp, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "dashboard.actions.createFromDoc", fallback: "Create Quiz from Document" })), /* @__PURE__ */ React163__namespace.default.createElement(Button, { size: "lg", onClick: handleStartPractice, className: "bg-amber-400 hover:bg-amber-500 text-amber-950 text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__namespace.default.createElement(BrainCircuit, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "dashboard.actions.freestyleTopic", fallback: "Freestyle Topic" })), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialog2, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React163__namespace.default.createElement(Button, { size: "lg", variant: "secondary", className: "px-8 py-6 text-base font-semibold", disabled: isGeneratingRoadmap }, /* @__PURE__ */ React163__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "dashboard.actions.suggestRoadmap", fallback: "Suggest Next Week's Roadmap" }))), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogTitle2, null, t4("dialogs.confirmRoadmap.title")), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogDescription2, null, t4("dialogs.confirmRoadmap.description"))), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogAction2, { onClick: handleGenerateRoadmap }, t4("dialogs.confirmRoadmap.action"))))), /* @__PURE__ */ React163__namespace.default.createElement(Button, { size: "lg", onClick: () => setIsAnalysisDialogOpen(true), className: "bg-blue-600 hover:bg-blue-700 text-white text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__namespace.default.createElement(BarChart4, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__namespace.default.createElement(ClientTranslation, { tKey: "dashboard.actions.deepAnalysis", fallback: "Deep Analysis" })))), /* @__PURE__ */ React163__namespace.default.createElement(PerformanceSnapshot, { stats, isLoading }), /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 items-start" }, /* @__PURE__ */ React163__namespace.default.createElement(SortableContext, { items: column1Items, strategy: verticalListSortingStrategy }, /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column1.map((cardConfig) => /* @__PURE__ */ React163__namespace.default.createElement(DraggableDashboardCard, { key: cardConfig.id, id: cardConfig.id, visible: cardConfig.visible, isEditMode, onToggleVisibility: handleToggleVisibility }, cardComponents[cardConfig.id])))), /* @__PURE__ */ React163__namespace.default.createElement(SortableContext, { items: column2Items, strategy: verticalListSortingStrategy }, /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column2.map((cardConfig) => /* @__PURE__ */ React163__namespace.default.createElement(DraggableDashboardCard, { key: cardConfig.id, id: cardConfig.id, visible: cardConfig.visible, isEditMode, onToggleVisibility: handleToggleVisibility }, cardComponents[cardConfig.id]))))), /* @__PURE__ */ React163__namespace.default.createElement(AnalysisDialog, { isOpen: isAnalysisDialogOpen, onClose: () => setIsAnalysisDialogOpen(false) }), /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "fixed bottom-6 right-6 z-50" }, /* @__PURE__ */ React163__namespace.default.createElement(Button, { size: "icon", className: "rounded-full h-14 w-14 shadow-lg bg-primary hover:bg-primary/90", onClick: () => setIsChatbotOpen(true), "aria-label": "Open AI Tutor Chat" }, /* @__PURE__ */ React163__namespace.default.createElement(Bot, { className: "h-7 w-7" }))), /* @__PURE__ */ React163__namespace.default.createElement(ChatbotDialog, { isOpen: isChatbotOpen, onClose: () => setIsChatbotOpen(false) }), /* @__PURE__ */ React163__namespace.default.createElement(UploadResourceModal, { isOpen: isUploadModalOpen, onClose: () => setIsUploadModalOpen(false) })));
167461
+ return /* @__PURE__ */ React163__namespace.default.createElement(
167462
+ DndContext,
167463
+ {
167464
+ sensors,
167465
+ collisionDetection: closestCenter,
167466
+ onDragEnd: handleDragEnd
167467
+ },
167468
+ /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React163__namespace.default.createElement(Card, { className: "text-center shadow-xl bg-gradient-to-br from-primary to-primary/80 text-primary-foreground border-none" }, /* @__PURE__ */ React163__namespace.default.createElement(CardHeader, { className: "p-8 relative" }, /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "absolute top-4 right-4 flex items-center gap-2" }, !isEditMode ? /* @__PURE__ */ React163__namespace.default.createElement(
167469
+ Button,
167470
+ {
167471
+ variant: "secondary",
167472
+ size: "sm",
167473
+ onClick: () => setIsEditMode(true)
167474
+ },
167475
+ /* @__PURE__ */ React163__namespace.default.createElement(LayoutDashboard, { className: "mr-2 h-4 w-4" }),
167476
+ /* @__PURE__ */ React163__namespace.default.createElement(
167477
+ ClientTranslation,
167478
+ {
167479
+ tKey: "common.customizeLayout",
167480
+ fallback: "Customize Layout"
167481
+ }
167482
+ )
167483
+ ) : /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__namespace.default.createElement(
167484
+ Button,
167485
+ {
167486
+ variant: "ghost",
167487
+ size: "sm",
167488
+ onClick: handleCancelEdit,
167489
+ className: "bg-background/20 hover:bg-background/40 text-white"
167490
+ },
167491
+ /* @__PURE__ */ React163__namespace.default.createElement(X, { className: "mr-2 h-4 w-4" }),
167492
+ /* @__PURE__ */ React163__namespace.default.createElement(
167493
+ ClientTranslation,
167494
+ {
167495
+ tKey: "common.cancel",
167496
+ fallback: "Cancel"
167497
+ }
167498
+ )
167499
+ ), /* @__PURE__ */ React163__namespace.default.createElement(
167500
+ Button,
167501
+ {
167502
+ variant: "secondary",
167503
+ size: "sm",
167504
+ onClick: handleSaveLayout
167505
+ },
167506
+ /* @__PURE__ */ React163__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }),
167507
+ /* @__PURE__ */ React163__namespace.default.createElement(
167508
+ ClientTranslation,
167509
+ {
167510
+ tKey: "common.saveLayout",
167511
+ fallback: "Save Layout"
167512
+ }
167513
+ )
167514
+ )), settingsPath ? /* @__PURE__ */ React163__namespace.default.createElement(
167515
+ Button,
167516
+ {
167517
+ asChild: true,
167518
+ variant: "ghost",
167519
+ size: "icon",
167520
+ className: "text-white hover:bg-background/20",
167521
+ title: "Settings"
167522
+ },
167523
+ /* @__PURE__ */ React163__namespace.default.createElement(Link3__default.default, { href: settingsPath }, /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "h-5 w-5" }))
167524
+ ) : /* @__PURE__ */ React163__namespace.default.createElement(
167525
+ Button,
167526
+ {
167527
+ variant: "ghost",
167528
+ size: "icon",
167529
+ className: "text-white hover:bg-background/20",
167530
+ title: "Settings",
167531
+ onClick: () => setIsSettingsModalOpen(true)
167532
+ },
167533
+ /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "h-5 w-5" })
167534
+ )), /* @__PURE__ */ React163__namespace.default.createElement(CardTitle, { className: "text-3xl md:text-4xl font-bold tracking-tight" }, welcomeMessage), /* @__PURE__ */ React163__namespace.default.createElement(CardDescription, { className: "text-lg md:text-xl text-primary-foreground/80 max-w-3xl mx-auto mt-2 h-14 flex items-center justify-center" }, quoteText)), /* @__PURE__ */ React163__namespace.default.createElement(CardContent, { className: "pb-8 flex flex-wrap justify-center items-center gap-4" }, /* @__PURE__ */ React163__namespace.default.createElement(
167535
+ Button,
167536
+ {
167537
+ size: "lg",
167538
+ onClick: () => setIsUploadModalOpen(true),
167539
+ className: "bg-indigo-600 hover:bg-indigo-700 text-white text-base font-semibold px-8 py-6"
167540
+ },
167541
+ /* @__PURE__ */ React163__namespace.default.createElement(FileUp, { className: "mr-2 h-5 w-5" }),
167542
+ /* @__PURE__ */ React163__namespace.default.createElement(
167543
+ ClientTranslation,
167544
+ {
167545
+ tKey: "dashboard.actions.createFromDoc",
167546
+ fallback: "Create Quiz from Document"
167547
+ }
167548
+ )
167549
+ ), /* @__PURE__ */ React163__namespace.default.createElement(
167550
+ Button,
167551
+ {
167552
+ size: "lg",
167553
+ onClick: handleStartPractice,
167554
+ className: "bg-amber-400 hover:bg-amber-500 text-amber-950 text-base font-semibold px-8 py-6"
167555
+ },
167556
+ /* @__PURE__ */ React163__namespace.default.createElement(BrainCircuit, { className: "mr-2 h-5 w-5" }),
167557
+ /* @__PURE__ */ React163__namespace.default.createElement(
167558
+ ClientTranslation,
167559
+ {
167560
+ tKey: "dashboard.actions.freestyleTopic",
167561
+ fallback: "Freestyle Topic"
167562
+ }
167563
+ )
167564
+ ), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialog2, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React163__namespace.default.createElement(
167565
+ Button,
167566
+ {
167567
+ size: "lg",
167568
+ variant: "secondary",
167569
+ className: "px-8 py-6 text-base font-semibold",
167570
+ disabled: isGeneratingRoadmap
167571
+ },
167572
+ /* @__PURE__ */ React163__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5" }),
167573
+ /* @__PURE__ */ React163__namespace.default.createElement(
167574
+ ClientTranslation,
167575
+ {
167576
+ tKey: "dashboard.actions.suggestRoadmap",
167577
+ fallback: "Suggest Next Week's Roadmap"
167578
+ }
167579
+ )
167580
+ )), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogTitle2, null, t4("dialogs.confirmRoadmap.title")), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogDescription2, null, t4(
167581
+ "dialogs.confirmRoadmap.description"
167582
+ ))), /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React163__namespace.default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React163__namespace.default.createElement(
167583
+ AlertDialogAction2,
167584
+ {
167585
+ onClick: handleGenerateRoadmap
167586
+ },
167587
+ t4("dialogs.confirmRoadmap.action")
167588
+ )))), /* @__PURE__ */ React163__namespace.default.createElement(
167589
+ Button,
167590
+ {
167591
+ size: "lg",
167592
+ onClick: () => setIsAnalysisDialogOpen(true),
167593
+ className: "bg-blue-600 hover:bg-blue-700 text-white text-base font-semibold px-8 py-6"
167594
+ },
167595
+ /* @__PURE__ */ React163__namespace.default.createElement(BarChart4, { className: "mr-2 h-5 w-5" }),
167596
+ /* @__PURE__ */ React163__namespace.default.createElement(
167597
+ ClientTranslation,
167598
+ {
167599
+ tKey: "dashboard.actions.deepAnalysis",
167600
+ fallback: "Deep Analysis"
167601
+ }
167602
+ )
167603
+ ))), /* @__PURE__ */ React163__namespace.default.createElement(PerformanceSnapshot, { stats, isLoading }), /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 items-start" }, /* @__PURE__ */ React163__namespace.default.createElement(
167604
+ SortableContext,
167605
+ {
167606
+ items: column1Items,
167607
+ strategy: verticalListSortingStrategy
167608
+ },
167609
+ /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column1.map((cardConfig) => /* @__PURE__ */ React163__namespace.default.createElement(
167610
+ DraggableDashboardCard,
167611
+ {
167612
+ key: cardConfig.id,
167613
+ id: cardConfig.id,
167614
+ visible: cardConfig.visible,
167615
+ isEditMode,
167616
+ onToggleVisibility: handleToggleVisibility
167617
+ },
167618
+ cardComponents[cardConfig.id]
167619
+ )))
167620
+ ), /* @__PURE__ */ React163__namespace.default.createElement(
167621
+ SortableContext,
167622
+ {
167623
+ items: column2Items,
167624
+ strategy: verticalListSortingStrategy
167625
+ },
167626
+ /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column2.map((cardConfig) => /* @__PURE__ */ React163__namespace.default.createElement(
167627
+ DraggableDashboardCard,
167628
+ {
167629
+ key: cardConfig.id,
167630
+ id: cardConfig.id,
167631
+ visible: cardConfig.visible,
167632
+ isEditMode,
167633
+ onToggleVisibility: handleToggleVisibility
167634
+ },
167635
+ cardComponents[cardConfig.id]
167636
+ )))
167637
+ )), /* @__PURE__ */ React163__namespace.default.createElement(
167638
+ AnalysisDialog,
167639
+ {
167640
+ isOpen: isAnalysisDialogOpen,
167641
+ onClose: () => setIsAnalysisDialogOpen(false)
167642
+ }
167643
+ ), /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "fixed bottom-6 right-6 z-50" }, /* @__PURE__ */ React163__namespace.default.createElement(
167644
+ Button,
167645
+ {
167646
+ size: "icon",
167647
+ className: "rounded-full h-14 w-14 shadow-lg bg-primary hover:bg-primary/90",
167648
+ onClick: () => setIsChatbotOpen(true),
167649
+ "aria-label": "Open AI Tutor Chat"
167650
+ },
167651
+ /* @__PURE__ */ React163__namespace.default.createElement(Bot, { className: "h-7 w-7" })
167652
+ )), /* @__PURE__ */ React163__namespace.default.createElement(
167653
+ ChatbotDialog,
167654
+ {
167655
+ isOpen: isChatbotOpen,
167656
+ onClose: () => setIsChatbotOpen(false)
167657
+ }
167658
+ ), /* @__PURE__ */ React163__namespace.default.createElement(
167659
+ UploadResourceModal,
167660
+ {
167661
+ isOpen: isUploadModalOpen,
167662
+ onClose: () => setIsUploadModalOpen(false)
167663
+ }
167664
+ )),
167665
+ !settingsPath && /* @__PURE__ */ React163__namespace.default.createElement(
167666
+ SettingsModal,
167667
+ {
167668
+ isOpen: isSettingsModalOpen,
167669
+ onClose: () => setIsSettingsModalOpen(false)
167670
+ }
167671
+ )
167672
+ );
167397
167673
  };
167398
167674
 
167399
167675
  // src/react-ui/components/practice/PracticeModeController.tsx
@@ -70,7 +70,10 @@ interface QuestionRendererProps {
70
70
  type ProgrammingQuestionUIRef = BlocklyProgrammingQuestionUIRef | ScratchProgrammingQuestionUIRef;
71
71
  declare const QuestionRenderer: React__default.ForwardRefExoticComponent<QuestionRendererProps & React__default.RefAttributes<ProgrammingQuestionUIRef>>;
72
72
 
73
- declare const PersonalPracticeDashboard: React__default.FC;
73
+ interface PersonalPracticeDashboardProps {
74
+ settingsPath?: string;
75
+ }
76
+ declare const PersonalPracticeDashboard: React__default.FC<PersonalPracticeDashboardProps>;
74
77
 
75
78
  type SettingsTab = 'personal' | 'topics' | 'imageContexts' | 'layout' | 'apiKeys';
76
79
  interface SettingsModalProps {
@@ -70,7 +70,10 @@ interface QuestionRendererProps {
70
70
  type ProgrammingQuestionUIRef = BlocklyProgrammingQuestionUIRef | ScratchProgrammingQuestionUIRef;
71
71
  declare const QuestionRenderer: React__default.ForwardRefExoticComponent<QuestionRendererProps & React__default.RefAttributes<ProgrammingQuestionUIRef>>;
72
72
 
73
- declare const PersonalPracticeDashboard: React__default.FC;
73
+ interface PersonalPracticeDashboardProps {
74
+ settingsPath?: string;
75
+ }
76
+ declare const PersonalPracticeDashboard: React__default.FC<PersonalPracticeDashboardProps>;
74
77
 
75
78
  type SettingsTab = 'personal' | 'topics' | 'imageContexts' | 'layout' | 'apiKeys';
76
79
  interface SettingsModalProps {
package/dist/react-ui.mjs CHANGED
@@ -10,6 +10,7 @@ import default3 from 'process';
10
10
  import { fileURLToPath } from 'url';
11
11
  import JSZip from 'jszip';
12
12
  import { useRouter } from 'next/navigation';
13
+ import Link3 from 'next/link';
13
14
 
14
15
  var __create = Object.create;
15
16
  var __defProp = Object.defineProperty;
@@ -167197,7 +167198,7 @@ var ClientTranslation = ({ tKey, options, fallback: fallback2 }) => {
167197
167198
  };
167198
167199
 
167199
167200
  // src/react-ui/components/app/PersonalPracticeDashboard.tsx
167200
- var PersonalPracticeDashboard = () => {
167201
+ var PersonalPracticeDashboard = ({ settingsPath }) => {
167201
167202
  const router = useRouter();
167202
167203
  const { toast: toast2 } = useToast();
167203
167204
  const { t: t4, i18n } = useTranslation();
@@ -167214,7 +167215,10 @@ var PersonalPracticeDashboard = () => {
167214
167215
  const [isChatbotOpen, setIsChatbotOpen] = useState(false);
167215
167216
  const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
167216
167217
  const [isEditMode, setIsEditMode] = useState(false);
167217
- const [dashboardLayout, setDashboardLayout] = useState(DashboardLayoutService.getLayout());
167218
+ const [dashboardLayout, setDashboardLayout] = useState(
167219
+ DashboardLayoutService.getLayout()
167220
+ );
167221
+ const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
167218
167222
  const loadDashboardData = useCallback(() => {
167219
167223
  setIsLoading(true);
167220
167224
  setDashboardLayout(DashboardLayoutService.getLayout());
@@ -167222,9 +167226,17 @@ var PersonalPracticeDashboard = () => {
167222
167226
  const practiceStats = PracticeHistoryService.getPracticeStats();
167223
167227
  const name3 = UserConfigService.getFullName();
167224
167228
  if (practiceStats && practiceHistory.length > 0) {
167225
- const newlyUnlocked = AchievementService.checkAndUnlockAchievements(practiceHistory, practiceStats);
167229
+ const newlyUnlocked = AchievementService.checkAndUnlockAchievements(
167230
+ practiceHistory,
167231
+ practiceStats
167232
+ );
167226
167233
  if (newlyUnlocked.length > 0) {
167227
- newlyUnlocked.forEach((ach) => toast2({ title: "\u{1F3C6} Th\xE0nh t\xEDch M\u1EDBi!", description: `B\u1EA1n \u0111\xE3 m\u1EDF kh\xF3a: ${t4(ach.nameKey)}` }));
167234
+ newlyUnlocked.forEach(
167235
+ (ach) => toast2({
167236
+ title: "\u{1F3C6} Th\xE0nh t\xEDch M\u1EDBi!",
167237
+ description: `B\u1EA1n \u0111\xE3 m\u1EDF kh\xF3a: ${t4(ach.nameKey)}`
167238
+ })
167239
+ );
167228
167240
  }
167229
167241
  }
167230
167242
  const achievementsWithStatus = AchievementService.getAllAchievementsWithStatus();
@@ -167246,32 +167258,55 @@ var PersonalPracticeDashboard = () => {
167246
167258
  const apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
167247
167259
  if (!apiKey || !stats) return;
167248
167260
  try {
167249
- const weakestTopic = stats.performanceByTopic.sort((a4, b2) => a4.averageScore - b2.averageScore)[0]?.name;
167250
- const aiQuote = await generateMotivationalQuote({
167251
- language: i18n.language === "vi" ? "Vietnamese" : "English",
167252
- userName: UserConfigService.getFullName() || void 0,
167253
- currentStreak: stats.currentStreak,
167254
- weakestTopic
167255
- }, apiKey);
167261
+ const weakestTopic = stats.performanceByTopic.sort(
167262
+ (a4, b2) => a4.averageScore - b2.averageScore
167263
+ )[0]?.name;
167264
+ const aiQuote = await generateMotivationalQuote(
167265
+ {
167266
+ language: i18n.language === "vi" ? "Vietnamese" : "English",
167267
+ userName: UserConfigService.getFullName() || void 0,
167268
+ currentStreak: stats.currentStreak,
167269
+ weakestTopic
167270
+ },
167271
+ apiKey
167272
+ );
167256
167273
  if (aiQuote) setMotivationalQuote(aiQuote);
167257
167274
  } catch (e3) {
167258
167275
  console.warn("Could not fetch AI-powered quote.", e3);
167259
167276
  }
167260
167277
  };
167261
167278
  if (stats) fetchAIQuote();
167262
- const intervalId = setInterval(() => setMotivationalQuote(QuoteService.getRandomQuote()), 10 * 60 * 1e3);
167279
+ const intervalId = setInterval(
167280
+ () => setMotivationalQuote(QuoteService.getRandomQuote()),
167281
+ 10 * 60 * 1e3
167282
+ );
167263
167283
  return () => clearInterval(intervalId);
167264
167284
  }, [stats, i18n.language]);
167265
167285
  const historySummaryForTable = useMemo(() => {
167266
- return history2.map((session) => ({ ...session.summary, id: session.id, timestamp: session.timestamp, quizTitle: session.quizConfig.title }));
167286
+ return history2.map((session) => ({
167287
+ ...session.summary,
167288
+ id: session.id,
167289
+ timestamp: session.timestamp,
167290
+ quizTitle: session.quizConfig.title
167291
+ }));
167267
167292
  }, [history2]);
167268
- const handleStartPractice = useCallback(() => router.push("/practice"), [router]);
167293
+ const handleStartPractice = useCallback(
167294
+ () => router.push("/practice"),
167295
+ [router]
167296
+ );
167269
167297
  const handleGenerateRoadmap = useCallback(async () => {
167270
167298
  setIsGeneratingRoadmap(true);
167271
- toast2({ title: "AI Tutor \u0111ang suy ngh\u0129...", description: "\u0110ang t\u1EA1o l\u1ED9 tr\xECnh h\u1ECDc t\u1EADp \u0111\u01B0\u1EE3c c\xE1 nh\xE2n h\xF3a cho b\u1EA1n." });
167299
+ toast2({
167300
+ title: "AI Tutor \u0111ang suy ngh\u0129...",
167301
+ description: "\u0110ang t\u1EA1o l\u1ED9 tr\xECnh h\u1ECDc t\u1EADp \u0111\u01B0\u1EE3c c\xE1 nh\xE2n h\xF3a cho b\u1EA1n."
167302
+ });
167272
167303
  const apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
167273
167304
  if (!apiKey) {
167274
- toast2({ title: "API Key Missing", description: "Vui l\xF2ng c\u1EA5u h\xECnh Gemini API Key trong C\xE0i \u0111\u1EB7t.", variant: "destructive" });
167305
+ toast2({
167306
+ title: "API Key Missing",
167307
+ description: "Vui l\xF2ng c\u1EA5u h\xECnh Gemini API Key trong C\xE0i \u0111\u1EB7t.",
167308
+ variant: "destructive"
167309
+ });
167275
167310
  setIsGeneratingRoadmap(false);
167276
167311
  return;
167277
167312
  }
@@ -167283,28 +167318,42 @@ var PersonalPracticeDashboard = () => {
167283
167318
  topic: lo.topic
167284
167319
  }));
167285
167320
  if (allAvailableTopics.length === 0) {
167286
- throw new Error("No topic data found. Please import curriculum data in Settings first.");
167321
+ throw new Error(
167322
+ "No topic data found. Please import curriculum data in Settings first."
167323
+ );
167287
167324
  }
167288
- const result = await generateLearningAnalysis({
167289
- language: i18n.language === "vi" ? "Vietnamese" : "English",
167290
- userName: UserConfigService.getFullName() || void 0,
167291
- weeklyGoal: UserConfigService.getWeeklyGoal() || void 0,
167292
- unlockedAchievements: AchievementService.getAllAchievementsWithStatus().filter((a4) => a4.unlockedAt),
167293
- stats: PracticeHistoryService.getPracticeStats(),
167294
- history: PracticeHistoryService.getPracticeHistory(),
167295
- startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
167296
- endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
167297
- allAvailableTopics
167298
- }, apiKey);
167325
+ const result = await generateLearningAnalysis(
167326
+ {
167327
+ language: i18n.language === "vi" ? "Vietnamese" : "English",
167328
+ userName: UserConfigService.getFullName() || void 0,
167329
+ weeklyGoal: UserConfigService.getWeeklyGoal() || void 0,
167330
+ unlockedAchievements: AchievementService.getAllAchievementsWithStatus().filter(
167331
+ (a4) => a4.unlockedAt
167332
+ ),
167333
+ stats: PracticeHistoryService.getPracticeStats(),
167334
+ history: PracticeHistoryService.getPracticeHistory(),
167335
+ startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
167336
+ endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
167337
+ allAvailableTopics
167338
+ },
167339
+ apiKey
167340
+ );
167299
167341
  if (result.weeklyRoadmap) {
167300
167342
  RoadmapService.saveRoadmap(result.weeklyRoadmap);
167301
- toast2({ title: "L\u1ED9 tr\xECnh M\u1EDBi \u0111\xE3 S\u1EB5n s\xE0ng!", description: "L\u1ED9 tr\xECnh h\u1ECDc t\u1EADp cho tu\u1EA7n n\xE0y \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt." });
167343
+ toast2({
167344
+ title: "L\u1ED9 tr\xECnh M\u1EDBi \u0111\xE3 S\u1EB5n s\xE0ng!",
167345
+ description: "L\u1ED9 tr\xECnh h\u1ECDc t\u1EADp cho tu\u1EA7n n\xE0y \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167346
+ });
167302
167347
  } else {
167303
167348
  throw new Error("AI \u0111\xE3 kh\xF4ng tr\u1EA3 v\u1EC1 m\u1ED9t l\u1ED9 tr\xECnh h\u1EE3p l\u1EC7.");
167304
167349
  }
167305
167350
  } catch (error) {
167306
167351
  const msg = error instanceof Error ? error.message : "\u0110\xE3 x\u1EA3y ra l\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh.";
167307
- toast2({ title: "T\u1EA1o L\u1ED9 tr\xECnh Th\u1EA5t b\u1EA1i", description: msg, variant: "destructive" });
167352
+ toast2({
167353
+ title: "T\u1EA1o L\u1ED9 tr\xECnh Th\u1EA5t b\u1EA1i",
167354
+ description: msg,
167355
+ variant: "destructive"
167356
+ });
167308
167357
  } finally {
167309
167358
  setIsGeneratingRoadmap(false);
167310
167359
  }
@@ -167328,14 +167377,26 @@ var PersonalPracticeDashboard = () => {
167328
167377
  if (!over || active.id === over.id) return;
167329
167378
  setDashboardLayout((prev) => {
167330
167379
  const newLayout = JSON.parse(JSON.stringify(prev));
167331
- const activeColKey = newLayout.column1.some((c4) => c4.id === active.id) ? "column1" : "column2";
167332
- const overColKey = newLayout.column1.some((c4) => c4.id === over.id) ? "column1" : "column2";
167380
+ const activeColKey = newLayout.column1.some(
167381
+ (c4) => c4.id === active.id
167382
+ ) ? "column1" : "column2";
167383
+ const overColKey = newLayout.column1.some(
167384
+ (c4) => c4.id === over.id
167385
+ ) ? "column1" : "column2";
167333
167386
  const activeCol = newLayout[activeColKey];
167334
167387
  const overCol = newLayout[overColKey];
167335
- const activeIndex = activeCol.findIndex((c4) => c4.id === active.id);
167336
- const overIndex = overCol.findIndex((c4) => c4.id === over.id);
167388
+ const activeIndex = activeCol.findIndex(
167389
+ (c4) => c4.id === active.id
167390
+ );
167391
+ const overIndex = overCol.findIndex(
167392
+ (c4) => c4.id === over.id
167393
+ );
167337
167394
  if (activeColKey === overColKey) {
167338
- newLayout[activeColKey] = arrayMove(activeCol, activeIndex, overIndex);
167395
+ newLayout[activeColKey] = arrayMove(
167396
+ activeCol,
167397
+ activeIndex,
167398
+ overIndex
167399
+ );
167339
167400
  } else {
167340
167401
  const [movedItem] = activeCol.splice(activeIndex, 1);
167341
167402
  overCol.splice(overIndex, 0, movedItem);
@@ -167346,7 +167407,10 @@ var PersonalPracticeDashboard = () => {
167346
167407
  const handleSaveLayout = () => {
167347
167408
  DashboardLayoutService.saveLayout(dashboardLayout);
167348
167409
  setIsEditMode(false);
167349
- toast2({ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u", description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt." });
167410
+ toast2({
167411
+ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u",
167412
+ description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167413
+ });
167350
167414
  };
167351
167415
  const handleCancelEdit = () => {
167352
167416
  setDashboardLayout(DashboardLayoutService.getLayout());
@@ -167367,7 +167431,218 @@ var PersonalPracticeDashboard = () => {
167367
167431
  const column2Items = dashboardLayout.column2.map((c4) => c4.id);
167368
167432
  const quoteText = isMounted ? motivationalQuote ? `"${motivationalQuote.text}" \u2014 ${motivationalQuote.author}` : t4("dashboard.motivationalQuoteFallback") : '"The best way to predict the future is to create it." \u2014 Peter Drucker';
167369
167433
  const welcomeMessage = isMounted ? userName ? t4("dashboard.welcomeMessage", { name: userName }) : t4("dashboard.welcomeMessageGuest") : userName ? `Welcome back, ${userName}!` : "Welcome to your Practice Dashboard";
167370
- return /* @__PURE__ */ React163__default.createElement(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd }, /* @__PURE__ */ React163__default.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React163__default.createElement(Card, { className: "text-center shadow-xl bg-gradient-to-br from-primary to-primary/80 text-primary-foreground border-none" }, /* @__PURE__ */ React163__default.createElement(CardHeader, { className: "p-8 relative" }, /* @__PURE__ */ React163__default.createElement("div", { className: "absolute top-4 right-4" }, !isEditMode ? /* @__PURE__ */ React163__default.createElement(Button, { variant: "secondary", size: "sm", onClick: () => setIsEditMode(true) }, /* @__PURE__ */ React163__default.createElement(Settings, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "common.customize", fallback: "Customize" })) : /* @__PURE__ */ React163__default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleCancelEdit, className: "bg-background/20 hover:bg-background/40 text-white" }, /* @__PURE__ */ React163__default.createElement(X, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "common.cancel", fallback: "Cancel" })), /* @__PURE__ */ React163__default.createElement(Button, { variant: "secondary", size: "sm", onClick: handleSaveLayout }, /* @__PURE__ */ React163__default.createElement(Save, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "common.saveLayout", fallback: "Save Layout" })))), /* @__PURE__ */ React163__default.createElement(CardTitle, { className: "text-3xl md:text-4xl font-bold tracking-tight" }, welcomeMessage), /* @__PURE__ */ React163__default.createElement(CardDescription, { className: "text-lg md:text-xl text-primary-foreground/80 max-w-3xl mx-auto mt-2 h-14 flex items-center justify-center" }, quoteText)), /* @__PURE__ */ React163__default.createElement(CardContent, { className: "pb-8 flex flex-wrap justify-center items-center gap-4" }, /* @__PURE__ */ React163__default.createElement(Button, { size: "lg", onClick: () => setIsUploadModalOpen(true), className: "bg-indigo-600 hover:bg-indigo-700 text-white text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__default.createElement(FileUp, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "dashboard.actions.createFromDoc", fallback: "Create Quiz from Document" })), /* @__PURE__ */ React163__default.createElement(Button, { size: "lg", onClick: handleStartPractice, className: "bg-amber-400 hover:bg-amber-500 text-amber-950 text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__default.createElement(BrainCircuit, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "dashboard.actions.freestyleTopic", fallback: "Freestyle Topic" })), /* @__PURE__ */ React163__default.createElement(AlertDialog2, null, /* @__PURE__ */ React163__default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React163__default.createElement(Button, { size: "lg", variant: "secondary", className: "px-8 py-6 text-base font-semibold", disabled: isGeneratingRoadmap }, /* @__PURE__ */ React163__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "dashboard.actions.suggestRoadmap", fallback: "Suggest Next Week's Roadmap" }))), /* @__PURE__ */ React163__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React163__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React163__default.createElement(AlertDialogTitle2, null, t4("dialogs.confirmRoadmap.title")), /* @__PURE__ */ React163__default.createElement(AlertDialogDescription2, null, t4("dialogs.confirmRoadmap.description"))), /* @__PURE__ */ React163__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React163__default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React163__default.createElement(AlertDialogAction2, { onClick: handleGenerateRoadmap }, t4("dialogs.confirmRoadmap.action"))))), /* @__PURE__ */ React163__default.createElement(Button, { size: "lg", onClick: () => setIsAnalysisDialogOpen(true), className: "bg-blue-600 hover:bg-blue-700 text-white text-base font-semibold px-8 py-6" }, /* @__PURE__ */ React163__default.createElement(BarChart4, { className: "mr-2 h-5 w-5" }), /* @__PURE__ */ React163__default.createElement(ClientTranslation, { tKey: "dashboard.actions.deepAnalysis", fallback: "Deep Analysis" })))), /* @__PURE__ */ React163__default.createElement(PerformanceSnapshot, { stats, isLoading }), /* @__PURE__ */ React163__default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 items-start" }, /* @__PURE__ */ React163__default.createElement(SortableContext, { items: column1Items, strategy: verticalListSortingStrategy }, /* @__PURE__ */ React163__default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column1.map((cardConfig) => /* @__PURE__ */ React163__default.createElement(DraggableDashboardCard, { key: cardConfig.id, id: cardConfig.id, visible: cardConfig.visible, isEditMode, onToggleVisibility: handleToggleVisibility }, cardComponents[cardConfig.id])))), /* @__PURE__ */ React163__default.createElement(SortableContext, { items: column2Items, strategy: verticalListSortingStrategy }, /* @__PURE__ */ React163__default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column2.map((cardConfig) => /* @__PURE__ */ React163__default.createElement(DraggableDashboardCard, { key: cardConfig.id, id: cardConfig.id, visible: cardConfig.visible, isEditMode, onToggleVisibility: handleToggleVisibility }, cardComponents[cardConfig.id]))))), /* @__PURE__ */ React163__default.createElement(AnalysisDialog, { isOpen: isAnalysisDialogOpen, onClose: () => setIsAnalysisDialogOpen(false) }), /* @__PURE__ */ React163__default.createElement("div", { className: "fixed bottom-6 right-6 z-50" }, /* @__PURE__ */ React163__default.createElement(Button, { size: "icon", className: "rounded-full h-14 w-14 shadow-lg bg-primary hover:bg-primary/90", onClick: () => setIsChatbotOpen(true), "aria-label": "Open AI Tutor Chat" }, /* @__PURE__ */ React163__default.createElement(Bot, { className: "h-7 w-7" }))), /* @__PURE__ */ React163__default.createElement(ChatbotDialog, { isOpen: isChatbotOpen, onClose: () => setIsChatbotOpen(false) }), /* @__PURE__ */ React163__default.createElement(UploadResourceModal, { isOpen: isUploadModalOpen, onClose: () => setIsUploadModalOpen(false) })));
167434
+ return /* @__PURE__ */ React163__default.createElement(
167435
+ DndContext,
167436
+ {
167437
+ sensors,
167438
+ collisionDetection: closestCenter,
167439
+ onDragEnd: handleDragEnd
167440
+ },
167441
+ /* @__PURE__ */ React163__default.createElement("div", { className: "space-y-8" }, /* @__PURE__ */ React163__default.createElement(Card, { className: "text-center shadow-xl bg-gradient-to-br from-primary to-primary/80 text-primary-foreground border-none" }, /* @__PURE__ */ React163__default.createElement(CardHeader, { className: "p-8 relative" }, /* @__PURE__ */ React163__default.createElement("div", { className: "absolute top-4 right-4 flex items-center gap-2" }, !isEditMode ? /* @__PURE__ */ React163__default.createElement(
167442
+ Button,
167443
+ {
167444
+ variant: "secondary",
167445
+ size: "sm",
167446
+ onClick: () => setIsEditMode(true)
167447
+ },
167448
+ /* @__PURE__ */ React163__default.createElement(LayoutDashboard, { className: "mr-2 h-4 w-4" }),
167449
+ /* @__PURE__ */ React163__default.createElement(
167450
+ ClientTranslation,
167451
+ {
167452
+ tKey: "common.customizeLayout",
167453
+ fallback: "Customize Layout"
167454
+ }
167455
+ )
167456
+ ) : /* @__PURE__ */ React163__default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__default.createElement(
167457
+ Button,
167458
+ {
167459
+ variant: "ghost",
167460
+ size: "sm",
167461
+ onClick: handleCancelEdit,
167462
+ className: "bg-background/20 hover:bg-background/40 text-white"
167463
+ },
167464
+ /* @__PURE__ */ React163__default.createElement(X, { className: "mr-2 h-4 w-4" }),
167465
+ /* @__PURE__ */ React163__default.createElement(
167466
+ ClientTranslation,
167467
+ {
167468
+ tKey: "common.cancel",
167469
+ fallback: "Cancel"
167470
+ }
167471
+ )
167472
+ ), /* @__PURE__ */ React163__default.createElement(
167473
+ Button,
167474
+ {
167475
+ variant: "secondary",
167476
+ size: "sm",
167477
+ onClick: handleSaveLayout
167478
+ },
167479
+ /* @__PURE__ */ React163__default.createElement(Save, { className: "mr-2 h-4 w-4" }),
167480
+ /* @__PURE__ */ React163__default.createElement(
167481
+ ClientTranslation,
167482
+ {
167483
+ tKey: "common.saveLayout",
167484
+ fallback: "Save Layout"
167485
+ }
167486
+ )
167487
+ )), settingsPath ? /* @__PURE__ */ React163__default.createElement(
167488
+ Button,
167489
+ {
167490
+ asChild: true,
167491
+ variant: "ghost",
167492
+ size: "icon",
167493
+ className: "text-white hover:bg-background/20",
167494
+ title: "Settings"
167495
+ },
167496
+ /* @__PURE__ */ React163__default.createElement(Link3, { href: settingsPath }, /* @__PURE__ */ React163__default.createElement(Settings, { className: "h-5 w-5" }))
167497
+ ) : /* @__PURE__ */ React163__default.createElement(
167498
+ Button,
167499
+ {
167500
+ variant: "ghost",
167501
+ size: "icon",
167502
+ className: "text-white hover:bg-background/20",
167503
+ title: "Settings",
167504
+ onClick: () => setIsSettingsModalOpen(true)
167505
+ },
167506
+ /* @__PURE__ */ React163__default.createElement(Settings, { className: "h-5 w-5" })
167507
+ )), /* @__PURE__ */ React163__default.createElement(CardTitle, { className: "text-3xl md:text-4xl font-bold tracking-tight" }, welcomeMessage), /* @__PURE__ */ React163__default.createElement(CardDescription, { className: "text-lg md:text-xl text-primary-foreground/80 max-w-3xl mx-auto mt-2 h-14 flex items-center justify-center" }, quoteText)), /* @__PURE__ */ React163__default.createElement(CardContent, { className: "pb-8 flex flex-wrap justify-center items-center gap-4" }, /* @__PURE__ */ React163__default.createElement(
167508
+ Button,
167509
+ {
167510
+ size: "lg",
167511
+ onClick: () => setIsUploadModalOpen(true),
167512
+ className: "bg-indigo-600 hover:bg-indigo-700 text-white text-base font-semibold px-8 py-6"
167513
+ },
167514
+ /* @__PURE__ */ React163__default.createElement(FileUp, { className: "mr-2 h-5 w-5" }),
167515
+ /* @__PURE__ */ React163__default.createElement(
167516
+ ClientTranslation,
167517
+ {
167518
+ tKey: "dashboard.actions.createFromDoc",
167519
+ fallback: "Create Quiz from Document"
167520
+ }
167521
+ )
167522
+ ), /* @__PURE__ */ React163__default.createElement(
167523
+ Button,
167524
+ {
167525
+ size: "lg",
167526
+ onClick: handleStartPractice,
167527
+ className: "bg-amber-400 hover:bg-amber-500 text-amber-950 text-base font-semibold px-8 py-6"
167528
+ },
167529
+ /* @__PURE__ */ React163__default.createElement(BrainCircuit, { className: "mr-2 h-5 w-5" }),
167530
+ /* @__PURE__ */ React163__default.createElement(
167531
+ ClientTranslation,
167532
+ {
167533
+ tKey: "dashboard.actions.freestyleTopic",
167534
+ fallback: "Freestyle Topic"
167535
+ }
167536
+ )
167537
+ ), /* @__PURE__ */ React163__default.createElement(AlertDialog2, null, /* @__PURE__ */ React163__default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React163__default.createElement(
167538
+ Button,
167539
+ {
167540
+ size: "lg",
167541
+ variant: "secondary",
167542
+ className: "px-8 py-6 text-base font-semibold",
167543
+ disabled: isGeneratingRoadmap
167544
+ },
167545
+ /* @__PURE__ */ React163__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5" }),
167546
+ /* @__PURE__ */ React163__default.createElement(
167547
+ ClientTranslation,
167548
+ {
167549
+ tKey: "dashboard.actions.suggestRoadmap",
167550
+ fallback: "Suggest Next Week's Roadmap"
167551
+ }
167552
+ )
167553
+ )), /* @__PURE__ */ React163__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React163__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React163__default.createElement(AlertDialogTitle2, null, t4("dialogs.confirmRoadmap.title")), /* @__PURE__ */ React163__default.createElement(AlertDialogDescription2, null, t4(
167554
+ "dialogs.confirmRoadmap.description"
167555
+ ))), /* @__PURE__ */ React163__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React163__default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React163__default.createElement(
167556
+ AlertDialogAction2,
167557
+ {
167558
+ onClick: handleGenerateRoadmap
167559
+ },
167560
+ t4("dialogs.confirmRoadmap.action")
167561
+ )))), /* @__PURE__ */ React163__default.createElement(
167562
+ Button,
167563
+ {
167564
+ size: "lg",
167565
+ onClick: () => setIsAnalysisDialogOpen(true),
167566
+ className: "bg-blue-600 hover:bg-blue-700 text-white text-base font-semibold px-8 py-6"
167567
+ },
167568
+ /* @__PURE__ */ React163__default.createElement(BarChart4, { className: "mr-2 h-5 w-5" }),
167569
+ /* @__PURE__ */ React163__default.createElement(
167570
+ ClientTranslation,
167571
+ {
167572
+ tKey: "dashboard.actions.deepAnalysis",
167573
+ fallback: "Deep Analysis"
167574
+ }
167575
+ )
167576
+ ))), /* @__PURE__ */ React163__default.createElement(PerformanceSnapshot, { stats, isLoading }), /* @__PURE__ */ React163__default.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 items-start" }, /* @__PURE__ */ React163__default.createElement(
167577
+ SortableContext,
167578
+ {
167579
+ items: column1Items,
167580
+ strategy: verticalListSortingStrategy
167581
+ },
167582
+ /* @__PURE__ */ React163__default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column1.map((cardConfig) => /* @__PURE__ */ React163__default.createElement(
167583
+ DraggableDashboardCard,
167584
+ {
167585
+ key: cardConfig.id,
167586
+ id: cardConfig.id,
167587
+ visible: cardConfig.visible,
167588
+ isEditMode,
167589
+ onToggleVisibility: handleToggleVisibility
167590
+ },
167591
+ cardComponents[cardConfig.id]
167592
+ )))
167593
+ ), /* @__PURE__ */ React163__default.createElement(
167594
+ SortableContext,
167595
+ {
167596
+ items: column2Items,
167597
+ strategy: verticalListSortingStrategy
167598
+ },
167599
+ /* @__PURE__ */ React163__default.createElement("div", { className: "flex flex-col gap-8" }, dashboardLayout.column2.map((cardConfig) => /* @__PURE__ */ React163__default.createElement(
167600
+ DraggableDashboardCard,
167601
+ {
167602
+ key: cardConfig.id,
167603
+ id: cardConfig.id,
167604
+ visible: cardConfig.visible,
167605
+ isEditMode,
167606
+ onToggleVisibility: handleToggleVisibility
167607
+ },
167608
+ cardComponents[cardConfig.id]
167609
+ )))
167610
+ )), /* @__PURE__ */ React163__default.createElement(
167611
+ AnalysisDialog,
167612
+ {
167613
+ isOpen: isAnalysisDialogOpen,
167614
+ onClose: () => setIsAnalysisDialogOpen(false)
167615
+ }
167616
+ ), /* @__PURE__ */ React163__default.createElement("div", { className: "fixed bottom-6 right-6 z-50" }, /* @__PURE__ */ React163__default.createElement(
167617
+ Button,
167618
+ {
167619
+ size: "icon",
167620
+ className: "rounded-full h-14 w-14 shadow-lg bg-primary hover:bg-primary/90",
167621
+ onClick: () => setIsChatbotOpen(true),
167622
+ "aria-label": "Open AI Tutor Chat"
167623
+ },
167624
+ /* @__PURE__ */ React163__default.createElement(Bot, { className: "h-7 w-7" })
167625
+ )), /* @__PURE__ */ React163__default.createElement(
167626
+ ChatbotDialog,
167627
+ {
167628
+ isOpen: isChatbotOpen,
167629
+ onClose: () => setIsChatbotOpen(false)
167630
+ }
167631
+ ), /* @__PURE__ */ React163__default.createElement(
167632
+ UploadResourceModal,
167633
+ {
167634
+ isOpen: isUploadModalOpen,
167635
+ onClose: () => setIsUploadModalOpen(false)
167636
+ }
167637
+ )),
167638
+ !settingsPath && /* @__PURE__ */ React163__default.createElement(
167639
+ SettingsModal,
167640
+ {
167641
+ isOpen: isSettingsModalOpen,
167642
+ onClose: () => setIsSettingsModalOpen(false)
167643
+ }
167644
+ )
167645
+ );
167371
167646
  };
167372
167647
 
167373
167648
  // src/react-ui/components/practice/PracticeModeController.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
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",