@thanh01.pmt/interactive-quiz-kit 1.0.45 → 1.0.47

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.
@@ -3980,6 +3980,11 @@ var UserConfigService = class {
3980
3980
  init_react_shim();
3981
3981
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
3982
3982
  var PracticeHistoryService = class {
3983
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
3984
+ static configure(provider) {
3985
+ this.syncProvider = provider.syncProvider;
3986
+ console.log("PracticeHistoryService configured with a sync provider.");
3987
+ }
3983
3988
  static getPracticeHistory() {
3984
3989
  if (typeof window === "undefined") return [];
3985
3990
  try {
@@ -4023,6 +4028,12 @@ var PracticeHistoryService = class {
4023
4028
  };
4024
4029
  history2.unshift(newSession);
4025
4030
  this.saveHistory(history2);
4031
+ if (this.syncProvider) {
4032
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
4033
+ this.syncProvider.pushSession(newSession).catch((err) => {
4034
+ console.error("Sync provider failed to push session:", err);
4035
+ });
4036
+ }
4026
4037
  return newSession.id;
4027
4038
  }
4028
4039
  static updatePracticeReview(sessionId, review) {
@@ -4155,6 +4166,8 @@ var PracticeHistoryService = class {
4155
4166
  }
4156
4167
  }
4157
4168
  };
4169
+ // NEW: A static property to hold the injected sync provider
4170
+ PracticeHistoryService.syncProvider = null;
4158
4171
 
4159
4172
  // src/services/AchievementService.ts
4160
4173
  init_react_shim();
@@ -3954,6 +3954,11 @@ var UserConfigService = class {
3954
3954
  init_react_shim();
3955
3955
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
3956
3956
  var PracticeHistoryService = class {
3957
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
3958
+ static configure(provider) {
3959
+ this.syncProvider = provider.syncProvider;
3960
+ console.log("PracticeHistoryService configured with a sync provider.");
3961
+ }
3957
3962
  static getPracticeHistory() {
3958
3963
  if (typeof window === "undefined") return [];
3959
3964
  try {
@@ -3997,6 +4002,12 @@ var PracticeHistoryService = class {
3997
4002
  };
3998
4003
  history2.unshift(newSession);
3999
4004
  this.saveHistory(history2);
4005
+ if (this.syncProvider) {
4006
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
4007
+ this.syncProvider.pushSession(newSession).catch((err) => {
4008
+ console.error("Sync provider failed to push session:", err);
4009
+ });
4010
+ }
4000
4011
  return newSession.id;
4001
4012
  }
4002
4013
  static updatePracticeReview(sessionId, review) {
@@ -4129,6 +4140,8 @@ var PracticeHistoryService = class {
4129
4140
  }
4130
4141
  }
4131
4142
  };
4143
+ // NEW: A static property to hold the injected sync provider
4144
+ PracticeHistoryService.syncProvider = null;
4132
4145
 
4133
4146
  // src/services/AchievementService.ts
4134
4147
  init_react_shim();
package/dist/index.cjs CHANGED
@@ -1946,6 +1946,11 @@ var UserConfigService = class {
1946
1946
  // src/services/PracticeHistoryService.ts
1947
1947
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
1948
1948
  var PracticeHistoryService = class {
1949
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
1950
+ static configure(provider) {
1951
+ this.syncProvider = provider.syncProvider;
1952
+ console.log("PracticeHistoryService configured with a sync provider.");
1953
+ }
1949
1954
  static getPracticeHistory() {
1950
1955
  if (typeof window === "undefined") return [];
1951
1956
  try {
@@ -1989,6 +1994,12 @@ var PracticeHistoryService = class {
1989
1994
  };
1990
1995
  history.unshift(newSession);
1991
1996
  this.saveHistory(history);
1997
+ if (this.syncProvider) {
1998
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
1999
+ this.syncProvider.pushSession(newSession).catch((err) => {
2000
+ console.error("Sync provider failed to push session:", err);
2001
+ });
2002
+ }
1992
2003
  return newSession.id;
1993
2004
  }
1994
2005
  static updatePracticeReview(sessionId, review) {
@@ -2121,6 +2132,8 @@ var PracticeHistoryService = class {
2121
2132
  }
2122
2133
  }
2123
2134
  };
2135
+ // NEW: A static property to hold the injected sync provider
2136
+ PracticeHistoryService.syncProvider = null;
2124
2137
 
2125
2138
  // src/data/achievements.json
2126
2139
  var achievements_default = [
package/dist/index.d.cts CHANGED
@@ -230,7 +230,14 @@ declare class UserConfigService {
230
230
  static deleteGoal(goalId: string): void;
231
231
  }
232
232
 
233
+ interface SyncProvider {
234
+ pushSession: (session: PracticeSession) => Promise<void>;
235
+ }
233
236
  declare class PracticeHistoryService {
237
+ private static syncProvider;
238
+ static configure(provider: {
239
+ syncProvider: SyncProvider;
240
+ }): void;
234
241
  static getPracticeHistory(): PracticeSession[];
235
242
  private static saveHistory;
236
243
  static saveCompletedPracticeSession(quizConfig: QuizConfig, result: QuizResultType, review?: QuizReviewContent | null): string;
package/dist/index.d.ts CHANGED
@@ -230,7 +230,14 @@ declare class UserConfigService {
230
230
  static deleteGoal(goalId: string): void;
231
231
  }
232
232
 
233
+ interface SyncProvider {
234
+ pushSession: (session: PracticeSession) => Promise<void>;
235
+ }
233
236
  declare class PracticeHistoryService {
237
+ private static syncProvider;
238
+ static configure(provider: {
239
+ syncProvider: SyncProvider;
240
+ }): void;
234
241
  static getPracticeHistory(): PracticeSession[];
235
242
  private static saveHistory;
236
243
  static saveCompletedPracticeSession(quizConfig: QuizConfig, result: QuizResultType, review?: QuizReviewContent | null): string;
package/dist/index.mjs CHANGED
@@ -1940,6 +1940,11 @@ var UserConfigService = class {
1940
1940
  // src/services/PracticeHistoryService.ts
1941
1941
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
1942
1942
  var PracticeHistoryService = class {
1943
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
1944
+ static configure(provider) {
1945
+ this.syncProvider = provider.syncProvider;
1946
+ console.log("PracticeHistoryService configured with a sync provider.");
1947
+ }
1943
1948
  static getPracticeHistory() {
1944
1949
  if (typeof window === "undefined") return [];
1945
1950
  try {
@@ -1983,6 +1988,12 @@ var PracticeHistoryService = class {
1983
1988
  };
1984
1989
  history.unshift(newSession);
1985
1990
  this.saveHistory(history);
1991
+ if (this.syncProvider) {
1992
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
1993
+ this.syncProvider.pushSession(newSession).catch((err) => {
1994
+ console.error("Sync provider failed to push session:", err);
1995
+ });
1996
+ }
1986
1997
  return newSession.id;
1987
1998
  }
1988
1999
  static updatePracticeReview(sessionId, review) {
@@ -2115,6 +2126,8 @@ var PracticeHistoryService = class {
2115
2126
  }
2116
2127
  }
2117
2128
  };
2129
+ // NEW: A static property to hold the injected sync provider
2130
+ PracticeHistoryService.syncProvider = null;
2118
2131
 
2119
2132
  // src/data/achievements.json
2120
2133
  var achievements_default = [
package/dist/react-ui.cjs CHANGED
@@ -139570,6 +139570,11 @@ AlertDialogCancel2.displayName = Cancel.displayName;
139570
139570
  init_react_shim();
139571
139571
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
139572
139572
  var PracticeHistoryService = class {
139573
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
139574
+ static configure(provider) {
139575
+ this.syncProvider = provider.syncProvider;
139576
+ console.log("PracticeHistoryService configured with a sync provider.");
139577
+ }
139573
139578
  static getPracticeHistory() {
139574
139579
  if (typeof window === "undefined") return [];
139575
139580
  try {
@@ -139613,6 +139618,12 @@ var PracticeHistoryService = class {
139613
139618
  };
139614
139619
  history2.unshift(newSession);
139615
139620
  this.saveHistory(history2);
139621
+ if (this.syncProvider) {
139622
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
139623
+ this.syncProvider.pushSession(newSession).catch((err) => {
139624
+ console.error("Sync provider failed to push session:", err);
139625
+ });
139626
+ }
139616
139627
  return newSession.id;
139617
139628
  }
139618
139629
  static updatePracticeReview(sessionId, review) {
@@ -139745,6 +139756,8 @@ var PracticeHistoryService = class {
139745
139756
  }
139746
139757
  }
139747
139758
  };
139759
+ // NEW: A static property to hold the injected sync provider
139760
+ PracticeHistoryService.syncProvider = null;
139748
139761
 
139749
139762
  // src/services/RoadmapService.ts
139750
139763
  init_react_shim();
@@ -142551,7 +142564,7 @@ function z29(e3 = x) {
142551
142564
  }
142552
142565
  var B = { tooltip: "core-styles-module_tooltip__3vRRp", fixed: "core-styles-module_fixed__pcSol", arrow: "core-styles-module_arrow__cvMwQ", noArrow: "core-styles-module_noArrow__xock6", clickable: "core-styles-module_clickable__ZuTTB", show: "core-styles-module_show__Nt9eE", closing: "core-styles-module_closing__sGnxF" };
142553
142566
  var D2 = { tooltip: "styles-module_tooltip__mnnfp", arrow: "styles-module_arrow__K0L3T", dark: "styles-module_dark__xNqje", light: "styles-module_light__Z6W-X", success: "styles-module_success__A2AKt", warning: "styles-module_warning__SCK0X", error: "styles-module_error__JvumD", info: "styles-module_info__BWdHW" };
142554
- var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u2 = "dark", anchorId: d, anchorSelect: p2, place: v = "top", offset: m = 10, events: h3 = ["hover"], openOnClick: w2 = false, positionStrategy: b2 = "absolute", middlewares: S3, wrapper: g, delayShow: A3 = 0, delayHide: O2 = 0, float: T3 = false, hidden: x3 = false, noArrow: N2 = false, clickable: $3 = false, closeOnEsc: I = false, closeOnScroll: j = false, closeOnResize: q2 = false, openEvents: H2, closeEvents: M2, globalCloseEvents: W, imperativeModeOnly: P2, style: V, position: F, afterShow: K, afterHide: U2, disableTooltip: X3, content: Y, contentWrapperRef: G, isOpen: Z2, defaultIsOpen: J = false, setIsOpen: Q, activeAnchor: ee, setActiveAnchor: te, border: oe, opacity: le, arrowColor: re3, arrowSize: ne = 8, role: ie3 = "tooltip" }) => {
142567
+ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u2 = "dark", anchorId: d, anchorSelect: p2, place: v = "top", offset: m = 10, events: h3 = ["hover"], openOnClick: w2 = false, positionStrategy: b2 = "absolute", middlewares: S3, wrapper: g, delayShow: A3 = 0, delayHide: O2 = 0, float: T3 = false, hidden: x3 = false, noArrow: N2 = false, clickable: $3 = false, closeOnEsc: I = false, closeOnScroll: j = false, closeOnResize: q2 = false, openEvents: H2, closeEvents: M2, globalCloseEvents: W, imperativeModeOnly: P2, style: V, position: F, afterShow: K, afterHide: U2, disableTooltip: X2, content: Y, contentWrapperRef: G, isOpen: Z2, defaultIsOpen: J = false, setIsOpen: Q, activeAnchor: ee, setActiveAnchor: te, border: oe, opacity: le, arrowColor: re3, arrowSize: ne = 8, role: ie3 = "tooltip" }) => {
142555
142568
  var ce;
142556
142569
  const se = React163.useRef(null), ae = React163.useRef(null), ue = React163.useRef(null), de = React163.useRef(null), pe = React163.useRef(null), [ve, me] = React163.useState({ tooltipStyles: {}, tooltipArrowStyles: {}, place: v }), [fe, ye] = React163.useState(false), [he, we] = React163.useState(false), [be, Se] = React163.useState(null), ge = React163.useRef(false), Ee = React163.useRef(null), { anchorRefs: Ae, setActiveAnchor: _e } = z29(l2), Oe = React163.useRef(false), [ke, Te] = React163.useState([]), Le = React163.useRef(false), Ce = w2 || h3.includes("click"), Re = Ce || (null == H2 ? void 0 : H2.click) || (null == H2 ? void 0 : H2.dblclick) || (null == H2 ? void 0 : H2.mousedown), xe = H2 ? { ...H2 } : { mouseover: true, focus: true, mouseenter: false, click: false, dblclick: false, mousedown: false };
142557
142570
  !H2 && Ce && Object.assign(xe, { mouseenter: false, focus: false, mouseover: false, click: true });
@@ -142639,10 +142652,10 @@ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u
142639
142652
  var e3, t5;
142640
142653
  const o2 = new Set(Ae);
142641
142654
  ke.forEach((e4) => {
142642
- (null == X3 ? void 0 : X3(e4)) || o2.add({ current: e4 });
142655
+ (null == X2 ? void 0 : X2(e4)) || o2.add({ current: e4 });
142643
142656
  });
142644
142657
  const l3 = document.querySelector(`[id='${d}']`);
142645
- l3 && !(null == X3 ? void 0 : X3(l3)) && o2.add({ current: l3 });
142658
+ l3 && !(null == X2 ? void 0 : X2(l3)) && o2.add({ current: l3 });
142646
142659
  const r4 = () => {
142647
142660
  Ie(false);
142648
142661
  }, n2 = L2(ee), i3 = L2(se.current);
@@ -142758,7 +142771,7 @@ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u
142758
142771
  }, style: { ...V, ...ve.tooltipStyles, opacity: void 0 !== le && Ye ? le : void 0 }, ref: se }, Xe, React163__namespace.default.createElement(g, { className: (0, import_classnames.default)("react-tooltip-arrow", B.arrow, D2.arrow, c4, N2 && B.noArrow), style: { ...ve.tooltipArrowStyles, background: re3 ? `linear-gradient(to right bottom, transparent 50%, ${re3} 50%)` : void 0, "--rt-arrow-size": `${ne}px` }, ref: ae })) : null;
142759
142772
  };
142760
142773
  var H = ({ content: t4 }) => React163__namespace.default.createElement("span", { dangerouslySetInnerHTML: { __html: t4 } });
142761
- var M = React163__namespace.default.forwardRef(({ id: t4, anchorId: l2, anchorSelect: n2, content: i2, html: c4, render: a4, className: u2, classNameArrow: d, variant: p2 = "dark", place: v = "top", offset: m = 10, wrapper: f = "div", children: h3 = null, events: w2 = ["hover"], openOnClick: b2 = false, positionStrategy: S3 = "absolute", middlewares: g, delayShow: E2 = 0, delayHide: _2 = 0, float: O2 = false, hidden: k3 = false, noArrow: T3 = false, clickable: L3 = false, closeOnEsc: C3 = false, closeOnScroll: R3 = false, closeOnResize: x3 = false, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j = false, style: B2, position: D3, isOpen: M2, defaultIsOpen: W = false, disableStyleInjection: P2 = false, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X3, afterShow: Y, afterHide: G, disableTooltip: Z2, role: J = "tooltip" }, Q) => {
142774
+ var M = React163__namespace.default.forwardRef(({ id: t4, anchorId: l2, anchorSelect: n2, content: i2, html: c4, render: a4, className: u2, classNameArrow: d, variant: p2 = "dark", place: v = "top", offset: m = 10, wrapper: f = "div", children: h3 = null, events: w2 = ["hover"], openOnClick: b2 = false, positionStrategy: S3 = "absolute", middlewares: g, delayShow: E2 = 0, delayHide: _2 = 0, float: O2 = false, hidden: k3 = false, noArrow: T3 = false, clickable: L3 = false, closeOnEsc: C3 = false, closeOnScroll: R3 = false, closeOnResize: x3 = false, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j = false, style: B2, position: D3, isOpen: M2, defaultIsOpen: W = false, disableStyleInjection: P2 = false, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X2, afterShow: Y, afterHide: G, disableTooltip: Z2, role: J = "tooltip" }, Q) => {
142762
142775
  const [ee, te] = React163.useState(i2), [oe, le] = React163.useState(c4), [re3, ne] = React163.useState(v), [ie3, ce] = React163.useState(p2), [se, ae] = React163.useState(m), [ue, de] = React163.useState(E2), [pe, ve] = React163.useState(_2), [me, fe] = React163.useState(O2), [ye, he] = React163.useState(k3), [we, be] = React163.useState(f), [Se, ge] = React163.useState(w2), [Ee, Ae] = React163.useState(S3), [_e, Oe] = React163.useState(null), [ke, Te] = React163.useState(null), Le = React163.useRef(P2), { anchorRefs: Ce, activeAnchor: Re } = z29(t4), xe = (e3) => null == e3 ? void 0 : e3.getAttributeNames().reduce((t5, o2) => {
142763
142776
  var l3;
142764
142777
  if (o2.startsWith("data-tooltip-")) {
@@ -142865,7 +142878,7 @@ var M = React163__namespace.default.forwardRef(({ id: t4, anchorId: l2, anchorSe
142865
142878
  $e = t5 ? React163__namespace.default.createElement("div", { ref: Ie, className: "react-tooltip-content-wrapper" }, t5) : null;
142866
142879
  } else ee && ($e = ee);
142867
142880
  oe && ($e = React163__namespace.default.createElement(H, { content: oe }));
142868
- const ze = { forwardRef: Q, id: t4, anchorId: l2, anchorSelect: n2, className: (0, import_classnames.default)(u2, _e), classNameArrow: d, content: $e, contentWrapperRef: Ie, place: re3, variant: ie3, offset: se, wrapper: we, events: Se, openOnClick: b2, positionStrategy: Ee, middlewares: g, delayShow: ue, delayHide: pe, float: me, hidden: ye, noArrow: T3, clickable: L3, closeOnEsc: C3, closeOnScroll: R3, closeOnResize: x3, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j, style: B2, position: D3, isOpen: M2, defaultIsOpen: W, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X3, afterShow: Y, afterHide: G, disableTooltip: Z2, activeAnchor: ke, setActiveAnchor: (e3) => Te(e3), role: J };
142881
+ const ze = { forwardRef: Q, id: t4, anchorId: l2, anchorSelect: n2, className: (0, import_classnames.default)(u2, _e), classNameArrow: d, content: $e, contentWrapperRef: Ie, place: re3, variant: ie3, offset: se, wrapper: we, events: Se, openOnClick: b2, positionStrategy: Ee, middlewares: g, delayShow: ue, delayHide: pe, float: me, hidden: ye, noArrow: T3, clickable: L3, closeOnEsc: C3, closeOnScroll: R3, closeOnResize: x3, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j, style: B2, position: D3, isOpen: M2, defaultIsOpen: W, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X2, afterShow: Y, afterHide: G, disableTooltip: Z2, activeAnchor: ke, setActiveAnchor: (e3) => Te(e3), role: J };
142869
142882
  return React163__namespace.default.createElement(q, { ...ze });
142870
142883
  });
142871
142884
  "undefined" != typeof window && window.addEventListener("react-tooltip-inject-styles", (e3) => {
@@ -167431,6 +167444,18 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167431
167444
  return newLayout;
167432
167445
  });
167433
167446
  };
167447
+ const handleSaveLayout = () => {
167448
+ DashboardLayoutService.saveLayout(dashboardLayout);
167449
+ setIsEditMode(false);
167450
+ toast2({
167451
+ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u",
167452
+ description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167453
+ });
167454
+ };
167455
+ const handleCancelEdit = () => {
167456
+ setDashboardLayout(DashboardLayoutService.getLayout());
167457
+ setIsEditMode(false);
167458
+ };
167434
167459
  const cardComponents = {
167435
167460
  roadmap: /* @__PURE__ */ React163__namespace.default.createElement(RoadmapChecklist, null),
167436
167461
  activity: /* @__PURE__ */ React163__namespace.default.createElement(ActivityCalendar, { stats }),
@@ -167453,33 +167478,72 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167453
167478
  collisionDetection: closestCenter,
167454
167479
  onDragEnd: handleDragEnd
167455
167480
  },
167456
- /* @__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" }, settingsPath ? (
167457
- // If settingsPath is provided, always render a Link regardless of edit mode
167458
- /* @__PURE__ */ React163__namespace.default.createElement(Button, { asChild: true, variant: "secondary", size: "sm" }, /* @__PURE__ */ React163__namespace.default.createElement(Link3__default.default, { href: settingsPath }, /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__namespace.default.createElement(
167481
+ /* @__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(
167482
+ Button,
167483
+ {
167484
+ variant: "secondary",
167485
+ size: "sm",
167486
+ onClick: () => setIsEditMode(true)
167487
+ },
167488
+ /* @__PURE__ */ React163__namespace.default.createElement(LayoutDashboard, { className: "mr-2 h-4 w-4" }),
167489
+ /* @__PURE__ */ React163__namespace.default.createElement(
167459
167490
  ClientTranslation,
167460
167491
  {
167461
- tKey: "common.customize",
167462
- fallback: "Customize"
167492
+ tKey: "common.customizeLayout",
167493
+ fallback: "Customize Layout"
167463
167494
  }
167464
- )))
167465
- ) : (
167466
- // If no settingsPath, always open the modal regardless of edit mode
167495
+ )
167496
+ ) : /* @__PURE__ */ React163__namespace.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__namespace.default.createElement(
167497
+ Button,
167498
+ {
167499
+ variant: "ghost",
167500
+ size: "sm",
167501
+ onClick: handleCancelEdit,
167502
+ className: "bg-background/20 hover:bg-background/40 text-white"
167503
+ },
167504
+ /* @__PURE__ */ React163__namespace.default.createElement(X, { className: "mr-2 h-4 w-4" }),
167467
167505
  /* @__PURE__ */ React163__namespace.default.createElement(
167468
- Button,
167506
+ ClientTranslation,
167469
167507
  {
167470
- variant: "secondary",
167471
- size: "sm",
167472
- onClick: () => setIsSettingsModalOpen(true)
167473
- },
167474
- /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "mr-2 h-4 w-4" }),
167475
- /* @__PURE__ */ React163__namespace.default.createElement(
167476
- ClientTranslation,
167477
- {
167478
- tKey: "common.customize",
167479
- fallback: "Customize"
167480
- }
167481
- )
167508
+ tKey: "common.cancel",
167509
+ fallback: "Cancel"
167510
+ }
167511
+ )
167512
+ ), /* @__PURE__ */ React163__namespace.default.createElement(
167513
+ Button,
167514
+ {
167515
+ variant: "secondary",
167516
+ size: "sm",
167517
+ onClick: handleSaveLayout
167518
+ },
167519
+ /* @__PURE__ */ React163__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }),
167520
+ /* @__PURE__ */ React163__namespace.default.createElement(
167521
+ ClientTranslation,
167522
+ {
167523
+ tKey: "common.saveLayout",
167524
+ fallback: "Save Layout"
167525
+ }
167482
167526
  )
167527
+ )), settingsPath ? /* @__PURE__ */ React163__namespace.default.createElement(
167528
+ Button,
167529
+ {
167530
+ asChild: true,
167531
+ variant: "ghost",
167532
+ size: "icon",
167533
+ className: "text-white hover:bg-background/20",
167534
+ title: "Settings"
167535
+ },
167536
+ /* @__PURE__ */ React163__namespace.default.createElement(Link3__default.default, { href: settingsPath }, /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "h-5 w-5" }))
167537
+ ) : /* @__PURE__ */ React163__namespace.default.createElement(
167538
+ Button,
167539
+ {
167540
+ variant: "ghost",
167541
+ size: "icon",
167542
+ className: "text-white hover:bg-background/20",
167543
+ title: "Settings",
167544
+ onClick: () => setIsSettingsModalOpen(true)
167545
+ },
167546
+ /* @__PURE__ */ React163__namespace.default.createElement(Settings, { className: "h-5 w-5" })
167483
167547
  )), /* @__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(
167484
167548
  Button,
167485
167549
  {
package/dist/react-ui.mjs CHANGED
@@ -139543,6 +139543,11 @@ AlertDialogCancel2.displayName = Cancel.displayName;
139543
139543
  init_react_shim();
139544
139544
  var LOCAL_STORAGE_KEY = "iqk_practice_history_v2";
139545
139545
  var PracticeHistoryService = class {
139546
+ // NEW: A configuration method to allow the host app (QMS) to inject its sync logic
139547
+ static configure(provider) {
139548
+ this.syncProvider = provider.syncProvider;
139549
+ console.log("PracticeHistoryService configured with a sync provider.");
139550
+ }
139546
139551
  static getPracticeHistory() {
139547
139552
  if (typeof window === "undefined") return [];
139548
139553
  try {
@@ -139586,6 +139591,12 @@ var PracticeHistoryService = class {
139586
139591
  };
139587
139592
  history2.unshift(newSession);
139588
139593
  this.saveHistory(history2);
139594
+ if (this.syncProvider) {
139595
+ console.log(`Sync provider found. Pushing session ${newSession.id} to backend.`);
139596
+ this.syncProvider.pushSession(newSession).catch((err) => {
139597
+ console.error("Sync provider failed to push session:", err);
139598
+ });
139599
+ }
139589
139600
  return newSession.id;
139590
139601
  }
139591
139602
  static updatePracticeReview(sessionId, review) {
@@ -139718,6 +139729,8 @@ var PracticeHistoryService = class {
139718
139729
  }
139719
139730
  }
139720
139731
  };
139732
+ // NEW: A static property to hold the injected sync provider
139733
+ PracticeHistoryService.syncProvider = null;
139721
139734
 
139722
139735
  // src/services/RoadmapService.ts
139723
139736
  init_react_shim();
@@ -142524,7 +142537,7 @@ function z29(e3 = x) {
142524
142537
  }
142525
142538
  var B = { tooltip: "core-styles-module_tooltip__3vRRp", fixed: "core-styles-module_fixed__pcSol", arrow: "core-styles-module_arrow__cvMwQ", noArrow: "core-styles-module_noArrow__xock6", clickable: "core-styles-module_clickable__ZuTTB", show: "core-styles-module_show__Nt9eE", closing: "core-styles-module_closing__sGnxF" };
142526
142539
  var D2 = { tooltip: "styles-module_tooltip__mnnfp", arrow: "styles-module_arrow__K0L3T", dark: "styles-module_dark__xNqje", light: "styles-module_light__Z6W-X", success: "styles-module_success__A2AKt", warning: "styles-module_warning__SCK0X", error: "styles-module_error__JvumD", info: "styles-module_info__BWdHW" };
142527
- var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u2 = "dark", anchorId: d, anchorSelect: p2, place: v = "top", offset: m = 10, events: h3 = ["hover"], openOnClick: w2 = false, positionStrategy: b2 = "absolute", middlewares: S3, wrapper: g, delayShow: A3 = 0, delayHide: O2 = 0, float: T3 = false, hidden: x3 = false, noArrow: N2 = false, clickable: $3 = false, closeOnEsc: I = false, closeOnScroll: j = false, closeOnResize: q2 = false, openEvents: H2, closeEvents: M2, globalCloseEvents: W, imperativeModeOnly: P2, style: V, position: F, afterShow: K, afterHide: U2, disableTooltip: X3, content: Y, contentWrapperRef: G, isOpen: Z2, defaultIsOpen: J = false, setIsOpen: Q, activeAnchor: ee, setActiveAnchor: te, border: oe, opacity: le, arrowColor: re3, arrowSize: ne = 8, role: ie3 = "tooltip" }) => {
142540
+ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u2 = "dark", anchorId: d, anchorSelect: p2, place: v = "top", offset: m = 10, events: h3 = ["hover"], openOnClick: w2 = false, positionStrategy: b2 = "absolute", middlewares: S3, wrapper: g, delayShow: A3 = 0, delayHide: O2 = 0, float: T3 = false, hidden: x3 = false, noArrow: N2 = false, clickable: $3 = false, closeOnEsc: I = false, closeOnScroll: j = false, closeOnResize: q2 = false, openEvents: H2, closeEvents: M2, globalCloseEvents: W, imperativeModeOnly: P2, style: V, position: F, afterShow: K, afterHide: U2, disableTooltip: X2, content: Y, contentWrapperRef: G, isOpen: Z2, defaultIsOpen: J = false, setIsOpen: Q, activeAnchor: ee, setActiveAnchor: te, border: oe, opacity: le, arrowColor: re3, arrowSize: ne = 8, role: ie3 = "tooltip" }) => {
142528
142541
  var ce;
142529
142542
  const se = useRef(null), ae = useRef(null), ue = useRef(null), de = useRef(null), pe = useRef(null), [ve, me] = useState({ tooltipStyles: {}, tooltipArrowStyles: {}, place: v }), [fe, ye] = useState(false), [he, we] = useState(false), [be, Se] = useState(null), ge = useRef(false), Ee = useRef(null), { anchorRefs: Ae, setActiveAnchor: _e } = z29(l2), Oe = useRef(false), [ke, Te] = useState([]), Le = useRef(false), Ce = w2 || h3.includes("click"), Re = Ce || (null == H2 ? void 0 : H2.click) || (null == H2 ? void 0 : H2.dblclick) || (null == H2 ? void 0 : H2.mousedown), xe = H2 ? { ...H2 } : { mouseover: true, focus: true, mouseenter: false, click: false, dblclick: false, mousedown: false };
142530
142543
  !H2 && Ce && Object.assign(xe, { mouseenter: false, focus: false, mouseover: false, click: true });
@@ -142612,10 +142625,10 @@ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u
142612
142625
  var e3, t5;
142613
142626
  const o2 = new Set(Ae);
142614
142627
  ke.forEach((e4) => {
142615
- (null == X3 ? void 0 : X3(e4)) || o2.add({ current: e4 });
142628
+ (null == X2 ? void 0 : X2(e4)) || o2.add({ current: e4 });
142616
142629
  });
142617
142630
  const l3 = document.querySelector(`[id='${d}']`);
142618
- l3 && !(null == X3 ? void 0 : X3(l3)) && o2.add({ current: l3 });
142631
+ l3 && !(null == X2 ? void 0 : X2(l3)) && o2.add({ current: l3 });
142619
142632
  const r4 = () => {
142620
142633
  Ie(false);
142621
142634
  }, n2 = L2(ee), i3 = L2(se.current);
@@ -142731,7 +142744,7 @@ var q = ({ forwardRef: t4, id: l2, className: i2, classNameArrow: c4, variant: u
142731
142744
  }, style: { ...V, ...ve.tooltipStyles, opacity: void 0 !== le && Ye ? le : void 0 }, ref: se }, Xe, React163__default.createElement(g, { className: (0, import_classnames.default)("react-tooltip-arrow", B.arrow, D2.arrow, c4, N2 && B.noArrow), style: { ...ve.tooltipArrowStyles, background: re3 ? `linear-gradient(to right bottom, transparent 50%, ${re3} 50%)` : void 0, "--rt-arrow-size": `${ne}px` }, ref: ae })) : null;
142732
142745
  };
142733
142746
  var H = ({ content: t4 }) => React163__default.createElement("span", { dangerouslySetInnerHTML: { __html: t4 } });
142734
- var M = React163__default.forwardRef(({ id: t4, anchorId: l2, anchorSelect: n2, content: i2, html: c4, render: a4, className: u2, classNameArrow: d, variant: p2 = "dark", place: v = "top", offset: m = 10, wrapper: f = "div", children: h3 = null, events: w2 = ["hover"], openOnClick: b2 = false, positionStrategy: S3 = "absolute", middlewares: g, delayShow: E2 = 0, delayHide: _2 = 0, float: O2 = false, hidden: k3 = false, noArrow: T3 = false, clickable: L3 = false, closeOnEsc: C3 = false, closeOnScroll: R3 = false, closeOnResize: x3 = false, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j = false, style: B2, position: D3, isOpen: M2, defaultIsOpen: W = false, disableStyleInjection: P2 = false, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X3, afterShow: Y, afterHide: G, disableTooltip: Z2, role: J = "tooltip" }, Q) => {
142747
+ var M = React163__default.forwardRef(({ id: t4, anchorId: l2, anchorSelect: n2, content: i2, html: c4, render: a4, className: u2, classNameArrow: d, variant: p2 = "dark", place: v = "top", offset: m = 10, wrapper: f = "div", children: h3 = null, events: w2 = ["hover"], openOnClick: b2 = false, positionStrategy: S3 = "absolute", middlewares: g, delayShow: E2 = 0, delayHide: _2 = 0, float: O2 = false, hidden: k3 = false, noArrow: T3 = false, clickable: L3 = false, closeOnEsc: C3 = false, closeOnScroll: R3 = false, closeOnResize: x3 = false, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j = false, style: B2, position: D3, isOpen: M2, defaultIsOpen: W = false, disableStyleInjection: P2 = false, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X2, afterShow: Y, afterHide: G, disableTooltip: Z2, role: J = "tooltip" }, Q) => {
142735
142748
  const [ee, te] = useState(i2), [oe, le] = useState(c4), [re3, ne] = useState(v), [ie3, ce] = useState(p2), [se, ae] = useState(m), [ue, de] = useState(E2), [pe, ve] = useState(_2), [me, fe] = useState(O2), [ye, he] = useState(k3), [we, be] = useState(f), [Se, ge] = useState(w2), [Ee, Ae] = useState(S3), [_e, Oe] = useState(null), [ke, Te] = useState(null), Le = useRef(P2), { anchorRefs: Ce, activeAnchor: Re } = z29(t4), xe = (e3) => null == e3 ? void 0 : e3.getAttributeNames().reduce((t5, o2) => {
142736
142749
  var l3;
142737
142750
  if (o2.startsWith("data-tooltip-")) {
@@ -142838,7 +142851,7 @@ var M = React163__default.forwardRef(({ id: t4, anchorId: l2, anchorSelect: n2,
142838
142851
  $e = t5 ? React163__default.createElement("div", { ref: Ie, className: "react-tooltip-content-wrapper" }, t5) : null;
142839
142852
  } else ee && ($e = ee);
142840
142853
  oe && ($e = React163__default.createElement(H, { content: oe }));
142841
- const ze = { forwardRef: Q, id: t4, anchorId: l2, anchorSelect: n2, className: (0, import_classnames.default)(u2, _e), classNameArrow: d, content: $e, contentWrapperRef: Ie, place: re3, variant: ie3, offset: se, wrapper: we, events: Se, openOnClick: b2, positionStrategy: Ee, middlewares: g, delayShow: ue, delayHide: pe, float: me, hidden: ye, noArrow: T3, clickable: L3, closeOnEsc: C3, closeOnScroll: R3, closeOnResize: x3, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j, style: B2, position: D3, isOpen: M2, defaultIsOpen: W, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X3, afterShow: Y, afterHide: G, disableTooltip: Z2, activeAnchor: ke, setActiveAnchor: (e3) => Te(e3), role: J };
142854
+ const ze = { forwardRef: Q, id: t4, anchorId: l2, anchorSelect: n2, className: (0, import_classnames.default)(u2, _e), classNameArrow: d, content: $e, contentWrapperRef: Ie, place: re3, variant: ie3, offset: se, wrapper: we, events: Se, openOnClick: b2, positionStrategy: Ee, middlewares: g, delayShow: ue, delayHide: pe, float: me, hidden: ye, noArrow: T3, clickable: L3, closeOnEsc: C3, closeOnScroll: R3, closeOnResize: x3, openEvents: N2, closeEvents: $3, globalCloseEvents: I, imperativeModeOnly: j, style: B2, position: D3, isOpen: M2, defaultIsOpen: W, border: V, opacity: F, arrowColor: K, arrowSize: U2, setIsOpen: X2, afterShow: Y, afterHide: G, disableTooltip: Z2, activeAnchor: ke, setActiveAnchor: (e3) => Te(e3), role: J };
142842
142855
  return React163__default.createElement(q, { ...ze });
142843
142856
  });
142844
142857
  "undefined" != typeof window && window.addEventListener("react-tooltip-inject-styles", (e3) => {
@@ -167404,6 +167417,18 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167404
167417
  return newLayout;
167405
167418
  });
167406
167419
  };
167420
+ const handleSaveLayout = () => {
167421
+ DashboardLayoutService.saveLayout(dashboardLayout);
167422
+ setIsEditMode(false);
167423
+ toast2({
167424
+ title: "Layout \u0111\xE3 \u0111\u01B0\u1EE3c l\u01B0u",
167425
+ description: "Dashboard c\u1EE7a b\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt."
167426
+ });
167427
+ };
167428
+ const handleCancelEdit = () => {
167429
+ setDashboardLayout(DashboardLayoutService.getLayout());
167430
+ setIsEditMode(false);
167431
+ };
167407
167432
  const cardComponents = {
167408
167433
  roadmap: /* @__PURE__ */ React163__default.createElement(RoadmapChecklist, null),
167409
167434
  activity: /* @__PURE__ */ React163__default.createElement(ActivityCalendar, { stats }),
@@ -167426,33 +167451,72 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167426
167451
  collisionDetection: closestCenter,
167427
167452
  onDragEnd: handleDragEnd
167428
167453
  },
167429
- /* @__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" }, settingsPath ? (
167430
- // If settingsPath is provided, always render a Link regardless of edit mode
167431
- /* @__PURE__ */ React163__default.createElement(Button, { asChild: true, variant: "secondary", size: "sm" }, /* @__PURE__ */ React163__default.createElement(Link3, { href: settingsPath }, /* @__PURE__ */ React163__default.createElement(Settings, { className: "mr-2 h-4 w-4" }), /* @__PURE__ */ React163__default.createElement(
167454
+ /* @__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(
167455
+ Button,
167456
+ {
167457
+ variant: "secondary",
167458
+ size: "sm",
167459
+ onClick: () => setIsEditMode(true)
167460
+ },
167461
+ /* @__PURE__ */ React163__default.createElement(LayoutDashboard, { className: "mr-2 h-4 w-4" }),
167462
+ /* @__PURE__ */ React163__default.createElement(
167432
167463
  ClientTranslation,
167433
167464
  {
167434
- tKey: "common.customize",
167435
- fallback: "Customize"
167465
+ tKey: "common.customizeLayout",
167466
+ fallback: "Customize Layout"
167436
167467
  }
167437
- )))
167438
- ) : (
167439
- // If no settingsPath, always open the modal regardless of edit mode
167468
+ )
167469
+ ) : /* @__PURE__ */ React163__default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React163__default.createElement(
167470
+ Button,
167471
+ {
167472
+ variant: "ghost",
167473
+ size: "sm",
167474
+ onClick: handleCancelEdit,
167475
+ className: "bg-background/20 hover:bg-background/40 text-white"
167476
+ },
167477
+ /* @__PURE__ */ React163__default.createElement(X, { className: "mr-2 h-4 w-4" }),
167440
167478
  /* @__PURE__ */ React163__default.createElement(
167441
- Button,
167479
+ ClientTranslation,
167442
167480
  {
167443
- variant: "secondary",
167444
- size: "sm",
167445
- onClick: () => setIsSettingsModalOpen(true)
167446
- },
167447
- /* @__PURE__ */ React163__default.createElement(Settings, { className: "mr-2 h-4 w-4" }),
167448
- /* @__PURE__ */ React163__default.createElement(
167449
- ClientTranslation,
167450
- {
167451
- tKey: "common.customize",
167452
- fallback: "Customize"
167453
- }
167454
- )
167481
+ tKey: "common.cancel",
167482
+ fallback: "Cancel"
167483
+ }
167484
+ )
167485
+ ), /* @__PURE__ */ React163__default.createElement(
167486
+ Button,
167487
+ {
167488
+ variant: "secondary",
167489
+ size: "sm",
167490
+ onClick: handleSaveLayout
167491
+ },
167492
+ /* @__PURE__ */ React163__default.createElement(Save, { className: "mr-2 h-4 w-4" }),
167493
+ /* @__PURE__ */ React163__default.createElement(
167494
+ ClientTranslation,
167495
+ {
167496
+ tKey: "common.saveLayout",
167497
+ fallback: "Save Layout"
167498
+ }
167455
167499
  )
167500
+ )), settingsPath ? /* @__PURE__ */ React163__default.createElement(
167501
+ Button,
167502
+ {
167503
+ asChild: true,
167504
+ variant: "ghost",
167505
+ size: "icon",
167506
+ className: "text-white hover:bg-background/20",
167507
+ title: "Settings"
167508
+ },
167509
+ /* @__PURE__ */ React163__default.createElement(Link3, { href: settingsPath }, /* @__PURE__ */ React163__default.createElement(Settings, { className: "h-5 w-5" }))
167510
+ ) : /* @__PURE__ */ React163__default.createElement(
167511
+ Button,
167512
+ {
167513
+ variant: "ghost",
167514
+ size: "icon",
167515
+ className: "text-white hover:bg-background/20",
167516
+ title: "Settings",
167517
+ onClick: () => setIsSettingsModalOpen(true)
167518
+ },
167519
+ /* @__PURE__ */ React163__default.createElement(Settings, { className: "h-5 w-5" })
167456
167520
  )), /* @__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(
167457
167521
  Button,
167458
167522
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
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",