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.
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +20 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/docs/Content-Organization.html +0 -0
- package/docs/ContentOrganization.html +0 -0
- package/docs/Gamification.html +0 -0
- package/docs/UserManagement.html +0 -0
- package/docs/UserManagementSystem.html +0 -0
- package/docs/api_types.js.html +0 -0
- package/docs/config.js.html +0 -0
- package/docs/content-org_content-org.js.html +0 -0
- package/docs/content-org_playlists-types.js.html +0 -0
- package/docs/content-org_playlists.js.html +0 -0
- package/docs/content.js.html +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/gamification_awards.js.html +0 -0
- package/docs/gamification_gamification.js.html +0 -0
- package/docs/gamification_types.js.html +0 -0
- package/docs/global.html +0 -0
- package/docs/global.html#User +0 -0
- package/docs/index.html +0 -0
- package/docs/module-Awards.html +0 -0
- package/docs/module-Config.html +0 -0
- package/docs/module-Content-Services-V2.html +0 -0
- package/docs/module-Interests.html +0 -0
- package/docs/module-Notifications.html +0 -0
- package/docs/module-Permissions.html +0 -0
- package/docs/module-Playlists.html +0 -0
- package/docs/module-Railcontent-Services.html +0 -0
- package/docs/module-Sanity-Services.html +0 -0
- package/docs/module-Session-Management.html +0 -0
- package/docs/module-Sessions.html +0 -0
- package/docs/module-User-Activity.html +0 -0
- package/docs/module-User-Management.html +0 -0
- package/docs/module-User-Permissions.html +0 -0
- package/docs/module-UserActivity.html +0 -0
- package/docs/module-UserChat.html +0 -0
- package/docs/module-UserManagement.html +0 -0
- package/docs/module-UserNotifications.html +0 -0
- package/docs/module-UserProfile.html +0 -0
- package/docs/railcontent.js.html +0 -0
- package/docs/sanity.js.html +0 -0
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/docs/types.js.html +0 -0
- package/docs/userActivity.js.html +0 -0
- package/docs/user_chat.js.html +0 -0
- package/docs/user_interests.js.html +0 -0
- package/docs/user_management.js.html +0 -0
- package/docs/user_notifications.js.html +0 -0
- package/docs/user_permissions.js.html +0 -0
- package/docs/user_profile.js.html +0 -0
- package/docs/user_sessions.js.html +0 -0
- package/docs/user_types.js.html +0 -0
- package/docs/user_user-management-system.js.html +0 -0
- package/docs/user_user-management.js.html +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +2 -1
- package/src/contentMetaData.js +1 -0
- package/src/contentTypeConfig.js +6 -5
- package/src/filterBuilder.js +0 -0
- package/src/index.d.ts +7 -5
- package/src/index.js +7 -5
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/httpHelper.js +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +0 -0
- package/src/services/contentAggregator.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +6 -26
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +31 -42
- package/src/services/forum.js +0 -0
- package/src/services/gamification/awards.js +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/gamification/types.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/railcontent.js +6 -9
- package/src/services/recommendations.js +0 -0
- package/src/services/sanity.js +7 -1
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/sessions.js +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +75 -68
- package/test/HttpClient.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}, {});
|
|
17
|
+
export function toDayjs(date, timeZone = 'UTC') {
|
|
18
|
+
return dayjs.tz(date, timeZone).startOf('day')
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
export function convertToTimeZone(date, timeZone) {
|
|
22
|
+
return dayjs(date).tz(timeZone).format('YYYY-MM-DD');
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
export function getMonday(date) {
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
32
|
-
|
|
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
|
|
40
|
-
return date1
|
|
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(
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
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())) {
|
package/src/services/forum.js
CHANGED
|
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
|
|
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
|
package/src/services/sanity.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/services/types.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
|
|
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) =>
|
|
93
|
-
.sort((a, b) => b - a)
|
|
94
|
-
|
|
95
|
-
let
|
|
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
|
-
|
|
102
|
-
|
|
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 =
|
|
145
|
+
const now = dayjs()
|
|
146
146
|
const {
|
|
147
|
-
year = now.
|
|
148
|
-
month = now.
|
|
149
|
-
day = 1,
|
|
147
|
+
year = now.year(),
|
|
148
|
+
month = now.month(), // 0-indexed
|
|
150
149
|
userId = globalConfig.sessionConfig.userId,
|
|
151
150
|
} = params
|
|
152
|
-
|
|
151
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
152
|
+
const practices = await getUserPractices(userId)
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
162
|
-
previousWeekStart.
|
|
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 =
|
|
169
|
-
|
|
170
|
-
if (practices[
|
|
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
|
-
|
|
177
|
-
let endOfGrid =
|
|
178
|
-
while (endOfGrid.
|
|
179
|
-
endOfGrid
|
|
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
|
-
|
|
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 =
|
|
190
|
-
day.
|
|
191
|
-
let
|
|
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 (
|
|
202
|
-
practiceDuration +=
|
|
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
|
-
|
|
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:
|
|
208
|
+
label: key,
|
|
216
209
|
isActive,
|
|
217
|
-
inStreak:
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
package/test/HttpClient.test.js
CHANGED
|
File without changes
|
package/test/content.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/dataContext.test.js
CHANGED
|
File without changes
|
package/test/forum.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/initializeTests.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/localStorageMock.js
CHANGED
|
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
|
|
File without changes
|
package/tools/generate-index.cjs
CHANGED
|
File without changes
|