@nualang/nualang-ui-components 0.1.1430 → 0.1.1432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Cards/Bot/Bot.js +2 -12
- package/dist/Cards/Course/Course.js +2 -15
- package/dist/Cards/Roleplay/Roleplay.js +2 -12
- package/dist/Cards/Topic/Topic.js +2 -12
- package/dist/Dialogs/FindChatbotDialog/FindChatbotDialog.js +17 -68
- package/dist/Dialogs/FindCoursesDialog/FindCoursesDialog.js +75 -85
- package/dist/Dialogs/FindCoursesDialog/FindCoursesDialog.test.js +256 -1
- package/dist/Dialogs/FindScenarioDialog/FindScenarioDialog.js +17 -68
- package/dist/Dialogs/FindTopicsDialog/FindTopicsDialog.js +19 -175
- package/dist/Dialogs/QuickCreateChatbotDialog/QuickCreateChatbotDialog.js +2 -1
- package/dist/Dialogs/SearchFilters/SearchFilters.js +147 -75
- package/dist/Dialogs/SearchFilters/SearchFilters.test.js +65 -0
- package/dist/Forms/Settings/Tabs/General/General.js +2 -1
- package/dist/Forms/UpdateBot/UpdateBot.js +4 -2
- package/dist/Forms/UpdateClassroom/UpdateClassroom.js +8 -4
- package/dist/Forms/UpdateCourse/UpdateCourse.js +6 -3
- package/dist/Forms/UpdateGroup/UpdateGroup.js +4 -2
- package/dist/Forms/UpdatePhraseList/UpdatePhraseList.js +14 -3
- package/dist/Forms/UpdateRoleplay/UpdateRoleplay.js +4 -2
- package/dist/Forms/UpdateTopic/UpdateTopic.js +6 -3
- package/dist/Misc/StickyBar/StickyBar.js +64 -0
- package/dist/Misc/StickyBar/StickyBar.test.js +62 -0
- package/dist/Misc/StickyBar/package.json +6 -0
- package/dist/Navigation/Header/Header.js +1 -0
- package/dist/Navigation/ResponsiveTabs/ResponsiveTabs.js +37 -7
- package/package.json +1 -1
package/dist/Cards/Bot/Bot.js
CHANGED
|
@@ -135,12 +135,7 @@ function Bot({
|
|
|
135
135
|
children: [/*#__PURE__*/_jsx(CardTop, {
|
|
136
136
|
children: /*#__PURE__*/_jsx(Skeleton, {
|
|
137
137
|
variant: "rectangular",
|
|
138
|
-
sx:
|
|
139
|
-
...classes.media,
|
|
140
|
-
...(compact ? {
|
|
141
|
-
paddingTop: "40%"
|
|
142
|
-
} : {})
|
|
143
|
-
}
|
|
138
|
+
sx: classes.media
|
|
144
139
|
})
|
|
145
140
|
}), /*#__PURE__*/_jsx(CardHeader, {
|
|
146
141
|
action: /*#__PURE__*/_jsx(IconButton, {
|
|
@@ -199,12 +194,7 @@ function Bot({
|
|
|
199
194
|
onClick: selectBot,
|
|
200
195
|
role: "link",
|
|
201
196
|
children: [/*#__PURE__*/_jsx(ProgressiveCardMedia, {
|
|
202
|
-
sx:
|
|
203
|
-
...classes.media,
|
|
204
|
-
...(compact ? {
|
|
205
|
-
paddingTop: "40%"
|
|
206
|
-
} : {})
|
|
207
|
-
},
|
|
197
|
+
sx: classes.media,
|
|
208
198
|
highQualitySrc: visible || !pictureXs ? cardPicture || picture || PlaceholderImages.bot : null,
|
|
209
199
|
lowQualitySrc: pictureXs,
|
|
210
200
|
title: botName,
|
|
@@ -297,13 +297,7 @@ CourseDifficulty.propTypes = {
|
|
|
297
297
|
difficulty: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
|
|
298
298
|
compact: PropTypes.bool
|
|
299
299
|
};
|
|
300
|
-
|
|
301
|
-
// Keep compact cards' media at 16:9 even though it makes the card a little
|
|
302
|
-
// taller than the old 40% (2.5:1) crop - matches how the picture actually
|
|
303
|
-
// looks everywhere else it's shown.
|
|
304
|
-
const COMPACT_MEDIA_ASPECT_RATIO = "56.25%";
|
|
305
300
|
function CourseCardMedia({
|
|
306
|
-
compact,
|
|
307
301
|
visible,
|
|
308
302
|
cardPicture,
|
|
309
303
|
picture,
|
|
@@ -311,10 +305,7 @@ function CourseCardMedia({
|
|
|
311
305
|
placeholderRef
|
|
312
306
|
}) {
|
|
313
307
|
return /*#__PURE__*/_jsx(ProgressiveCardMedia, {
|
|
314
|
-
sx:
|
|
315
|
-
...classes.media,
|
|
316
|
-
paddingTop: COMPACT_MEDIA_ASPECT_RATIO
|
|
317
|
-
} : classes.media,
|
|
308
|
+
sx: classes.media,
|
|
318
309
|
highQualitySrc: visible || !pictureXs ? cardPicture || picture || PlaceholderImages.course : null,
|
|
319
310
|
lowQualitySrc: pictureXs,
|
|
320
311
|
placeholderRef: placeholderRef
|
|
@@ -373,10 +364,7 @@ function CompactCourseLoading() {
|
|
|
373
364
|
children: [/*#__PURE__*/_jsx(CardTop, {
|
|
374
365
|
children: /*#__PURE__*/_jsx(Skeleton, {
|
|
375
366
|
variant: "rectangular",
|
|
376
|
-
sx:
|
|
377
|
-
...classes.media,
|
|
378
|
-
paddingTop: COMPACT_MEDIA_ASPECT_RATIO
|
|
379
|
-
}
|
|
367
|
+
sx: classes.media
|
|
380
368
|
})
|
|
381
369
|
}), /*#__PURE__*/_jsx(CardHeader, {
|
|
382
370
|
title: /*#__PURE__*/_jsx(Skeleton, {
|
|
@@ -698,7 +686,6 @@ function CourseCard({
|
|
|
698
686
|
role: "link",
|
|
699
687
|
"aria-label": `${checked ? t("selected") : t("select")} ${courseName}`,
|
|
700
688
|
children: /*#__PURE__*/_jsx(CourseCardMedia, {
|
|
701
|
-
compact: true,
|
|
702
689
|
visible: visible,
|
|
703
690
|
cardPicture: cardPicture,
|
|
704
691
|
picture: picture,
|
|
@@ -135,12 +135,7 @@ function Roleplay({
|
|
|
135
135
|
children: [/*#__PURE__*/_jsx(CardTop, {
|
|
136
136
|
children: /*#__PURE__*/_jsx(Skeleton, {
|
|
137
137
|
variant: "rectangular",
|
|
138
|
-
sx:
|
|
139
|
-
...classes.media,
|
|
140
|
-
...(compact ? {
|
|
141
|
-
paddingTop: "40%"
|
|
142
|
-
} : {})
|
|
143
|
-
}
|
|
138
|
+
sx: classes.media
|
|
144
139
|
})
|
|
145
140
|
}), /*#__PURE__*/_jsx(CardHeader, {
|
|
146
141
|
action: /*#__PURE__*/_jsx(IconButton, {
|
|
@@ -199,12 +194,7 @@ function Roleplay({
|
|
|
199
194
|
role: "link",
|
|
200
195
|
"aria-label": "Card Image",
|
|
201
196
|
children: [/*#__PURE__*/_jsx(ProgressiveCardMedia, {
|
|
202
|
-
sx:
|
|
203
|
-
...classes.media,
|
|
204
|
-
...(compact ? {
|
|
205
|
-
paddingTop: "40%"
|
|
206
|
-
} : {})
|
|
207
|
-
},
|
|
197
|
+
sx: classes.media,
|
|
208
198
|
highQualitySrc: visible || !pictureXs ? cardPicture || picture || PlaceholderImages.roleplay : null,
|
|
209
199
|
lowQualitySrc: pictureXs,
|
|
210
200
|
placeholderRef: placeholderRef,
|
|
@@ -38,12 +38,7 @@ function Topic({
|
|
|
38
38
|
children: [/*#__PURE__*/_jsx(CardTop, {
|
|
39
39
|
children: /*#__PURE__*/_jsx(Skeleton, {
|
|
40
40
|
variant: "rectangular",
|
|
41
|
-
sx:
|
|
42
|
-
...classes.media,
|
|
43
|
-
...(compact ? {
|
|
44
|
-
paddingTop: "40%"
|
|
45
|
-
} : {})
|
|
46
|
-
}
|
|
41
|
+
sx: classes.media
|
|
47
42
|
})
|
|
48
43
|
}), /*#__PURE__*/_jsx(CardHeader, {
|
|
49
44
|
title: /*#__PURE__*/_jsx(Skeleton, {
|
|
@@ -94,12 +89,7 @@ function Topic({
|
|
|
94
89
|
disabled: disableActions,
|
|
95
90
|
role: "link",
|
|
96
91
|
children: /*#__PURE__*/_jsx(ProgressiveCardMedia, {
|
|
97
|
-
sx:
|
|
98
|
-
...classes.media,
|
|
99
|
-
...(compact ? {
|
|
100
|
-
paddingTop: "40%"
|
|
101
|
-
} : {})
|
|
102
|
-
},
|
|
92
|
+
sx: classes.media,
|
|
103
93
|
highQualitySrc: visible || !pictureXs ? cardPicture || picture || PlaceholderImages.topic : null,
|
|
104
94
|
lowQualitySrc: pictureXs,
|
|
105
95
|
title: topicName,
|
|
@@ -16,7 +16,7 @@ import CloseIcon from "@mui/icons-material/Close";
|
|
|
16
16
|
import AddIcon from "@mui/icons-material/Add";
|
|
17
17
|
import Bots from "../../Lists/Bots/Bots";
|
|
18
18
|
import PlaceholderImages from "../../utils/placeholder-images/index";
|
|
19
|
-
import { SearchFilters, Transition } from "../SearchFilters/SearchFilters";
|
|
19
|
+
import { SearchFilters, Transition, applyFilters, useSearchFilters } from "../SearchFilters/SearchFilters";
|
|
20
20
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
21
21
|
export default function FindChatbotDialog({
|
|
22
22
|
open,
|
|
@@ -43,11 +43,11 @@ export default function FindChatbotDialog({
|
|
|
43
43
|
// Restricted users are always kept on the library tab, regardless of state.
|
|
44
44
|
const effectiveTab = isCommunityAccessRestricted ? 0 : tab;
|
|
45
45
|
const [selectedBotIds, setSelectedBotIds] = useState([]);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
46
|
+
const {
|
|
47
|
+
filters,
|
|
48
|
+
resetFilters,
|
|
49
|
+
filterProps
|
|
50
|
+
} = useSearchFilters();
|
|
51
51
|
const allChatbots = [...libraryChatbots, ...communityChatbots];
|
|
52
52
|
const selectedChatbots = allChatbots.filter(bot => selectedBotIds.includes(bot.botId)).filter((bot, index, arr) => arr.findIndex(b => b.botId === bot.botId) === index).map(bot => ({
|
|
53
53
|
...bot,
|
|
@@ -56,16 +56,6 @@ export default function FindChatbotDialog({
|
|
|
56
56
|
const handleToggle = botId => {
|
|
57
57
|
setSelectedBotIds(prev => prev.includes(botId) ? prev.filter(id => id !== botId) : [...prev, botId]);
|
|
58
58
|
};
|
|
59
|
-
const handleFilterChange = event => {
|
|
60
|
-
const {
|
|
61
|
-
name,
|
|
62
|
-
value
|
|
63
|
-
} = event.target;
|
|
64
|
-
setFilters(prev => ({
|
|
65
|
-
...prev,
|
|
66
|
-
[name]: value
|
|
67
|
-
}));
|
|
68
|
-
};
|
|
69
59
|
const handleAdd = () => {
|
|
70
60
|
onAddChatbots(selectedChatbots);
|
|
71
61
|
reset();
|
|
@@ -73,11 +63,7 @@ export default function FindChatbotDialog({
|
|
|
73
63
|
};
|
|
74
64
|
const reset = () => {
|
|
75
65
|
setSelectedBotIds([]);
|
|
76
|
-
|
|
77
|
-
setFilters({
|
|
78
|
-
difficulty: "all",
|
|
79
|
-
sort: "newest"
|
|
80
|
-
});
|
|
66
|
+
resetFilters();
|
|
81
67
|
setTab(0);
|
|
82
68
|
};
|
|
83
69
|
const handleDialogClose = () => {
|
|
@@ -101,37 +87,14 @@ export default function FindChatbotDialog({
|
|
|
101
87
|
const handleTabChange = (_, v) => {
|
|
102
88
|
if (isCommunityAccessRestricted) return;
|
|
103
89
|
setTab(v);
|
|
104
|
-
|
|
105
|
-
setFilters({
|
|
106
|
-
difficulty: "all",
|
|
107
|
-
sort: "newest"
|
|
108
|
-
});
|
|
90
|
+
resetFilters();
|
|
109
91
|
if (scrollRef.current) scrollRef.current.scrollTop = 0;
|
|
110
92
|
};
|
|
111
|
-
const
|
|
112
|
-
if (!search.trim()) return chatbots;
|
|
113
|
-
const q = search.toLowerCase();
|
|
114
|
-
return chatbots.filter(bot => bot.botName?.toLowerCase().includes(q) || bot.description?.toLowerCase().includes(q) || bot.createdByName?.toLowerCase().includes(q));
|
|
115
|
-
};
|
|
116
|
-
const applyCommunityFilters = chatbots => {
|
|
117
|
-
let result = filterChatbots(chatbots);
|
|
118
|
-
if (filters.difficulty && filters.difficulty !== "all") {
|
|
119
|
-
result = result.filter(bot => bot.difficulty === filters.difficulty);
|
|
120
|
-
}
|
|
121
|
-
if (filters.sort === "newest") {
|
|
122
|
-
result = [...result].sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0));
|
|
123
|
-
} else if (filters.sort === "oldest") {
|
|
124
|
-
result = [...result].sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
|
|
125
|
-
} else {
|
|
126
|
-
result = [...result].sort((a, b) => (b.exerciseStartTotal || 0) - (a.exerciseStartTotal || 0));
|
|
127
|
-
}
|
|
128
|
-
return result;
|
|
129
|
-
};
|
|
130
|
-
const currentChatbots = effectiveTab === 0 ? filterChatbots(libraryChatbots) : applyCommunityFilters(communityChatbots);
|
|
93
|
+
const currentChatbots = applyFilters(effectiveTab === 0 ? libraryChatbots : communityChatbots, filters, "botName");
|
|
131
94
|
const isLoading = effectiveTab === 0 ? isLoadingLibrary : isLoadingCommunity;
|
|
132
|
-
const
|
|
95
|
+
const libraryLabel = t("add_own_exercise_message", {
|
|
133
96
|
exerciseName: t("chatbots").toLowerCase()
|
|
134
|
-
})
|
|
97
|
+
});
|
|
135
98
|
const addButtonLabel = selectedBotIds.length > 0 ? `${t("add_chatbots")} (${selectedBotIds.length})` : t("add_chatbots");
|
|
136
99
|
return /*#__PURE__*/_jsxs(Dialog, {
|
|
137
100
|
open: open,
|
|
@@ -190,24 +153,10 @@ export default function FindChatbotDialog({
|
|
|
190
153
|
disabled: isCommunityAccessRestricted
|
|
191
154
|
})]
|
|
192
155
|
})
|
|
193
|
-
}),
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
xs: 0,
|
|
198
|
-
md: 1.5
|
|
199
|
-
},
|
|
200
|
-
borderBottom: 1,
|
|
201
|
-
borderColor: "divider"
|
|
202
|
-
},
|
|
203
|
-
children: /*#__PURE__*/_jsx(SearchFilters, {
|
|
204
|
-
t: t,
|
|
205
|
-
difficulty: filters.difficulty,
|
|
206
|
-
handleFilterChange: handleFilterChange,
|
|
207
|
-
sort: filters.sort,
|
|
208
|
-
search: search,
|
|
209
|
-
setSearch: setSearch
|
|
210
|
-
})
|
|
156
|
+
}), /*#__PURE__*/_jsx(SearchFilters, {
|
|
157
|
+
t: t,
|
|
158
|
+
...filterProps,
|
|
159
|
+
dataCyPrefix: "find-chatbots"
|
|
211
160
|
}), /*#__PURE__*/_jsx(Box, {
|
|
212
161
|
ref: scrollRef,
|
|
213
162
|
sx: {
|
|
@@ -229,13 +178,13 @@ export default function FindChatbotDialog({
|
|
|
229
178
|
children: t("no_chatbots_found") || "No chatbots found"
|
|
230
179
|
})
|
|
231
180
|
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
232
|
-
children: [/*#__PURE__*/_jsx(Typography, {
|
|
181
|
+
children: [effectiveTab === 0 && /*#__PURE__*/_jsx(Typography, {
|
|
233
182
|
color: "text.secondary",
|
|
234
183
|
sx: {
|
|
235
184
|
display: "block",
|
|
236
185
|
mb: 2
|
|
237
186
|
},
|
|
238
|
-
children:
|
|
187
|
+
children: libraryLabel
|
|
239
188
|
}), /*#__PURE__*/_jsx(Bots, {
|
|
240
189
|
t: t,
|
|
241
190
|
bots: currentChatbots,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo, useRef
|
|
1
|
+
import { useState, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { useInView } from "react-intersection-observer";
|
|
3
3
|
import { courses as courseQuerys, ReactQuery } from "@nualang/nualang-api-and-queries/Queries";
|
|
4
4
|
import { getCourses } from "@nualang/nualang-api-and-queries/APIs/Courses";
|
|
@@ -7,7 +7,6 @@ import AppBar from "@mui/material/AppBar";
|
|
|
7
7
|
import Toolbar from "@mui/material/Toolbar";
|
|
8
8
|
import IconButton from "@mui/material/IconButton";
|
|
9
9
|
import Typography from "@mui/material/Typography";
|
|
10
|
-
import Slide from "@mui/material/Slide";
|
|
11
10
|
import Box from "@mui/material/Box";
|
|
12
11
|
import Tabs from "@mui/material/Tabs";
|
|
13
12
|
import Tab from "@mui/material/Tab";
|
|
@@ -26,6 +25,7 @@ import SortBySelector from "../../Forms/SortBySelector/SortBySelector";
|
|
|
26
25
|
import useDebounce from "../../hooks/useDebounce";
|
|
27
26
|
import useHorizontalScroll from "../../hooks/useHorizontalScroll";
|
|
28
27
|
import PlaceholderImages from "../../utils/placeholder-images/index";
|
|
28
|
+
import { DIFFICULTIES, SORT_BY_OPTIONS, Transition, sortItems } from "../SearchFilters/SearchFilters";
|
|
29
29
|
|
|
30
30
|
// The Community tab wraps into a grid, same as Topics.jsx's compact cards in
|
|
31
31
|
// FindTopicsDialog. My library keeps horizontal scrolling rows (below) so
|
|
@@ -46,29 +46,47 @@ const courseGridSx = {
|
|
|
46
46
|
const {
|
|
47
47
|
useInfiniteQuery
|
|
48
48
|
} = ReactQuery;
|
|
49
|
-
|
|
50
|
-
// Same CEFR codes FindTopicsDialog's community difficulty filter uses.
|
|
51
|
-
const DIFFICULTIES = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
|
52
49
|
const LIBRARY_PAGE_SIZE = 12;
|
|
53
50
|
const nextLibraryPageParam = lastPage => lastPage.LastEvaluatedKey?.page || lastPage.LastEvaluatedKey?.courseId ? lastPage.LastEvaluatedKey : undefined;
|
|
54
|
-
const SORT_BY_OPTIONS = {
|
|
55
|
-
popular: "popular",
|
|
56
|
-
newest: "newest",
|
|
57
|
-
oldest: "oldest"
|
|
58
|
-
};
|
|
59
51
|
const DEFAULT_FILTERS = {
|
|
60
52
|
learnLang: "all",
|
|
61
53
|
forLang: "all",
|
|
62
54
|
difficulty: "all",
|
|
63
55
|
sort: "popular"
|
|
64
56
|
};
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
const MY_LIBRARY_TAB = 0;
|
|
58
|
+
// My library lists the user's own courses, so newest first is more useful
|
|
59
|
+
// there than popularity. The Community tab keeps the "popular" default.
|
|
60
|
+
const MY_LIBRARY_DEFAULT_SORT = "newest";
|
|
61
|
+
|
|
62
|
+
// My library rows are fetched in full and filtered here, the same way the
|
|
63
|
+
// Courses screen does it, so search never goes through the global course search.
|
|
64
|
+
function filterLibraryCourses(courses, filters, search) {
|
|
65
|
+
const searchText = search.trim().toLowerCase();
|
|
66
|
+
const filtered = courses.filter(course => {
|
|
67
|
+
if (filters.learnLang !== "all" && course.learnLang !== filters.learnLang) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (filters.forLang !== "all" && course.forLang !== filters.forLang) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (filters.difficulty !== "all") {
|
|
74
|
+
const difficulties = Array.isArray(course.difficulty) ? course.difficulty : [course.difficulty];
|
|
75
|
+
if (!difficulties.includes(filters.difficulty)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (searchText) {
|
|
80
|
+
const nameMatch = (course.courseName || "").toLowerCase().includes(searchText);
|
|
81
|
+
const descriptionMatch = (course.description || "").toLowerCase().includes(searchText);
|
|
82
|
+
if (!nameMatch && !descriptionMatch) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
70
87
|
});
|
|
71
|
-
|
|
88
|
+
return sortItems(filtered, filters.sort);
|
|
89
|
+
}
|
|
72
90
|
function CourseFilters({
|
|
73
91
|
t,
|
|
74
92
|
search,
|
|
@@ -202,7 +220,7 @@ function CourseFilters({
|
|
|
202
220
|
children: t("all") || "All"
|
|
203
221
|
}), DIFFICULTIES.map(d => /*#__PURE__*/_jsx(MenuItem, {
|
|
204
222
|
value: d,
|
|
205
|
-
children: d
|
|
223
|
+
children: t(d) || d
|
|
206
224
|
}, d))]
|
|
207
225
|
}), /*#__PURE__*/_jsx(SortBySelector, {
|
|
208
226
|
id: "course-sort",
|
|
@@ -383,6 +401,7 @@ export default function FindCoursesDialog({
|
|
|
383
401
|
formatCourses,
|
|
384
402
|
groupCourseIds = [],
|
|
385
403
|
groupCoursesLimit = 100,
|
|
404
|
+
createdCoursesLimit = 1000,
|
|
386
405
|
alreadyAddedCourseIds = [],
|
|
387
406
|
isCommunityAccessRestricted = false,
|
|
388
407
|
learnLangOptions = [],
|
|
@@ -397,16 +416,19 @@ export default function FindCoursesDialog({
|
|
|
397
416
|
} = useInView({
|
|
398
417
|
threshold: 0
|
|
399
418
|
});
|
|
400
|
-
const [tab, setTab] = useState(
|
|
401
|
-
const effectiveTab = isCommunityAccessRestricted ?
|
|
419
|
+
const [tab, setTab] = useState(MY_LIBRARY_TAB);
|
|
420
|
+
const effectiveTab = isCommunityAccessRestricted ? MY_LIBRARY_TAB : tab;
|
|
402
421
|
const [selectedCourseIds, setSelectedCourseIds] = useState([]);
|
|
403
422
|
const [search, setSearch] = useState("");
|
|
404
|
-
const
|
|
423
|
+
const getDefaultFilters = tabIndex => ({
|
|
405
424
|
...DEFAULT_FILTERS,
|
|
406
425
|
learnLang: defaultLearnLang,
|
|
407
|
-
forLang: defaultForLang
|
|
408
|
-
|
|
409
|
-
|
|
426
|
+
forLang: defaultForLang,
|
|
427
|
+
...(tabIndex === MY_LIBRARY_TAB && {
|
|
428
|
+
sort: MY_LIBRARY_DEFAULT_SORT
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
const [filters, setFilters] = useState(() => getDefaultFilters(MY_LIBRARY_TAB));
|
|
410
432
|
// Every course object ever handed to this dialog, keyed by id. search/
|
|
411
433
|
// difficulty/sort are now applied upstream (the caller only fetches
|
|
412
434
|
// matching pages), so a course that's selected and then filtered out of
|
|
@@ -422,8 +444,8 @@ export default function FindCoursesDialog({
|
|
|
422
444
|
const reset = () => {
|
|
423
445
|
setSelectedCourseIds([]);
|
|
424
446
|
setSearch("");
|
|
425
|
-
setFilters(
|
|
426
|
-
setTab(
|
|
447
|
+
setFilters(getDefaultFilters(MY_LIBRARY_TAB));
|
|
448
|
+
setTab(MY_LIBRARY_TAB);
|
|
427
449
|
setSearchResetKey(key => key + 1);
|
|
428
450
|
setCourseCache(new Map());
|
|
429
451
|
};
|
|
@@ -447,7 +469,7 @@ export default function FindCoursesDialog({
|
|
|
447
469
|
if (isCommunityAccessRestricted) return;
|
|
448
470
|
setTab(v);
|
|
449
471
|
setSearch("");
|
|
450
|
-
setFilters(
|
|
472
|
+
setFilters(getDefaultFilters(v));
|
|
451
473
|
setSearchResetKey(key => key + 1);
|
|
452
474
|
};
|
|
453
475
|
const handleFilterChange = event => {
|
|
@@ -465,28 +487,19 @@ export default function FindCoursesDialog({
|
|
|
465
487
|
// every page load - so the carousels below know when a reorder is a real
|
|
466
488
|
// reset versus just more of the same sort settling in as data grows.
|
|
467
489
|
const librarySortResetKey = `${search}|${filters.learnLang}|${filters.forLang}|${filters.difficulty}|${filters.sort}`;
|
|
490
|
+
|
|
491
|
+
// Search/filters/sort are applied client-side in libraryCreatedCourses, so
|
|
492
|
+
// changing them doesn't refetch
|
|
468
493
|
const teachCoursesQuery = useInfiniteQuery({
|
|
469
|
-
queryKey: ["findCoursesDialog", "created", username
|
|
494
|
+
queryKey: ["findCoursesDialog", "created", username],
|
|
470
495
|
queryFn: async ({
|
|
471
496
|
pageParam = {}
|
|
472
497
|
}) => {
|
|
473
498
|
const response = await getCourses({
|
|
474
499
|
createdBy: username,
|
|
475
500
|
verbose: false,
|
|
476
|
-
limit
|
|
477
|
-
|
|
478
|
-
...(filters.learnLang !== "all" && {
|
|
479
|
-
learnLang: filters.learnLang
|
|
480
|
-
}),
|
|
481
|
-
...(filters.forLang !== "all" && {
|
|
482
|
-
forLang: filters.forLang
|
|
483
|
-
}),
|
|
484
|
-
...(filters.difficulty !== "all" && {
|
|
485
|
-
difficulty: filters.difficulty
|
|
486
|
-
}),
|
|
487
|
-
...(search && {
|
|
488
|
-
search
|
|
489
|
-
}),
|
|
501
|
+
// Get all using high limit, same as the Courses screen
|
|
502
|
+
limit: createdCoursesLimit,
|
|
490
503
|
page: pageParam.page || 1,
|
|
491
504
|
lastEvaluatedCourseId: pageParam.courseId || "",
|
|
492
505
|
lastEvaluatedKey: pageParam.courseId || "",
|
|
@@ -503,6 +516,20 @@ export default function FindCoursesDialog({
|
|
|
503
516
|
getNextPageParam: nextLibraryPageParam,
|
|
504
517
|
enabled: !!username && open
|
|
505
518
|
});
|
|
519
|
+
const {
|
|
520
|
+
hasNextPage: hasMoreCreated,
|
|
521
|
+
isFetchingNextPage: isFetchingMoreCreated,
|
|
522
|
+
isFetchNextPageError: isCreatedNextPageError
|
|
523
|
+
} = teachCoursesQuery;
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if (!open || !username || !hasMoreCreated || isFetchingMoreCreated || isCreatedNextPageError) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
teachCoursesQuery.fetchNextPage({
|
|
529
|
+
cancelRefetch: false
|
|
530
|
+
});
|
|
531
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
532
|
+
}, [open, username, hasMoreCreated, isFetchingMoreCreated, isCreatedNextPageError]);
|
|
506
533
|
const learnCoursesQuery = useInfiniteQuery({
|
|
507
534
|
queryKey: ["findCoursesDialog", "joined", username],
|
|
508
535
|
queryFn: async ({
|
|
@@ -545,32 +572,9 @@ export default function FindCoursesDialog({
|
|
|
545
572
|
const groupCourses = groupCoursesQuery.isSuccess && Array.isArray(groupCoursesQuery.data) ? groupCoursesQuery.data : [];
|
|
546
573
|
const combined = [...learnCourses, ...groupCourses];
|
|
547
574
|
const deduped = combined.filter((course, index) => combined.findIndex(c => c.courseId === course.courseId) === index);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (filters.forLang !== "all" && course.forLang !== filters.forLang) return false;
|
|
552
|
-
if (filters.difficulty !== "all") {
|
|
553
|
-
const difficulties = Array.isArray(course.difficulty) ? course.difficulty : [course.difficulty];
|
|
554
|
-
if (!difficulties.includes(filters.difficulty)) return false;
|
|
555
|
-
}
|
|
556
|
-
if (searchText) {
|
|
557
|
-
const nameMatch = (course.courseName || "").toLowerCase().includes(searchText);
|
|
558
|
-
const descriptionMatch = (course.description || "").toLowerCase().includes(searchText);
|
|
559
|
-
if (!nameMatch && !descriptionMatch) return false;
|
|
560
|
-
}
|
|
561
|
-
return true;
|
|
562
|
-
});
|
|
563
|
-
const sorted = [...filtered];
|
|
564
|
-
if (filters.sort === "newest") {
|
|
565
|
-
sorted.sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0));
|
|
566
|
-
} else if (filters.sort === "oldest") {
|
|
567
|
-
sorted.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
|
|
568
|
-
} else {
|
|
569
|
-
sorted.sort((a, b) => (b.exerciseStartTotal || 0) - (a.exerciseStartTotal || 0));
|
|
570
|
-
}
|
|
571
|
-
return sorted;
|
|
572
|
-
}, [learnCoursesQuery.data, groupCoursesQuery.data, groupCoursesQuery.isSuccess, filters.learnLang, filters.forLang, filters.difficulty, search, filters.sort]);
|
|
573
|
-
const libraryCreatedCourses = useMemo(() => (teachCoursesQuery.data?.pages || []).flatMap(page => page.Items || []), [teachCoursesQuery.data]);
|
|
575
|
+
return filterLibraryCourses(deduped, filters, search);
|
|
576
|
+
}, [learnCoursesQuery.data, groupCoursesQuery.data, groupCoursesQuery.isSuccess, filters, search]);
|
|
577
|
+
const libraryCreatedCourses = useMemo(() => filterLibraryCourses((teachCoursesQuery.data?.pages || []).flatMap(page => page.Items || []), filters, search), [teachCoursesQuery.data, filters, search]);
|
|
574
578
|
const communityCoursesQuery = courseQuerys.useCommunityCourses(async queryFilters => {
|
|
575
579
|
const response = await getCourses(queryFilters);
|
|
576
580
|
const formattedCourses = await formatCourses(response.Items, authData);
|
|
@@ -807,18 +811,11 @@ export default function FindCoursesDialog({
|
|
|
807
811
|
isLoadingMore: learnCoursesQuery.isFetchingNextPage,
|
|
808
812
|
resetKey: librarySortResetKey
|
|
809
813
|
})]
|
|
810
|
-
}) : isLoadingCommunity ? /*#__PURE__*/
|
|
814
|
+
}) : isLoadingCommunity ? /*#__PURE__*/_jsx(Box, {
|
|
811
815
|
sx: {
|
|
812
816
|
p: 3
|
|
813
817
|
},
|
|
814
|
-
children:
|
|
815
|
-
variant: "text",
|
|
816
|
-
width: 220,
|
|
817
|
-
height: 28,
|
|
818
|
-
sx: {
|
|
819
|
-
mb: 2
|
|
820
|
-
}
|
|
821
|
-
}), /*#__PURE__*/_jsx(Box, {
|
|
818
|
+
children: /*#__PURE__*/_jsx(Box, {
|
|
822
819
|
sx: courseGridSx,
|
|
823
820
|
children: Array.from({
|
|
824
821
|
length: 10
|
|
@@ -826,7 +823,7 @@ export default function FindCoursesDialog({
|
|
|
826
823
|
t: t,
|
|
827
824
|
compact: true
|
|
828
825
|
}, i))
|
|
829
|
-
})
|
|
826
|
+
})
|
|
830
827
|
}) : isCommunityEmpty ? /*#__PURE__*/_jsx(Box, {
|
|
831
828
|
display: "flex",
|
|
832
829
|
justifyContent: "center",
|
|
@@ -839,14 +836,7 @@ export default function FindCoursesDialog({
|
|
|
839
836
|
sx: {
|
|
840
837
|
p: 3
|
|
841
838
|
},
|
|
842
|
-
children: [/*#__PURE__*/_jsx(Typography, {
|
|
843
|
-
variant: "body2",
|
|
844
|
-
color: "text.secondary",
|
|
845
|
-
sx: {
|
|
846
|
-
mb: 2
|
|
847
|
-
},
|
|
848
|
-
children: t("courses_shared_by_other_teachers") || "Courses shared by other teachers"
|
|
849
|
-
}), communityCourses.length === 0 ? /*#__PURE__*/_jsx(Typography, {
|
|
839
|
+
children: [communityCourses.length === 0 ? /*#__PURE__*/_jsx(Typography, {
|
|
850
840
|
variant: "body2",
|
|
851
841
|
color: "text.secondary",
|
|
852
842
|
children: t("no_community_courses_match") || "No courses match these filters"
|