musora-content-services 2.27.1 → 2.27.3

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.
Files changed (167) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/.yarnrc.yml +1 -0
  10. package/CHANGELOG.md +20 -0
  11. package/README.md +0 -0
  12. package/babel.config.cjs +0 -0
  13. package/docs/Content-Organization.html +0 -0
  14. package/docs/ContentOrganization.html +0 -0
  15. package/docs/Gamification.html +0 -0
  16. package/docs/UserManagement.html +0 -0
  17. package/docs/UserManagementSystem.html +0 -0
  18. package/docs/api_types.js.html +0 -0
  19. package/docs/config.js.html +0 -0
  20. package/docs/content-org_content-org.js.html +0 -0
  21. package/docs/content-org_playlists-types.js.html +0 -0
  22. package/docs/content-org_playlists.js.html +0 -0
  23. package/docs/content.js.html +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  28. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  29. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  30. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  31. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  39. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  42. package/docs/gamification_awards.js.html +0 -0
  43. package/docs/gamification_gamification.js.html +0 -0
  44. package/docs/gamification_types.js.html +0 -0
  45. package/docs/global.html +0 -0
  46. package/docs/global.html#User +0 -0
  47. package/docs/index.html +0 -0
  48. package/docs/module-Awards.html +0 -0
  49. package/docs/module-Config.html +0 -0
  50. package/docs/module-Content-Services-V2.html +0 -0
  51. package/docs/module-Interests.html +0 -0
  52. package/docs/module-Notifications.html +0 -0
  53. package/docs/module-Permissions.html +0 -0
  54. package/docs/module-Playlists.html +0 -0
  55. package/docs/module-Railcontent-Services.html +0 -0
  56. package/docs/module-Sanity-Services.html +0 -0
  57. package/docs/module-Session-Management.html +0 -0
  58. package/docs/module-Sessions.html +0 -0
  59. package/docs/module-User-Activity.html +0 -0
  60. package/docs/module-User-Management.html +0 -0
  61. package/docs/module-User-Permissions.html +0 -0
  62. package/docs/module-UserActivity.html +0 -0
  63. package/docs/module-UserChat.html +0 -0
  64. package/docs/module-UserManagement.html +0 -0
  65. package/docs/module-UserNotifications.html +0 -0
  66. package/docs/module-UserProfile.html +0 -0
  67. package/docs/railcontent.js.html +0 -0
  68. package/docs/sanity.js.html +0 -0
  69. package/docs/scripts/collapse.js +0 -0
  70. package/docs/scripts/commonNav.js +0 -0
  71. package/docs/scripts/linenumber.js +0 -0
  72. package/docs/scripts/nav.js +0 -0
  73. package/docs/scripts/polyfill.js +0 -0
  74. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  75. package/docs/scripts/prettify/lang-css.js +0 -0
  76. package/docs/scripts/prettify/prettify.js +0 -0
  77. package/docs/scripts/search.js +0 -0
  78. package/docs/styles/jsdoc.css +0 -0
  79. package/docs/styles/prettify.css +0 -0
  80. package/docs/types.js.html +0 -0
  81. package/docs/userActivity.js.html +0 -0
  82. package/docs/user_chat.js.html +0 -0
  83. package/docs/user_interests.js.html +0 -0
  84. package/docs/user_management.js.html +0 -0
  85. package/docs/user_notifications.js.html +0 -0
  86. package/docs/user_permissions.js.html +0 -0
  87. package/docs/user_profile.js.html +0 -0
  88. package/docs/user_sessions.js.html +0 -0
  89. package/docs/user_types.js.html +0 -0
  90. package/docs/user_user-management-system.js.html +0 -0
  91. package/docs/user_user-management.js.html +0 -0
  92. package/jest.config.js +0 -0
  93. package/jsdoc.json +0 -0
  94. package/package.json +2 -1
  95. package/src/contentMetaData.js +1 -0
  96. package/src/contentTypeConfig.js +6 -5
  97. package/src/filterBuilder.js +0 -0
  98. package/src/index.d.ts +7 -5
  99. package/src/index.js +7 -5
  100. package/src/infrastructure/http/HttpClient.ts +0 -0
  101. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  102. package/src/infrastructure/http/index.ts +0 -0
  103. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  104. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  105. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  106. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  107. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  108. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  109. package/src/lib/httpHelper.js +0 -0
  110. package/src/lib/lastUpdated.js +0 -0
  111. package/src/services/api/types.js +0 -0
  112. package/src/services/config.js +0 -0
  113. package/src/services/content-org/content-org.js +0 -0
  114. package/src/services/content-org/guided-courses.ts +0 -0
  115. package/src/services/content-org/playlists-types.js +0 -0
  116. package/src/services/content-org/playlists.js +0 -0
  117. package/src/services/content.js +0 -0
  118. package/src/services/contentAggregator.js +0 -0
  119. package/src/services/contentLikes.js +0 -0
  120. package/src/services/contentProgress.js +6 -26
  121. package/src/services/dataContext.js +0 -0
  122. package/src/services/dateUtils.js +31 -42
  123. package/src/services/forum.js +0 -0
  124. package/src/services/gamification/awards.js +0 -0
  125. package/src/services/gamification/gamification.js +0 -0
  126. package/src/services/gamification/types.js +0 -0
  127. package/src/services/imageSRCBuilder.js +0 -0
  128. package/src/services/railcontent.js +6 -9
  129. package/src/services/recommendations.js +0 -0
  130. package/src/services/sanity.js +7 -1
  131. package/src/services/types.js +0 -0
  132. package/src/services/user/account.ts +0 -0
  133. package/src/services/user/chat.js +0 -0
  134. package/src/services/user/interests.js +0 -0
  135. package/src/services/user/management.js +0 -0
  136. package/src/services/user/notifications.js +0 -0
  137. package/src/services/user/permissions.js +0 -0
  138. package/src/services/user/profile.js +0 -0
  139. package/src/services/user/sessions.js +0 -0
  140. package/src/services/user/types.js +0 -0
  141. package/src/services/user/user-management-system.js +0 -0
  142. package/src/services/userActivity.js +75 -68
  143. package/test/HttpClient.test.js +0 -0
  144. package/test/content.test.js +0 -0
  145. package/test/contentLikes.test.js +0 -0
  146. package/test/contentProgress.test.js +0 -0
  147. package/test/dataContext.test.js +0 -0
  148. package/test/forum.test.js +0 -0
  149. package/test/imageSRCBuilder.test.js +0 -0
  150. package/test/imageSRCVerify.test.js +0 -0
  151. package/test/initializeTests.js +0 -0
  152. package/test/lib/lastUpdated.test.js +0 -0
  153. package/test/live/contentProgressLive.test.js +0 -0
  154. package/test/live/railcontentLive.test.js +0 -0
  155. package/test/localStorageMock.js +0 -0
  156. package/test/log.js +0 -0
  157. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  158. package/test/mockData/mockData_progress_content.json +0 -0
  159. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  160. package/test/mockData/mockData_user_practices.json +0 -0
  161. package/test/notifications.test.js +0 -0
  162. package/test/progressRows.test.js +0 -0
  163. package/test/sanityQueryService.test.js +0 -0
  164. package/test/streakMessage.test.js +0 -0
  165. package/test/user/permissions.test.js +0 -0
  166. package/test/userActivity.test.js +0 -0
  167. package/tools/generate-index.cjs +0 -0
@@ -1,58 +1,47 @@
1
1
  // dateUtils.js
2
+ import dayjs from "dayjs";
3
+ import utc from "dayjs/plugin/utc";
4
+ import timezone from "dayjs/plugin/timezone";
5
+ import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
6
+ import isoWeek from 'dayjs/plugin/isoWeek';
7
+ import isBetween from 'dayjs/plugin/isBetween'
8
+ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
2
9
 
3
- export function convertToTimeZone(date, timeZone) {
4
- const formatter = new Intl.DateTimeFormat('en-US', {
5
- timeZone,
6
- year: 'numeric',
7
- month: '2-digit',
8
- day: '2-digit',
9
- hour: '2-digit',
10
- minute: '2-digit',
11
- second: '2-digit',
12
- hour12: false,
13
- });
10
+ dayjs.extend(utc);
11
+ dayjs.extend(timezone);
12
+ dayjs.extend(isSameOrAfter)
13
+ dayjs.extend(isoWeek)
14
+ dayjs.extend(isBetween)
15
+ dayjs.extend(isSameOrBefore)
14
16
 
15
- const parts = formatter.formatToParts(date).reduce((acc, part) => {
16
- if (part.type !== 'literal') acc[part.type] = part.value;
17
- return acc;
18
- }, {});
17
+ export function toDayjs(date, timeZone = 'UTC') {
18
+ return dayjs.tz(date, timeZone).startOf('day')
19
+ }
19
20
 
20
- return new Date(`${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}`);
21
+ export function convertToTimeZone(date, timeZone) {
22
+ return dayjs(date).tz(timeZone).format('YYYY-MM-DD');
21
23
  }
22
- // Get start of the week (Monday)
23
- export function getMonday(date) {
24
- const d = new Date(date);
25
- const day = d.getDay();
26
- const diff = d.getDate() - day + (day === 0 ? -6 : 1);
27
- return new Date(d.setDate(diff));
24
+
25
+ export function getMonday(date, timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone) {
26
+ // Use isoWeekday(1) - Monday is 1
27
+ return toDayjs(date, timeZone).isoWeekday(1)
28
28
  }
29
29
 
30
- // Get the week number
31
- export function getWeekNumber(d) {
32
- let newDate = new Date(d.getTime());
33
- newDate.setUTCDate(newDate.getUTCDate() + 4 - (newDate.getUTCDay()||7));
34
- var yearStart = new Date(Date.UTC(newDate.getUTCFullYear(),0,1));
35
- var weekNo = Math.ceil(( ( (newDate - yearStart) / 86400000) + 1)/7);
36
- return weekNo;
30
+ // Get the ISO week number for a dayjs object
31
+ export function getWeekNumber(date) {
32
+ return dayjs(date).isoWeek()
37
33
  }
38
34
  //Check if two dates are the same
39
- export function isSameDate(date1, date2, method = '') {
40
- return date1.toISOString().split('T')[0] === date2.toISOString().split('T')[0];
35
+ export function isSameDate(date1, date2) {
36
+ return dayjs(date1).isSame(dayjs(date2), 'day')
41
37
  }
42
38
 
43
39
  // Check if two dates are consecutive days
44
- export function isNextDay(prev, current) {
45
- const prevDate = new Date(prev.getFullYear(), prev.getMonth(), prev.getDate());
46
- const nextDate = new Date(prevDate);
47
- nextDate.setDate(prevDate.getDate() + 1); // Add 1 day
48
-
49
- return (
50
- nextDate.getFullYear() === current.getFullYear() &&
51
- nextDate.getMonth() === current.getMonth() &&
52
- nextDate.getDate() === current.getDate()
53
- );
40
+ export function isNextDay(date1, date2) {
41
+ const d1 = dayjs(date1).startOf('day')
42
+ const d2 = dayjs(date2).startOf('day')
43
+ return d2.diff(d1, 'day') === 1
54
44
  }
55
-
56
45
  export function getTimeRemainingUntilLocal(targetUtcIsoString, {withTotalSeconds} = {}) {
57
46
  const targetUTC = new Date(targetUtcIsoString);
58
47
  if (isNaN(targetUTC.getTime())) {
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -323,6 +323,11 @@ export async function fetchContentProgress(currentVersion) {
323
323
  return fetchDataHandler(url, currentVersion)
324
324
  }
325
325
 
326
+ export async function postPlaylistContentEngaged(playlistItemId) {
327
+ let url = `/railtracker/v1/last-engaged/${playlistItemId}`
328
+ return postDataHandler(url)
329
+ }
330
+
326
331
  export async function postRecordWatchSession(
327
332
  contentId,
328
333
  mediaTypeId,
@@ -786,17 +791,9 @@ export async function fetchUserPractices(currentVersion = 0, { userId } = {}) {
786
791
 
787
792
  const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
788
793
 
789
-
790
794
  const formattedPractices = userPractices.reduce((acc, practice) => {
791
795
  // Convert UTC date to user's local date (still a Date object)
792
- const utcDate = new Date(practice.day);
793
- const localDate = convertToTimeZone(utcDate, userTimeZone);
794
-
795
- const userTimeZoneDay =
796
- localDate.getFullYear() + '-' +
797
- String(localDate.getMonth() + 1).padStart(2, '0') + '-' +
798
- String(localDate.getDate()).padStart(2, '0');
799
-
796
+ const userTimeZoneDay = convertToTimeZone(practice.day, userTimeZone);
800
797
  if (!acc[userTimeZoneDay]) {
801
798
  acc[userTimeZoneDay] = [];
802
799
  }
File without changes
@@ -31,6 +31,7 @@ import {
31
31
  import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
32
32
  import { fetchUserPermissions } from './user/permissions.js'
33
33
  import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
34
+ import {fetchRecentActivitiesActiveTabs} from "./userActivity.js";
34
35
 
35
36
  /**
36
37
  * Exported functions that are excluded from index generation.
@@ -2152,7 +2153,12 @@ export async function fetchShowsData(brand) {
2152
2153
  * .catch(error => console.error(error));
2153
2154
  */
2154
2155
  export async function fetchMetadata(brand, type) {
2155
- const processedData = processMetadata(brand, type, true)
2156
+ let processedData = processMetadata(brand, type, true)
2157
+ if(processedData?.onlyAvailableTabs === true) {
2158
+ const activeTabs = await fetchRecentActivitiesActiveTabs()
2159
+ processedData.tabs = activeTabs
2160
+ }
2161
+
2156
2162
  return processedData ? processedData : {}
2157
2163
  }
2158
2164
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -16,7 +16,7 @@ import { DataContext, UserActivityVersionKey } from './dataContext.js'
16
16
  import { fetchByRailContentIds, fetchShows } from './sanity'
17
17
  import {fetchPlaylist, fetchUserPlaylists} from "./content-org/playlists"
18
18
  import {pinnedGuidedCourses} from "./content-org/guided-courses"
19
- import {convertToTimeZone, getMonday, getWeekNumber, isSameDate, isNextDay, getTimeRemainingUntilLocal} from './dateUtils.js'
19
+ import {convertToTimeZone, getMonday, getWeekNumber, isSameDate, isNextDay, getTimeRemainingUntilLocal, toDayjs} from './dateUtils.js'
20
20
  import { globalConfig } from './config'
21
21
  import {collectionLessonTypes, lessonTypesMapping, progressTypesMapping, showsLessonTypes, songs} from "../contentTypeConfig";
22
22
  import {
@@ -27,6 +27,9 @@ import {
27
27
  } from "./contentProgress";
28
28
  import {TabResponseType} from "../contentMetaData";
29
29
  import {isContentLikedByIds} from "./contentLikes.js";
30
+ import dayjs from 'dayjs'
31
+ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
32
+ import weekOfYear from 'dayjs/plugin/weekOfYear'
30
33
 
31
34
  const DATA_KEY_PRACTICES = 'practices'
32
35
  const DATA_KEY_LAST_UPDATED_TIME = 'u'
@@ -86,24 +89,21 @@ export let userActivityContext = new DataContext(UserActivityVersionKey, fetchUs
86
89
  * .catch(error => console.error(error));
87
90
  */
88
91
  export async function getUserWeeklyStats() {
92
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
89
93
  let data = await userActivityContext.getData()
90
94
  let practices = data?.[DATA_KEY_PRACTICES] ?? {}
91
95
  let sortedPracticeDays = Object.keys(practices)
92
- .map((date) => new Date(date))
93
- .sort((a, b) => b - a)
94
-
95
- let today = new Date()
96
- today.setHours(0, 0, 0, 0)
97
- let startOfWeek = getMonday(today) // Get last Monday
96
+ .map((date) => toDayjs(date)) // Convert to dayjs instance
97
+ .sort((a, b) => b.valueOf() - a.valueOf())
98
+ let today = dayjs()
99
+ let startOfWeek = getMonday(today, timeZone) // Get last Monday
98
100
  let dailyStats = []
99
-
100
101
  for (let i = 0; i < 7; i++) {
101
- let day = new Date(startOfWeek)
102
- day.setDate(startOfWeek.getDate() + i)
103
- let hasPractice = sortedPracticeDays.some((practiceDate) => isSameDate(practiceDate, day))
102
+ const day = startOfWeek.add(i, 'day')
103
+ let hasPractice = sortedPracticeDays.some((practiceDate) => isSameDate(practiceDate, day.format('YYYY-MM-DD')))
104
104
  let isActive = isSameDate(today, day)
105
105
  let type = hasPractice ? 'tracked' : isActive ? 'active' : 'none'
106
- dailyStats.push({ key: i, label: DAYS[i], isActive, inStreak: hasPractice, type })
106
+ dailyStats.push({ key: i, label: DAYS[i], isActive, inStreak: hasPractice, type, day: day.format('YYYY-MM-DD') })
107
107
  }
108
108
 
109
109
  let { streakMessage } = getStreaksAndMessage(practices)
@@ -142,83 +142,77 @@ export async function getUserWeeklyStats() {
142
142
  * getUserMonthlyStats({ userId: 123 }).then(console.log);
143
143
  */
144
144
  export async function getUserMonthlyStats(params = {}) {
145
- const now = new Date()
145
+ const now = dayjs()
146
146
  const {
147
- year = now.getFullYear(),
148
- month = now.getMonth(),
149
- day = 1,
147
+ year = now.year(),
148
+ month = now.month(), // 0-indexed
150
149
  userId = globalConfig.sessionConfig.userId,
151
150
  } = params
152
- let practices = await getUserPractices(userId)
151
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
152
+ const practices = await getUserPractices(userId)
153
153
 
154
- // Get the first day of the specified month and the number of days in that month
155
- let firstDayOfMonth = new Date(year, month, 1)
156
- let today = new Date()
157
- today.setHours(0, 0, 0, 0)
154
+ const firstDayOfMonth = dayjs.tz(`${year}-${month + 1}-01`, timeZone).startOf('day')
155
+ const endOfMonth = firstDayOfMonth.endOf('month')
156
+ const today = dayjs().tz(timeZone).startOf('day')
158
157
 
159
- let startOfGrid = getMonday(firstDayOfMonth)
158
+ let startOfGrid = getMonday(firstDayOfMonth, timeZone)
160
159
 
161
- let previousWeekStart = new Date(startOfGrid)
162
- previousWeekStart.setDate(previousWeekStart.getDate() - 7)
163
-
164
- let previousWeekEnd = new Date(startOfGrid)
165
- previousWeekEnd.setDate(previousWeekEnd.getDate() - 1)
160
+ // Previous week range
161
+ const previousWeekStart = startOfGrid.subtract(7, 'day')
162
+ const previousWeekEnd = startOfGrid.subtract(1, 'day')
166
163
 
167
164
  let hadStreakBeforeMonth = false
168
- for (let d = new Date(previousWeekStart); d <= previousWeekEnd; d.setDate(d.getDate() + 1)) {
169
- let dayKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
170
- if (practices[dayKey]) {
165
+ for (let d = previousWeekStart.clone(); d.isSameOrBefore(previousWeekEnd); d = d.add(1, 'day')) {
166
+ const key = d.format('YYYY-MM-DD')
167
+ if (practices[key]) {
171
168
  hadStreakBeforeMonth = true
172
169
  break
173
170
  }
174
171
  }
175
172
 
176
- let endOfMonth = new Date(year, month + 1, 0)
177
- let endOfGrid = new Date(year, month + 1, 0)
178
- while (endOfGrid.getDay() !== 0) {
179
- endOfGrid.setDate(endOfGrid.getDate() + 1)
173
+ // let endOfMonth = new Date(year, month + 1, 0)
174
+ let endOfGrid = endOfMonth.clone()
175
+ while (endOfGrid.day() !== 0) {
176
+ endOfGrid = endOfGrid.add(1, 'day')
180
177
  }
181
- let daysInMonth = Math.ceil((endOfGrid - startOfGrid) / (1000 * 60 * 60 * 24)) + 1
182
-
178
+ const daysInMonth = endOfGrid.diff(startOfGrid, 'day') + 1
183
179
  let dailyStats = []
184
180
  let practiceDuration = 0
185
181
  let daysPracticed = 0
186
182
  let weeklyStats = {}
187
183
 
188
184
  for (let i = 0; i < daysInMonth; i++) {
189
- let day = new Date(startOfGrid)
190
- day.setDate(startOfGrid.getDate() + i)
191
- let dayKey = `${day.getFullYear()}-${String(day.getMonth() + 1).padStart(2, '0')}-${String(day.getDate()).padStart(2, '0')}`
192
-
193
- // Check if the user has activity for the day
194
- let dayActivity = practices[dayKey] ?? null
185
+ let day = startOfGrid.add(i, 'day')
186
+ let key = day.format('YYYY-MM-DD')
187
+ let activity = practices[key] ?? null
195
188
  let weekKey = getWeekNumber(day)
196
189
 
197
190
  if (!weeklyStats[weekKey]) {
198
191
  weeklyStats[weekKey] = { key: weekKey, inStreak: false }
199
192
  }
200
193
 
201
- if (dayActivity !== null && firstDayOfMonth <= day && day <= endOfMonth) {
202
- practiceDuration += dayActivity.reduce((sum, entry) => sum + entry.duration_seconds, 0)
194
+ if (activity && day.isBetween(firstDayOfMonth, endOfMonth, null, '[]')) {
195
+ practiceDuration += activity.reduce((sum, entry) => sum + entry.duration_seconds, 0)
203
196
  daysPracticed++
204
197
  }
205
198
 
206
- let isActive = isSameDate(today, day)
207
- let type = dayActivity !== null ? 'tracked' : isActive ? 'active' : 'none'
208
- let isInStreak = dayActivity !== null
209
- if (isInStreak) {
199
+ if (activity) {
210
200
  weeklyStats[weekKey].inStreak = true
211
201
  }
212
202
 
203
+ const isActive = day.isSame(today, 'day')
204
+ const type = activity ? 'tracked' : isActive ? 'active' : 'none'
205
+
213
206
  dailyStats.push({
214
207
  key: i,
215
- label: dayKey,
208
+ label: key,
216
209
  isActive,
217
- inStreak: dayActivity !== null,
210
+ inStreak: !!activity,
218
211
  type,
219
212
  })
220
213
  }
221
214
 
215
+ // Continue streak into month
222
216
  if (hadStreakBeforeMonth) {
223
217
  const firstWeekKey = getWeekNumber(startOfGrid)
224
218
  if (weeklyStats[firstWeekKey]) {
@@ -226,14 +220,15 @@ export async function getUserMonthlyStats(params = {}) {
226
220
  }
227
221
  }
228
222
 
229
- let filteredPractices = Object.keys(practices)
230
- .filter((date) => new Date(date) <= endOfMonth)
231
- .reduce((obj, key) => {
232
- obj[key] = practices[key]
233
- return obj
223
+ // Filter past practices only
224
+ let filteredPractices = Object.entries(practices)
225
+ .filter(([date]) => dayjs.tz(date, timeZone).isSameOrBefore(endOfMonth))
226
+ .reduce((acc, [date, val]) => {
227
+ acc[date] = val
228
+ return acc
234
229
  }, {})
235
230
 
236
- let { currentDailyStreak, currentWeeklyStreak } = calculateStreaks(filteredPractices)
231
+ const { currentDailyStreak, currentWeeklyStreak } = calculateStreaks(filteredPractices)
237
232
 
238
233
  return {
239
234
  data: {
@@ -392,12 +387,7 @@ export async function restoreUserPractice(id) {
392
387
  if (restoredPractice) {
393
388
  await userActivityContext.updateLocal(async function (localContext) {
394
389
  const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
395
- const utcDate = new Date(restoredPractice.day)
396
- const localDate = convertToTimeZone(utcDate, userTimeZone)
397
- const date =
398
- localDate.getFullYear() + '-' +
399
- String(localDate.getMonth() + 1).padStart(2, '0') + '-' +
400
- String(localDate.getDate()).padStart(2, '0')
390
+ const date = convertToTimeZone(restoredPractice.day, userTimeZone)
401
391
  if (localContext.data[DATA_KEY_PRACTICES][date]) {
402
392
  localContext.data[DATA_KEY_PRACTICES][date] = []
403
393
  }
@@ -621,7 +611,7 @@ function getStreaksAndMessage(practices) {
621
611
  }
622
612
  }
623
613
 
624
- async function getUserPracticeIds(day = new Date().toISOString().split('T')[0], userId = null) {
614
+ async function getUserPracticeIds(day = dayjs().format('YYYY-MM-DD'), userId = null) {
625
615
  let practices = {}
626
616
  if (userId !== globalConfig.sessionConfig.userId) {
627
617
  let data = await fetchUserPractices(0, { userId: userId })
@@ -651,6 +641,7 @@ function calculateStreaks(practices, includeStreakMessage = false) {
651
641
  let lastActiveDay = null
652
642
  let streakMessage = ''
653
643
 
644
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
654
645
  let sortedPracticeDays = Object.keys(practices)
655
646
  .map((dateStr) => {
656
647
  const [year, month, day] = dateStr.split('-').map(Number)
@@ -702,9 +693,8 @@ function calculateStreaks(practices, includeStreakMessage = false) {
702
693
  let yesterday = new Date(today)
703
694
  yesterday.setDate(today.getDate() - 1)
704
695
 
705
- let currentWeekStart = getMonday(today)
706
- let lastWeekStart = new Date(currentWeekStart)
707
- lastWeekStart.setDate(currentWeekStart.getDate() - 7)
696
+ let currentWeekStart = getMonday(today, timeZone)
697
+ let lastWeekStart = currentWeekStart.subtract(7, 'days')
708
698
 
709
699
  let hasYesterdayPractice = sortedPracticeDays.some((date) => isSameDate(date, yesterday))
710
700
  let hasCurrentWeekPractice = sortedPracticeDays.some((date) => date >= currentWeekStart)
@@ -837,7 +827,6 @@ async function formatPracticeMeta(practices) {
837
827
  const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
838
828
 
839
829
  return practices.map((practice) => {
840
- const utcDate = new Date(practice.created_at)
841
830
  const content = contents.find((c) => c.id === practice.content_id) || {}
842
831
 
843
832
  return {
@@ -854,7 +843,7 @@ async function formatPracticeMeta(practices) {
854
843
  content_type: getFormattedType(content.type || '', content.brand),
855
844
  content_id: practice.content_id || null,
856
845
  content_brand: content.brand || null,
857
- created_at: convertToTimeZone(utcDate, userTimeZone),
846
+ created_at: convertToTimeZone(dayjs(practice.created_at), userTimeZone),
858
847
  sanity_type: content.type || null,
859
848
  content_slug: content.slug || null,
860
849
  }
@@ -973,6 +962,7 @@ export async function getProgressRows({ brand = null, limit = 8 } = {}) {
973
962
  childToParentMap[content.id] = content.parent_content_data[content.parent_content_data.length - 1];
974
963
  }
975
964
  });
965
+
976
966
  const progressMap = new Map();
977
967
  for (const [idStr, progress] of Object.entries(progressContents)) {
978
968
  const id = parseInt(idStr);
@@ -1474,4 +1464,21 @@ function getFirstLeafLessonId(data) {
1474
1464
  return data.lessons ? findFirstLeaf(data.lessons) : null
1475
1465
  }
1476
1466
 
1467
+ export async function fetchRecentActivitiesActiveTabs() {
1468
+ const url = `/api/user-management-system/v1/activities/tabs`
1469
+ try {
1470
+ const tabs = await fetchHandler(url, 'GET');
1471
+ const activitiesTabs = [];
1472
+
1473
+ tabs.forEach(tab => {
1474
+ activitiesTabs.push({ name: tab.label, short_name:tab.label });
1475
+ });
1476
+
1477
+ return activitiesTabs;
1478
+ } catch (error) {
1479
+ console.error('Error fetching activity tabs:', error);
1480
+ return [];
1481
+ }
1482
+ }
1483
+
1477
1484
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes