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

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 {
@@ -3993,20 +3998,30 @@ var PracticeHistoryService = class {
3993
3998
  static saveHistory(history2) {
3994
3999
  if (typeof window !== "undefined") {
3995
4000
  try {
3996
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history2));
4001
+ localStorage.setItem(
4002
+ LOCAL_STORAGE_KEY,
4003
+ JSON.stringify(history2)
4004
+ );
3997
4005
  } catch (e2) {
3998
- console.error("Error saving practice history to localStorage:", e2);
4006
+ console.error(
4007
+ "Error saving practice history to localStorage:",
4008
+ e2
4009
+ );
3999
4010
  }
4000
4011
  }
4001
4012
  }
4002
4013
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
4003
4014
  const history2 = this.getPracticeHistory();
4004
4015
  const topicsCovered = Array.from(
4005
- new Set(quizConfig.questions.map((q) => JSON.stringify({
4006
- subject: q.subject || "Uncategorized",
4007
- category: q.category || "General",
4008
- topic: q.topic || "General Topic"
4009
- })))
4016
+ new Set(
4017
+ quizConfig.questions.map(
4018
+ (q) => JSON.stringify({
4019
+ subject: q.subject || "Uncategorized",
4020
+ category: q.category || "General",
4021
+ topic: q.topic || "General Topic"
4022
+ })
4023
+ )
4024
+ )
4010
4025
  ).map((s2) => JSON.parse(s2));
4011
4026
  const newSession = {
4012
4027
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -4023,6 +4038,14 @@ var PracticeHistoryService = class {
4023
4038
  };
4024
4039
  history2.unshift(newSession);
4025
4040
  this.saveHistory(history2);
4041
+ if (this.syncProvider) {
4042
+ console.log(
4043
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
4044
+ );
4045
+ this.syncProvider.pushSession(newSession).catch((err) => {
4046
+ console.error("Sync provider failed to push session:", err);
4047
+ });
4048
+ }
4026
4049
  return newSession.id;
4027
4050
  }
4028
4051
  static updatePracticeReview(sessionId, review) {
@@ -4032,7 +4055,9 @@ var PracticeHistoryService = class {
4032
4055
  history2[sessionIndex].quizReview = review;
4033
4056
  this.saveHistory(history2);
4034
4057
  } else {
4035
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
4058
+ console.warn(
4059
+ `Could not find session with ID "${sessionId}" to update review.`
4060
+ );
4036
4061
  }
4037
4062
  }
4038
4063
  static getPracticeSessionById(sessionId) {
@@ -4090,7 +4115,9 @@ var PracticeHistoryService = class {
4090
4115
  const currentDay = new Date(sortedDays[i]);
4091
4116
  const nextDay = new Date(sortedDays[i + 1]);
4092
4117
  const diffTime = currentDay.getTime() - nextDay.getTime();
4093
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
4118
+ const diffDays = Math.round(
4119
+ diffTime / (1e3 * 60 * 60 * 24)
4120
+ );
4094
4121
  if (diffDays === 1) {
4095
4122
  currentStreak++;
4096
4123
  } else {
@@ -4122,22 +4149,31 @@ var PracticeHistoryService = class {
4122
4149
  const subjectPerf = {};
4123
4150
  const topicPerf = {};
4124
4151
  history2.forEach((session) => {
4125
- session.summary.topics.forEach((topicInfo) => {
4126
- if (session.summary.percentage !== null) {
4127
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
4128
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
4129
- subjectPerf[topicInfo.subject].count++;
4130
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
4131
- topicPerf[topicInfo.topic].total += session.summary.percentage;
4132
- topicPerf[topicInfo.topic].count++;
4152
+ session.summary.topics.forEach(
4153
+ (topicInfo) => {
4154
+ if (session.summary.percentage !== null) {
4155
+ if (!subjectPerf[topicInfo.subject])
4156
+ subjectPerf[topicInfo.subject] = {
4157
+ total: 0,
4158
+ count: 0
4159
+ };
4160
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
4161
+ subjectPerf[topicInfo.subject].count++;
4162
+ if (!topicPerf[topicInfo.topic])
4163
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
4164
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
4165
+ topicPerf[topicInfo.topic].count++;
4166
+ }
4133
4167
  }
4134
- });
4168
+ );
4135
4169
  });
4136
4170
  const formatPerf = (perfData) => {
4137
4171
  return Object.entries(perfData).map(([name3, data]) => ({
4138
4172
  name: name3,
4139
4173
  totalSessions: data.count,
4140
- averageScore: parseFloat((data.total / data.count).toFixed(2))
4174
+ averageScore: parseFloat(
4175
+ (data.total / data.count).toFixed(2)
4176
+ )
4141
4177
  })).sort((a2, b) => b.totalSessions - a2.totalSessions);
4142
4178
  };
4143
4179
  return {
@@ -4155,6 +4191,8 @@ var PracticeHistoryService = class {
4155
4191
  }
4156
4192
  }
4157
4193
  };
4194
+ // NEW: A static property to hold the injected sync provider
4195
+ PracticeHistoryService.syncProvider = null;
4158
4196
 
4159
4197
  // src/services/AchievementService.ts
4160
4198
  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 {
@@ -3967,20 +3972,30 @@ var PracticeHistoryService = class {
3967
3972
  static saveHistory(history2) {
3968
3973
  if (typeof window !== "undefined") {
3969
3974
  try {
3970
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history2));
3975
+ localStorage.setItem(
3976
+ LOCAL_STORAGE_KEY,
3977
+ JSON.stringify(history2)
3978
+ );
3971
3979
  } catch (e2) {
3972
- console.error("Error saving practice history to localStorage:", e2);
3980
+ console.error(
3981
+ "Error saving practice history to localStorage:",
3982
+ e2
3983
+ );
3973
3984
  }
3974
3985
  }
3975
3986
  }
3976
3987
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
3977
3988
  const history2 = this.getPracticeHistory();
3978
3989
  const topicsCovered = Array.from(
3979
- new Set(quizConfig.questions.map((q) => JSON.stringify({
3980
- subject: q.subject || "Uncategorized",
3981
- category: q.category || "General",
3982
- topic: q.topic || "General Topic"
3983
- })))
3990
+ new Set(
3991
+ quizConfig.questions.map(
3992
+ (q) => JSON.stringify({
3993
+ subject: q.subject || "Uncategorized",
3994
+ category: q.category || "General",
3995
+ topic: q.topic || "General Topic"
3996
+ })
3997
+ )
3998
+ )
3984
3999
  ).map((s2) => JSON.parse(s2));
3985
4000
  const newSession = {
3986
4001
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -3997,6 +4012,14 @@ var PracticeHistoryService = class {
3997
4012
  };
3998
4013
  history2.unshift(newSession);
3999
4014
  this.saveHistory(history2);
4015
+ if (this.syncProvider) {
4016
+ console.log(
4017
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
4018
+ );
4019
+ this.syncProvider.pushSession(newSession).catch((err) => {
4020
+ console.error("Sync provider failed to push session:", err);
4021
+ });
4022
+ }
4000
4023
  return newSession.id;
4001
4024
  }
4002
4025
  static updatePracticeReview(sessionId, review) {
@@ -4006,7 +4029,9 @@ var PracticeHistoryService = class {
4006
4029
  history2[sessionIndex].quizReview = review;
4007
4030
  this.saveHistory(history2);
4008
4031
  } else {
4009
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
4032
+ console.warn(
4033
+ `Could not find session with ID "${sessionId}" to update review.`
4034
+ );
4010
4035
  }
4011
4036
  }
4012
4037
  static getPracticeSessionById(sessionId) {
@@ -4064,7 +4089,9 @@ var PracticeHistoryService = class {
4064
4089
  const currentDay = new Date(sortedDays[i]);
4065
4090
  const nextDay = new Date(sortedDays[i + 1]);
4066
4091
  const diffTime = currentDay.getTime() - nextDay.getTime();
4067
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
4092
+ const diffDays = Math.round(
4093
+ diffTime / (1e3 * 60 * 60 * 24)
4094
+ );
4068
4095
  if (diffDays === 1) {
4069
4096
  currentStreak++;
4070
4097
  } else {
@@ -4096,22 +4123,31 @@ var PracticeHistoryService = class {
4096
4123
  const subjectPerf = {};
4097
4124
  const topicPerf = {};
4098
4125
  history2.forEach((session) => {
4099
- session.summary.topics.forEach((topicInfo) => {
4100
- if (session.summary.percentage !== null) {
4101
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
4102
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
4103
- subjectPerf[topicInfo.subject].count++;
4104
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
4105
- topicPerf[topicInfo.topic].total += session.summary.percentage;
4106
- topicPerf[topicInfo.topic].count++;
4126
+ session.summary.topics.forEach(
4127
+ (topicInfo) => {
4128
+ if (session.summary.percentage !== null) {
4129
+ if (!subjectPerf[topicInfo.subject])
4130
+ subjectPerf[topicInfo.subject] = {
4131
+ total: 0,
4132
+ count: 0
4133
+ };
4134
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
4135
+ subjectPerf[topicInfo.subject].count++;
4136
+ if (!topicPerf[topicInfo.topic])
4137
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
4138
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
4139
+ topicPerf[topicInfo.topic].count++;
4140
+ }
4107
4141
  }
4108
- });
4142
+ );
4109
4143
  });
4110
4144
  const formatPerf = (perfData) => {
4111
4145
  return Object.entries(perfData).map(([name3, data]) => ({
4112
4146
  name: name3,
4113
4147
  totalSessions: data.count,
4114
- averageScore: parseFloat((data.total / data.count).toFixed(2))
4148
+ averageScore: parseFloat(
4149
+ (data.total / data.count).toFixed(2)
4150
+ )
4115
4151
  })).sort((a2, b) => b.totalSessions - a2.totalSessions);
4116
4152
  };
4117
4153
  return {
@@ -4129,6 +4165,8 @@ var PracticeHistoryService = class {
4129
4165
  }
4130
4166
  }
4131
4167
  };
4168
+ // NEW: A static property to hold the injected sync provider
4169
+ PracticeHistoryService.syncProvider = null;
4132
4170
 
4133
4171
  // src/services/AchievementService.ts
4134
4172
  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 {
@@ -1959,20 +1964,30 @@ var PracticeHistoryService = class {
1959
1964
  static saveHistory(history) {
1960
1965
  if (typeof window !== "undefined") {
1961
1966
  try {
1962
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history));
1967
+ localStorage.setItem(
1968
+ LOCAL_STORAGE_KEY,
1969
+ JSON.stringify(history)
1970
+ );
1963
1971
  } catch (e) {
1964
- console.error("Error saving practice history to localStorage:", e);
1972
+ console.error(
1973
+ "Error saving practice history to localStorage:",
1974
+ e
1975
+ );
1965
1976
  }
1966
1977
  }
1967
1978
  }
1968
1979
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
1969
1980
  const history = this.getPracticeHistory();
1970
1981
  const topicsCovered = Array.from(
1971
- new Set(quizConfig.questions.map((q) => JSON.stringify({
1972
- subject: q.subject || "Uncategorized",
1973
- category: q.category || "General",
1974
- topic: q.topic || "General Topic"
1975
- })))
1982
+ new Set(
1983
+ quizConfig.questions.map(
1984
+ (q) => JSON.stringify({
1985
+ subject: q.subject || "Uncategorized",
1986
+ category: q.category || "General",
1987
+ topic: q.topic || "General Topic"
1988
+ })
1989
+ )
1990
+ )
1976
1991
  ).map((s) => JSON.parse(s));
1977
1992
  const newSession = {
1978
1993
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -1989,6 +2004,14 @@ var PracticeHistoryService = class {
1989
2004
  };
1990
2005
  history.unshift(newSession);
1991
2006
  this.saveHistory(history);
2007
+ if (this.syncProvider) {
2008
+ console.log(
2009
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
2010
+ );
2011
+ this.syncProvider.pushSession(newSession).catch((err) => {
2012
+ console.error("Sync provider failed to push session:", err);
2013
+ });
2014
+ }
1992
2015
  return newSession.id;
1993
2016
  }
1994
2017
  static updatePracticeReview(sessionId, review) {
@@ -1998,7 +2021,9 @@ var PracticeHistoryService = class {
1998
2021
  history[sessionIndex].quizReview = review;
1999
2022
  this.saveHistory(history);
2000
2023
  } else {
2001
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
2024
+ console.warn(
2025
+ `Could not find session with ID "${sessionId}" to update review.`
2026
+ );
2002
2027
  }
2003
2028
  }
2004
2029
  static getPracticeSessionById(sessionId) {
@@ -2056,7 +2081,9 @@ var PracticeHistoryService = class {
2056
2081
  const currentDay = new Date(sortedDays[i]);
2057
2082
  const nextDay = new Date(sortedDays[i + 1]);
2058
2083
  const diffTime = currentDay.getTime() - nextDay.getTime();
2059
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
2084
+ const diffDays = Math.round(
2085
+ diffTime / (1e3 * 60 * 60 * 24)
2086
+ );
2060
2087
  if (diffDays === 1) {
2061
2088
  currentStreak++;
2062
2089
  } else {
@@ -2088,22 +2115,31 @@ var PracticeHistoryService = class {
2088
2115
  const subjectPerf = {};
2089
2116
  const topicPerf = {};
2090
2117
  history.forEach((session) => {
2091
- session.summary.topics.forEach((topicInfo) => {
2092
- if (session.summary.percentage !== null) {
2093
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
2094
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
2095
- subjectPerf[topicInfo.subject].count++;
2096
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
2097
- topicPerf[topicInfo.topic].total += session.summary.percentage;
2098
- topicPerf[topicInfo.topic].count++;
2118
+ session.summary.topics.forEach(
2119
+ (topicInfo) => {
2120
+ if (session.summary.percentage !== null) {
2121
+ if (!subjectPerf[topicInfo.subject])
2122
+ subjectPerf[topicInfo.subject] = {
2123
+ total: 0,
2124
+ count: 0
2125
+ };
2126
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
2127
+ subjectPerf[topicInfo.subject].count++;
2128
+ if (!topicPerf[topicInfo.topic])
2129
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
2130
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
2131
+ topicPerf[topicInfo.topic].count++;
2132
+ }
2099
2133
  }
2100
- });
2134
+ );
2101
2135
  });
2102
2136
  const formatPerf = (perfData) => {
2103
2137
  return Object.entries(perfData).map(([name, data]) => ({
2104
2138
  name,
2105
2139
  totalSessions: data.count,
2106
- averageScore: parseFloat((data.total / data.count).toFixed(2))
2140
+ averageScore: parseFloat(
2141
+ (data.total / data.count).toFixed(2)
2142
+ )
2107
2143
  })).sort((a, b) => b.totalSessions - a.totalSessions);
2108
2144
  };
2109
2145
  return {
@@ -2121,6 +2157,8 @@ var PracticeHistoryService = class {
2121
2157
  }
2122
2158
  }
2123
2159
  };
2160
+ // NEW: A static property to hold the injected sync provider
2161
+ PracticeHistoryService.syncProvider = null;
2124
2162
 
2125
2163
  // src/data/achievements.json
2126
2164
  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 {
@@ -1953,20 +1958,30 @@ var PracticeHistoryService = class {
1953
1958
  static saveHistory(history) {
1954
1959
  if (typeof window !== "undefined") {
1955
1960
  try {
1956
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history));
1961
+ localStorage.setItem(
1962
+ LOCAL_STORAGE_KEY,
1963
+ JSON.stringify(history)
1964
+ );
1957
1965
  } catch (e) {
1958
- console.error("Error saving practice history to localStorage:", e);
1966
+ console.error(
1967
+ "Error saving practice history to localStorage:",
1968
+ e
1969
+ );
1959
1970
  }
1960
1971
  }
1961
1972
  }
1962
1973
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
1963
1974
  const history = this.getPracticeHistory();
1964
1975
  const topicsCovered = Array.from(
1965
- new Set(quizConfig.questions.map((q) => JSON.stringify({
1966
- subject: q.subject || "Uncategorized",
1967
- category: q.category || "General",
1968
- topic: q.topic || "General Topic"
1969
- })))
1976
+ new Set(
1977
+ quizConfig.questions.map(
1978
+ (q) => JSON.stringify({
1979
+ subject: q.subject || "Uncategorized",
1980
+ category: q.category || "General",
1981
+ topic: q.topic || "General Topic"
1982
+ })
1983
+ )
1984
+ )
1970
1985
  ).map((s) => JSON.parse(s));
1971
1986
  const newSession = {
1972
1987
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -1983,6 +1998,14 @@ var PracticeHistoryService = class {
1983
1998
  };
1984
1999
  history.unshift(newSession);
1985
2000
  this.saveHistory(history);
2001
+ if (this.syncProvider) {
2002
+ console.log(
2003
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
2004
+ );
2005
+ this.syncProvider.pushSession(newSession).catch((err) => {
2006
+ console.error("Sync provider failed to push session:", err);
2007
+ });
2008
+ }
1986
2009
  return newSession.id;
1987
2010
  }
1988
2011
  static updatePracticeReview(sessionId, review) {
@@ -1992,7 +2015,9 @@ var PracticeHistoryService = class {
1992
2015
  history[sessionIndex].quizReview = review;
1993
2016
  this.saveHistory(history);
1994
2017
  } else {
1995
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
2018
+ console.warn(
2019
+ `Could not find session with ID "${sessionId}" to update review.`
2020
+ );
1996
2021
  }
1997
2022
  }
1998
2023
  static getPracticeSessionById(sessionId) {
@@ -2050,7 +2075,9 @@ var PracticeHistoryService = class {
2050
2075
  const currentDay = new Date(sortedDays[i]);
2051
2076
  const nextDay = new Date(sortedDays[i + 1]);
2052
2077
  const diffTime = currentDay.getTime() - nextDay.getTime();
2053
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
2078
+ const diffDays = Math.round(
2079
+ diffTime / (1e3 * 60 * 60 * 24)
2080
+ );
2054
2081
  if (diffDays === 1) {
2055
2082
  currentStreak++;
2056
2083
  } else {
@@ -2082,22 +2109,31 @@ var PracticeHistoryService = class {
2082
2109
  const subjectPerf = {};
2083
2110
  const topicPerf = {};
2084
2111
  history.forEach((session) => {
2085
- session.summary.topics.forEach((topicInfo) => {
2086
- if (session.summary.percentage !== null) {
2087
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
2088
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
2089
- subjectPerf[topicInfo.subject].count++;
2090
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
2091
- topicPerf[topicInfo.topic].total += session.summary.percentage;
2092
- topicPerf[topicInfo.topic].count++;
2112
+ session.summary.topics.forEach(
2113
+ (topicInfo) => {
2114
+ if (session.summary.percentage !== null) {
2115
+ if (!subjectPerf[topicInfo.subject])
2116
+ subjectPerf[topicInfo.subject] = {
2117
+ total: 0,
2118
+ count: 0
2119
+ };
2120
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
2121
+ subjectPerf[topicInfo.subject].count++;
2122
+ if (!topicPerf[topicInfo.topic])
2123
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
2124
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
2125
+ topicPerf[topicInfo.topic].count++;
2126
+ }
2093
2127
  }
2094
- });
2128
+ );
2095
2129
  });
2096
2130
  const formatPerf = (perfData) => {
2097
2131
  return Object.entries(perfData).map(([name, data]) => ({
2098
2132
  name,
2099
2133
  totalSessions: data.count,
2100
- averageScore: parseFloat((data.total / data.count).toFixed(2))
2134
+ averageScore: parseFloat(
2135
+ (data.total / data.count).toFixed(2)
2136
+ )
2101
2137
  })).sort((a, b) => b.totalSessions - a.totalSessions);
2102
2138
  };
2103
2139
  return {
@@ -2115,6 +2151,8 @@ var PracticeHistoryService = class {
2115
2151
  }
2116
2152
  }
2117
2153
  };
2154
+ // NEW: A static property to hold the injected sync provider
2155
+ PracticeHistoryService.syncProvider = null;
2118
2156
 
2119
2157
  // src/data/achievements.json
2120
2158
  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 {
@@ -139583,20 +139588,30 @@ var PracticeHistoryService = class {
139583
139588
  static saveHistory(history2) {
139584
139589
  if (typeof window !== "undefined") {
139585
139590
  try {
139586
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history2));
139591
+ localStorage.setItem(
139592
+ LOCAL_STORAGE_KEY,
139593
+ JSON.stringify(history2)
139594
+ );
139587
139595
  } catch (e3) {
139588
- console.error("Error saving practice history to localStorage:", e3);
139596
+ console.error(
139597
+ "Error saving practice history to localStorage:",
139598
+ e3
139599
+ );
139589
139600
  }
139590
139601
  }
139591
139602
  }
139592
139603
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
139593
139604
  const history2 = this.getPracticeHistory();
139594
139605
  const topicsCovered = Array.from(
139595
- new Set(quizConfig.questions.map((q2) => JSON.stringify({
139596
- subject: q2.subject || "Uncategorized",
139597
- category: q2.category || "General",
139598
- topic: q2.topic || "General Topic"
139599
- })))
139606
+ new Set(
139607
+ quizConfig.questions.map(
139608
+ (q2) => JSON.stringify({
139609
+ subject: q2.subject || "Uncategorized",
139610
+ category: q2.category || "General",
139611
+ topic: q2.topic || "General Topic"
139612
+ })
139613
+ )
139614
+ )
139600
139615
  ).map((s4) => JSON.parse(s4));
139601
139616
  const newSession = {
139602
139617
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -139613,6 +139628,14 @@ var PracticeHistoryService = class {
139613
139628
  };
139614
139629
  history2.unshift(newSession);
139615
139630
  this.saveHistory(history2);
139631
+ if (this.syncProvider) {
139632
+ console.log(
139633
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
139634
+ );
139635
+ this.syncProvider.pushSession(newSession).catch((err) => {
139636
+ console.error("Sync provider failed to push session:", err);
139637
+ });
139638
+ }
139616
139639
  return newSession.id;
139617
139640
  }
139618
139641
  static updatePracticeReview(sessionId, review) {
@@ -139622,7 +139645,9 @@ var PracticeHistoryService = class {
139622
139645
  history2[sessionIndex].quizReview = review;
139623
139646
  this.saveHistory(history2);
139624
139647
  } else {
139625
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
139648
+ console.warn(
139649
+ `Could not find session with ID "${sessionId}" to update review.`
139650
+ );
139626
139651
  }
139627
139652
  }
139628
139653
  static getPracticeSessionById(sessionId) {
@@ -139680,7 +139705,9 @@ var PracticeHistoryService = class {
139680
139705
  const currentDay = new Date(sortedDays[i2]);
139681
139706
  const nextDay = new Date(sortedDays[i2 + 1]);
139682
139707
  const diffTime = currentDay.getTime() - nextDay.getTime();
139683
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
139708
+ const diffDays = Math.round(
139709
+ diffTime / (1e3 * 60 * 60 * 24)
139710
+ );
139684
139711
  if (diffDays === 1) {
139685
139712
  currentStreak++;
139686
139713
  } else {
@@ -139712,22 +139739,31 @@ var PracticeHistoryService = class {
139712
139739
  const subjectPerf = {};
139713
139740
  const topicPerf = {};
139714
139741
  history2.forEach((session) => {
139715
- session.summary.topics.forEach((topicInfo) => {
139716
- if (session.summary.percentage !== null) {
139717
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
139718
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
139719
- subjectPerf[topicInfo.subject].count++;
139720
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
139721
- topicPerf[topicInfo.topic].total += session.summary.percentage;
139722
- topicPerf[topicInfo.topic].count++;
139742
+ session.summary.topics.forEach(
139743
+ (topicInfo) => {
139744
+ if (session.summary.percentage !== null) {
139745
+ if (!subjectPerf[topicInfo.subject])
139746
+ subjectPerf[topicInfo.subject] = {
139747
+ total: 0,
139748
+ count: 0
139749
+ };
139750
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
139751
+ subjectPerf[topicInfo.subject].count++;
139752
+ if (!topicPerf[topicInfo.topic])
139753
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
139754
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
139755
+ topicPerf[topicInfo.topic].count++;
139756
+ }
139723
139757
  }
139724
- });
139758
+ );
139725
139759
  });
139726
139760
  const formatPerf = (perfData) => {
139727
139761
  return Object.entries(perfData).map(([name3, data]) => ({
139728
139762
  name: name3,
139729
139763
  totalSessions: data.count,
139730
- averageScore: parseFloat((data.total / data.count).toFixed(2))
139764
+ averageScore: parseFloat(
139765
+ (data.total / data.count).toFixed(2)
139766
+ )
139731
139767
  })).sort((a4, b2) => b2.totalSessions - a4.totalSessions);
139732
139768
  };
139733
139769
  return {
@@ -139745,6 +139781,8 @@ var PracticeHistoryService = class {
139745
139781
  }
139746
139782
  }
139747
139783
  };
139784
+ // NEW: A static property to hold the injected sync provider
139785
+ PracticeHistoryService.syncProvider = null;
139748
139786
 
139749
139787
  // src/services/RoadmapService.ts
139750
139788
  init_react_shim();
@@ -167225,15 +167263,19 @@ var ClientTranslation = ({ tKey, options, fallback: fallback2 }) => {
167225
167263
  };
167226
167264
 
167227
167265
  // src/react-ui/components/app/PersonalPracticeDashboard.tsx
167228
- var PersonalPracticeDashboard = ({ settingsPath }) => {
167266
+ var PersonalPracticeDashboard = ({ settingsPath, initialHistory, initialStats, isControlled = false }) => {
167229
167267
  const router = navigation.useRouter();
167230
167268
  const { toast: toast2 } = useToast();
167231
167269
  const { t: t4, i18n } = useTranslation();
167232
167270
  const [isMounted, setIsMounted] = React163.useState(false);
167233
167271
  React163.useEffect(() => setIsMounted(true), []);
167234
167272
  const [isLoading, setIsLoading] = React163.useState(true);
167235
- const [stats, setStats] = React163.useState(null);
167236
- const [history2, setHistory] = React163.useState([]);
167273
+ const [stats, setStats] = React163.useState(
167274
+ initialStats || null
167275
+ );
167276
+ const [history2, setHistory] = React163.useState(
167277
+ initialHistory || []
167278
+ );
167237
167279
  const [userName, setUserName] = React163.useState(null);
167238
167280
  const [allAchievements, setAllAchievements] = React163.useState([]);
167239
167281
  const [motivationalQuote, setMotivationalQuote] = React163.useState(null);
@@ -167247,6 +167289,10 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167247
167289
  );
167248
167290
  const [isSettingsModalOpen, setIsSettingsModalOpen] = React163.useState(false);
167249
167291
  const loadDashboardData = React163.useCallback(() => {
167292
+ if (isControlled) {
167293
+ setIsLoading(false);
167294
+ return;
167295
+ }
167250
167296
  setIsLoading(true);
167251
167297
  setDashboardLayout(DashboardLayoutService.getLayout());
167252
167298
  const practiceHistory = PracticeHistoryService.getPracticeHistory();
@@ -167275,10 +167321,20 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167275
167321
  if (KnowledgeCardService.getPendingConcepts().length > 0) {
167276
167322
  CardGenerationProcessor.processQueue();
167277
167323
  }
167278
- }, [toast2, t4]);
167324
+ }, [isControlled, toast2, t4]);
167279
167325
  React163.useEffect(() => {
167280
- loadDashboardData();
167281
- }, [loadDashboardData]);
167326
+ if (isControlled) {
167327
+ setHistory(initialHistory || []);
167328
+ setStats(initialStats || null);
167329
+ const name3 = UserConfigService.getFullName();
167330
+ setUserName(name3);
167331
+ const achievementsWithStatus = AchievementService.getAllAchievementsWithStatus();
167332
+ setAllAchievements(achievementsWithStatus);
167333
+ setIsLoading(false);
167334
+ } else {
167335
+ loadDashboardData();
167336
+ }
167337
+ }, [isControlled, initialHistory, initialStats, loadDashboardData]);
167282
167338
  React163.useEffect(() => {
167283
167339
  setMotivationalQuote(QuoteService.getRandomQuote());
167284
167340
  const fetchAIQuote = async () => {
@@ -1,8 +1,8 @@
1
1
  import { s as QuizConfig, g as QuizQuestion } from './quiz-config-o4j2dfsu.cjs';
2
2
  export { B as BaseQuestion, e as BlocklyProgrammingQuestion, C as CodingQuestion, D as DragAndDropQuestion, l as DraggableItem, m as DropZone, F as FillInTheBlanksQuestion, n as HotspotArea, H as HotspotQuestion, M as MarkdownString, k as MatchOptionItem, j as MatchPromptItem, d as MatchingQuestion, a as MultipleChoiceQuestion, b as MultipleResponseQuestion, N as NumericQuestion, h as QuestionOption, Q as QuestionTypeStrings, r as QuizSettings, R as RichContentString, q as SCORMSettings, f as ScratchProgrammingQuestion, i as SequenceItem, c as SequenceQuestion, S as ShortAnswerQuestion, p as SupportedCodingLanguage, o as TestCase, T as TrueFalseQuestion } from './quiz-config-o4j2dfsu.cjs';
3
3
  export { APIKeyService, AchievementService, Approach, ApproachTableRawDifficulty, BloomLevelName, BloomLevelType, Category, CodeNamedEntity, Context, GEMINI_API_KEY_SERVICE_NAME, GradeLevel, KnowledgeCardService, KnowledgeDimension, LearningObjective, LearningObjectiveMetadata, MetadataService, PracticeHistoryService, QuestionBankService, QuestionImportService, QuestionInBank, QuestionTypeType, QuizEditorService, QuizEngine, QuizEngineCallbacks, QuizEngineConstructorOptions, QuoteService, SCORMService, StandardDifficulty, Subject, Topic, UserConfigService, cn, emptyQuiz, exportQuizAsSCORMZip, generateLauncherHTML, generateSCORMManifest, generateUniqueId, sampleQuiz } from './index.cjs';
4
- import { Q as QuizResultType, U as UserAnswerType, m as PracticeSuggestion, l as PracticeSuggestionTopic, i as Achievement, o as PracticeStats, p as PracticeSessionSummary, h as QuizReviewContent } from './ai-ecosystem-DqFRlFU3.cjs';
5
- export { j as AchievementDefinition, r as ActivityCalendarData, u as AnalysisReport, A as AnswerDetail, x as ChatContext, C as ChatMessage, v as DashboardCardConfig, D as DashboardCardId, w as DashboardLayout, y as Goal, G as GoalType, t as ImageContextItem, I as ImportError, K as KnowledgeCard, L as LearningAnalysis, e as PerformanceByBloomLevel, b as PerformanceByCategory, d as PerformanceByDifficulty, P as PerformanceByLearningObjective, c as PerformanceByTopic, f as PerformanceMetric, s as PerformanceSummary, k as PracticeDifficulty, n as PracticeSession, q as PracticeTopicSummary, g as QuestionReview, R as RoadmapItem, T as TestCaseResult, a as UserAnswers, W as WeeklyRoadmap } from './ai-ecosystem-DqFRlFU3.cjs';
4
+ import { Q as QuizResultType, U as UserAnswerType, n as PracticeSession, o as PracticeStats, m as PracticeSuggestion, l as PracticeSuggestionTopic, i as Achievement, p as PracticeSessionSummary, h as QuizReviewContent } from './ai-ecosystem-DqFRlFU3.cjs';
5
+ export { j as AchievementDefinition, r as ActivityCalendarData, u as AnalysisReport, A as AnswerDetail, x as ChatContext, C as ChatMessage, v as DashboardCardConfig, D as DashboardCardId, w as DashboardLayout, y as Goal, G as GoalType, t as ImageContextItem, I as ImportError, K as KnowledgeCard, L as LearningAnalysis, e as PerformanceByBloomLevel, b as PerformanceByCategory, d as PerformanceByDifficulty, P as PerformanceByLearningObjective, c as PerformanceByTopic, f as PerformanceMetric, s as PerformanceSummary, k as PracticeDifficulty, q as PracticeTopicSummary, g as QuestionReview, R as RoadmapItem, T as TestCaseResult, a as UserAnswers, W as WeeklyRoadmap } from './ai-ecosystem-DqFRlFU3.cjs';
6
6
  import * as React$1 from 'react';
7
7
  import React__default, { ReactNode } from 'react';
8
8
  export { a as AIFullQuizGeneratorModal, A as AIQuestionGeneratorModal, b as APIKeyManagerModal, c as ApiKeySettings, j as ApproachManager, B as BloomLevelManager, C as CategoryManager, i as ContextManager, E as EditQuestionModal, G as GradeLevelManager, I as ImportQuestionsModal, L as LearningObjectiveManager, M as MetadataTabs, e as QuestionFilters, f as QuestionFormDialog, d as QuestionList, h as QuestionTypeManager, Q as QuizAuthoringTool, S as SCORMExportModal, g as SubjectManager, k as Toaster, T as TopicManager, t as toast, u as useToast } from './toaster-DfLqsPwa.cjs';
@@ -72,6 +72,9 @@ declare const QuestionRenderer: React__default.ForwardRefExoticComponent<Questio
72
72
 
73
73
  interface PersonalPracticeDashboardProps {
74
74
  settingsPath?: string;
75
+ initialHistory?: PracticeSession[];
76
+ initialStats?: PracticeStats | null;
77
+ isControlled?: boolean;
75
78
  }
76
79
  declare const PersonalPracticeDashboard: React__default.FC<PersonalPracticeDashboardProps>;
77
80
 
@@ -215,4 +218,4 @@ declare const TabsList: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.Tab
215
218
  declare const TabsTrigger: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
216
219
  declare const TabsContent: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
217
220
 
218
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Achievement, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cheatsheet, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, FreestyleQuizzesCard, GeneratedQuizzesCard, Input, Label, LanguageProvider, ManageTopics, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, PracticeSessionSummary, PracticeStats, PracticeSuggestion, PracticeSuggestionTopic, Progress, QuestionRenderer, QuizConfig, QuizDataManagement, QuizPlayer, QuizQuestion, QuizResult, QuizResultType, QuizReview, QuizReviewContent, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SettingsModal, Skeleton, SuggestionDialog, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadResourceModal, UserAnswerType };
221
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Achievement, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cheatsheet, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, FreestyleQuizzesCard, GeneratedQuizzesCard, Input, Label, LanguageProvider, ManageTopics, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, PracticeSession, PracticeSessionSummary, PracticeStats, PracticeSuggestion, PracticeSuggestionTopic, Progress, QuestionRenderer, QuizConfig, QuizDataManagement, QuizPlayer, QuizQuestion, QuizResult, QuizResultType, QuizReview, QuizReviewContent, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SettingsModal, Skeleton, SuggestionDialog, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadResourceModal, UserAnswerType };
@@ -1,8 +1,8 @@
1
1
  import { s as QuizConfig, g as QuizQuestion } from './quiz-config-o4j2dfsu.js';
2
2
  export { B as BaseQuestion, e as BlocklyProgrammingQuestion, C as CodingQuestion, D as DragAndDropQuestion, l as DraggableItem, m as DropZone, F as FillInTheBlanksQuestion, n as HotspotArea, H as HotspotQuestion, M as MarkdownString, k as MatchOptionItem, j as MatchPromptItem, d as MatchingQuestion, a as MultipleChoiceQuestion, b as MultipleResponseQuestion, N as NumericQuestion, h as QuestionOption, Q as QuestionTypeStrings, r as QuizSettings, R as RichContentString, q as SCORMSettings, f as ScratchProgrammingQuestion, i as SequenceItem, c as SequenceQuestion, S as ShortAnswerQuestion, p as SupportedCodingLanguage, o as TestCase, T as TrueFalseQuestion } from './quiz-config-o4j2dfsu.js';
3
3
  export { APIKeyService, AchievementService, Approach, ApproachTableRawDifficulty, BloomLevelName, BloomLevelType, Category, CodeNamedEntity, Context, GEMINI_API_KEY_SERVICE_NAME, GradeLevel, KnowledgeCardService, KnowledgeDimension, LearningObjective, LearningObjectiveMetadata, MetadataService, PracticeHistoryService, QuestionBankService, QuestionImportService, QuestionInBank, QuestionTypeType, QuizEditorService, QuizEngine, QuizEngineCallbacks, QuizEngineConstructorOptions, QuoteService, SCORMService, StandardDifficulty, Subject, Topic, UserConfigService, cn, emptyQuiz, exportQuizAsSCORMZip, generateLauncherHTML, generateSCORMManifest, generateUniqueId, sampleQuiz } from './index.js';
4
- import { Q as QuizResultType, U as UserAnswerType, m as PracticeSuggestion, l as PracticeSuggestionTopic, i as Achievement, o as PracticeStats, p as PracticeSessionSummary, h as QuizReviewContent } from './ai-ecosystem-DqVlSO3r.js';
5
- export { j as AchievementDefinition, r as ActivityCalendarData, u as AnalysisReport, A as AnswerDetail, x as ChatContext, C as ChatMessage, v as DashboardCardConfig, D as DashboardCardId, w as DashboardLayout, y as Goal, G as GoalType, t as ImageContextItem, I as ImportError, K as KnowledgeCard, L as LearningAnalysis, e as PerformanceByBloomLevel, b as PerformanceByCategory, d as PerformanceByDifficulty, P as PerformanceByLearningObjective, c as PerformanceByTopic, f as PerformanceMetric, s as PerformanceSummary, k as PracticeDifficulty, n as PracticeSession, q as PracticeTopicSummary, g as QuestionReview, R as RoadmapItem, T as TestCaseResult, a as UserAnswers, W as WeeklyRoadmap } from './ai-ecosystem-DqVlSO3r.js';
4
+ import { Q as QuizResultType, U as UserAnswerType, n as PracticeSession, o as PracticeStats, m as PracticeSuggestion, l as PracticeSuggestionTopic, i as Achievement, p as PracticeSessionSummary, h as QuizReviewContent } from './ai-ecosystem-DqVlSO3r.js';
5
+ export { j as AchievementDefinition, r as ActivityCalendarData, u as AnalysisReport, A as AnswerDetail, x as ChatContext, C as ChatMessage, v as DashboardCardConfig, D as DashboardCardId, w as DashboardLayout, y as Goal, G as GoalType, t as ImageContextItem, I as ImportError, K as KnowledgeCard, L as LearningAnalysis, e as PerformanceByBloomLevel, b as PerformanceByCategory, d as PerformanceByDifficulty, P as PerformanceByLearningObjective, c as PerformanceByTopic, f as PerformanceMetric, s as PerformanceSummary, k as PracticeDifficulty, q as PracticeTopicSummary, g as QuestionReview, R as RoadmapItem, T as TestCaseResult, a as UserAnswers, W as WeeklyRoadmap } from './ai-ecosystem-DqVlSO3r.js';
6
6
  import * as React$1 from 'react';
7
7
  import React__default, { ReactNode } from 'react';
8
8
  export { a as AIFullQuizGeneratorModal, A as AIQuestionGeneratorModal, b as APIKeyManagerModal, c as ApiKeySettings, j as ApproachManager, B as BloomLevelManager, C as CategoryManager, i as ContextManager, E as EditQuestionModal, G as GradeLevelManager, I as ImportQuestionsModal, L as LearningObjectiveManager, M as MetadataTabs, e as QuestionFilters, f as QuestionFormDialog, d as QuestionList, h as QuestionTypeManager, Q as QuizAuthoringTool, S as SCORMExportModal, g as SubjectManager, k as Toaster, T as TopicManager, t as toast, u as useToast } from './toaster-Z0Qz6kch.js';
@@ -72,6 +72,9 @@ declare const QuestionRenderer: React__default.ForwardRefExoticComponent<Questio
72
72
 
73
73
  interface PersonalPracticeDashboardProps {
74
74
  settingsPath?: string;
75
+ initialHistory?: PracticeSession[];
76
+ initialStats?: PracticeStats | null;
77
+ isControlled?: boolean;
75
78
  }
76
79
  declare const PersonalPracticeDashboard: React__default.FC<PersonalPracticeDashboardProps>;
77
80
 
@@ -215,4 +218,4 @@ declare const TabsList: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.Tab
215
218
  declare const TabsTrigger: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
216
219
  declare const TabsContent: React$1.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
217
220
 
218
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Achievement, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cheatsheet, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, FreestyleQuizzesCard, GeneratedQuizzesCard, Input, Label, LanguageProvider, ManageTopics, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, PracticeSessionSummary, PracticeStats, PracticeSuggestion, PracticeSuggestionTopic, Progress, QuestionRenderer, QuizConfig, QuizDataManagement, QuizPlayer, QuizQuestion, QuizResult, QuizResultType, QuizReview, QuizReviewContent, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SettingsModal, Skeleton, SuggestionDialog, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadResourceModal, UserAnswerType };
221
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Achievement, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cheatsheet, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, FreestyleQuizzesCard, GeneratedQuizzesCard, Input, Label, LanguageProvider, ManageTopics, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, PracticeSession, PracticeSessionSummary, PracticeStats, PracticeSuggestion, PracticeSuggestionTopic, Progress, QuestionRenderer, QuizConfig, QuizDataManagement, QuizPlayer, QuizQuestion, QuizResult, QuizResultType, QuizReview, QuizReviewContent, RadioGroup, RadioGroupItem, ScrollArea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SettingsModal, Skeleton, SuggestionDialog, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadResourceModal, UserAnswerType };
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 {
@@ -139556,20 +139561,30 @@ var PracticeHistoryService = class {
139556
139561
  static saveHistory(history2) {
139557
139562
  if (typeof window !== "undefined") {
139558
139563
  try {
139559
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(history2));
139564
+ localStorage.setItem(
139565
+ LOCAL_STORAGE_KEY,
139566
+ JSON.stringify(history2)
139567
+ );
139560
139568
  } catch (e3) {
139561
- console.error("Error saving practice history to localStorage:", e3);
139569
+ console.error(
139570
+ "Error saving practice history to localStorage:",
139571
+ e3
139572
+ );
139562
139573
  }
139563
139574
  }
139564
139575
  }
139565
139576
  static saveCompletedPracticeSession(quizConfig, result, review = null) {
139566
139577
  const history2 = this.getPracticeHistory();
139567
139578
  const topicsCovered = Array.from(
139568
- new Set(quizConfig.questions.map((q2) => JSON.stringify({
139569
- subject: q2.subject || "Uncategorized",
139570
- category: q2.category || "General",
139571
- topic: q2.topic || "General Topic"
139572
- })))
139579
+ new Set(
139580
+ quizConfig.questions.map(
139581
+ (q2) => JSON.stringify({
139582
+ subject: q2.subject || "Uncategorized",
139583
+ category: q2.category || "General",
139584
+ topic: q2.topic || "General Topic"
139585
+ })
139586
+ )
139587
+ )
139573
139588
  ).map((s4) => JSON.parse(s4));
139574
139589
  const newSession = {
139575
139590
  id: `session_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
@@ -139586,6 +139601,14 @@ var PracticeHistoryService = class {
139586
139601
  };
139587
139602
  history2.unshift(newSession);
139588
139603
  this.saveHistory(history2);
139604
+ if (this.syncProvider) {
139605
+ console.log(
139606
+ `Sync provider found. Pushing session ${newSession.id} to backend.`
139607
+ );
139608
+ this.syncProvider.pushSession(newSession).catch((err) => {
139609
+ console.error("Sync provider failed to push session:", err);
139610
+ });
139611
+ }
139589
139612
  return newSession.id;
139590
139613
  }
139591
139614
  static updatePracticeReview(sessionId, review) {
@@ -139595,7 +139618,9 @@ var PracticeHistoryService = class {
139595
139618
  history2[sessionIndex].quizReview = review;
139596
139619
  this.saveHistory(history2);
139597
139620
  } else {
139598
- console.warn(`Could not find session with ID "${sessionId}" to update review.`);
139621
+ console.warn(
139622
+ `Could not find session with ID "${sessionId}" to update review.`
139623
+ );
139599
139624
  }
139600
139625
  }
139601
139626
  static getPracticeSessionById(sessionId) {
@@ -139653,7 +139678,9 @@ var PracticeHistoryService = class {
139653
139678
  const currentDay = new Date(sortedDays[i2]);
139654
139679
  const nextDay = new Date(sortedDays[i2 + 1]);
139655
139680
  const diffTime = currentDay.getTime() - nextDay.getTime();
139656
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
139681
+ const diffDays = Math.round(
139682
+ diffTime / (1e3 * 60 * 60 * 24)
139683
+ );
139657
139684
  if (diffDays === 1) {
139658
139685
  currentStreak++;
139659
139686
  } else {
@@ -139685,22 +139712,31 @@ var PracticeHistoryService = class {
139685
139712
  const subjectPerf = {};
139686
139713
  const topicPerf = {};
139687
139714
  history2.forEach((session) => {
139688
- session.summary.topics.forEach((topicInfo) => {
139689
- if (session.summary.percentage !== null) {
139690
- if (!subjectPerf[topicInfo.subject]) subjectPerf[topicInfo.subject] = { total: 0, count: 0 };
139691
- subjectPerf[topicInfo.subject].total += session.summary.percentage;
139692
- subjectPerf[topicInfo.subject].count++;
139693
- if (!topicPerf[topicInfo.topic]) topicPerf[topicInfo.topic] = { total: 0, count: 0 };
139694
- topicPerf[topicInfo.topic].total += session.summary.percentage;
139695
- topicPerf[topicInfo.topic].count++;
139715
+ session.summary.topics.forEach(
139716
+ (topicInfo) => {
139717
+ if (session.summary.percentage !== null) {
139718
+ if (!subjectPerf[topicInfo.subject])
139719
+ subjectPerf[topicInfo.subject] = {
139720
+ total: 0,
139721
+ count: 0
139722
+ };
139723
+ subjectPerf[topicInfo.subject].total += session.summary.percentage;
139724
+ subjectPerf[topicInfo.subject].count++;
139725
+ if (!topicPerf[topicInfo.topic])
139726
+ topicPerf[topicInfo.topic] = { total: 0, count: 0 };
139727
+ topicPerf[topicInfo.topic].total += session.summary.percentage;
139728
+ topicPerf[topicInfo.topic].count++;
139729
+ }
139696
139730
  }
139697
- });
139731
+ );
139698
139732
  });
139699
139733
  const formatPerf = (perfData) => {
139700
139734
  return Object.entries(perfData).map(([name3, data]) => ({
139701
139735
  name: name3,
139702
139736
  totalSessions: data.count,
139703
- averageScore: parseFloat((data.total / data.count).toFixed(2))
139737
+ averageScore: parseFloat(
139738
+ (data.total / data.count).toFixed(2)
139739
+ )
139704
139740
  })).sort((a4, b2) => b2.totalSessions - a4.totalSessions);
139705
139741
  };
139706
139742
  return {
@@ -139718,6 +139754,8 @@ var PracticeHistoryService = class {
139718
139754
  }
139719
139755
  }
139720
139756
  };
139757
+ // NEW: A static property to hold the injected sync provider
139758
+ PracticeHistoryService.syncProvider = null;
139721
139759
 
139722
139760
  // src/services/RoadmapService.ts
139723
139761
  init_react_shim();
@@ -167198,15 +167236,19 @@ var ClientTranslation = ({ tKey, options, fallback: fallback2 }) => {
167198
167236
  };
167199
167237
 
167200
167238
  // src/react-ui/components/app/PersonalPracticeDashboard.tsx
167201
- var PersonalPracticeDashboard = ({ settingsPath }) => {
167239
+ var PersonalPracticeDashboard = ({ settingsPath, initialHistory, initialStats, isControlled = false }) => {
167202
167240
  const router = useRouter();
167203
167241
  const { toast: toast2 } = useToast();
167204
167242
  const { t: t4, i18n } = useTranslation();
167205
167243
  const [isMounted, setIsMounted] = useState(false);
167206
167244
  useEffect(() => setIsMounted(true), []);
167207
167245
  const [isLoading, setIsLoading] = useState(true);
167208
- const [stats, setStats] = useState(null);
167209
- const [history2, setHistory] = useState([]);
167246
+ const [stats, setStats] = useState(
167247
+ initialStats || null
167248
+ );
167249
+ const [history2, setHistory] = useState(
167250
+ initialHistory || []
167251
+ );
167210
167252
  const [userName, setUserName] = useState(null);
167211
167253
  const [allAchievements, setAllAchievements] = useState([]);
167212
167254
  const [motivationalQuote, setMotivationalQuote] = useState(null);
@@ -167220,6 +167262,10 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167220
167262
  );
167221
167263
  const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
167222
167264
  const loadDashboardData = useCallback(() => {
167265
+ if (isControlled) {
167266
+ setIsLoading(false);
167267
+ return;
167268
+ }
167223
167269
  setIsLoading(true);
167224
167270
  setDashboardLayout(DashboardLayoutService.getLayout());
167225
167271
  const practiceHistory = PracticeHistoryService.getPracticeHistory();
@@ -167248,10 +167294,20 @@ var PersonalPracticeDashboard = ({ settingsPath }) => {
167248
167294
  if (KnowledgeCardService.getPendingConcepts().length > 0) {
167249
167295
  CardGenerationProcessor.processQueue();
167250
167296
  }
167251
- }, [toast2, t4]);
167297
+ }, [isControlled, toast2, t4]);
167252
167298
  useEffect(() => {
167253
- loadDashboardData();
167254
- }, [loadDashboardData]);
167299
+ if (isControlled) {
167300
+ setHistory(initialHistory || []);
167301
+ setStats(initialStats || null);
167302
+ const name3 = UserConfigService.getFullName();
167303
+ setUserName(name3);
167304
+ const achievementsWithStatus = AchievementService.getAllAchievementsWithStatus();
167305
+ setAllAchievements(achievementsWithStatus);
167306
+ setIsLoading(false);
167307
+ } else {
167308
+ loadDashboardData();
167309
+ }
167310
+ }, [isControlled, initialHistory, initialStats, loadDashboardData]);
167255
167311
  useEffect(() => {
167256
167312
  setMotivationalQuote(QuoteService.getRandomQuote());
167257
167313
  const fetchAIQuote = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
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",