@selfcommunity/react-ui 0.10.2-courses.172 → 0.10.2-courses.173
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/lib/cjs/components/CourseDashboard/Student.js +10 -3
- package/lib/cjs/components/CourseParticipantsButton/CourseParticipantsButton.js +9 -7
- package/lib/cjs/components/EditCourse/Users.js +5 -2
- package/lib/cjs/shared/AccordionLessons/AccordionLessons.d.ts +2 -1
- package/lib/cjs/shared/AccordionLessons/AccordionLessons.js +16 -3
- package/lib/cjs/shared/AddUsersButton/AddUsersButton.d.ts +0 -5
- package/lib/cjs/shared/AddUsersButton/AddUsersButton.js +6 -7
- package/lib/cjs/shared/CourseUsersTable/SeeProgressButton.js +4 -1
- package/lib/esm/components/CourseDashboard/Student.js +11 -4
- package/lib/esm/components/CourseParticipantsButton/CourseParticipantsButton.js +9 -7
- package/lib/esm/components/EditCourse/Users.js +5 -2
- package/lib/esm/shared/AccordionLessons/AccordionLessons.d.ts +2 -1
- package/lib/esm/shared/AccordionLessons/AccordionLessons.js +18 -5
- package/lib/esm/shared/AddUsersButton/AddUsersButton.d.ts +0 -5
- package/lib/esm/shared/AddUsersButton/AddUsersButton.js +6 -7
- package/lib/esm/shared/CourseUsersTable/SeeProgressButton.js +4 -1
- package/lib/umd/react-ui.js +1 -1
- package/package.json +8 -8
|
@@ -52,6 +52,7 @@ const classes = {
|
|
|
52
52
|
contrastBgColor: `${constants_1.PREFIX}-contrast-bg-color`
|
|
53
53
|
};
|
|
54
54
|
function getUrlNextLesson(course) {
|
|
55
|
+
var _a;
|
|
55
56
|
const data = {
|
|
56
57
|
id: course.id,
|
|
57
58
|
slug: course.slug
|
|
@@ -63,7 +64,7 @@ function getUrlNextLesson(course) {
|
|
|
63
64
|
});
|
|
64
65
|
return data;
|
|
65
66
|
}
|
|
66
|
-
course.sections.some((section) => {
|
|
67
|
+
(_a = course.sections) === null || _a === void 0 ? void 0 : _a.some((section) => {
|
|
67
68
|
const isNextLessonInThisSection = section.num_lessons_completed < section.num_lessons;
|
|
68
69
|
if (isNextLessonInThisSection) {
|
|
69
70
|
Object.assign(data, {
|
|
@@ -76,7 +77,8 @@ function getUrlNextLesson(course) {
|
|
|
76
77
|
return data;
|
|
77
78
|
}
|
|
78
79
|
function getIsNextLessonLocked(course) {
|
|
79
|
-
|
|
80
|
+
var _a;
|
|
81
|
+
return (_a = course.sections) === null || _a === void 0 ? void 0 : _a.every((section) => {
|
|
80
82
|
var _a, _b;
|
|
81
83
|
return (section.num_lessons_completed < section.num_lessons &&
|
|
82
84
|
((_b = (_a = section.lessons) === null || _a === void 0 ? void 0 : _a.find((lesson) => lesson.completion_status === types_1.SCCourseLessonCompletionStatusType.UNCOMPLETED)) === null || _b === void 0 ? void 0 : _b.locked));
|
|
@@ -99,6 +101,8 @@ function Student(inProps) {
|
|
|
99
101
|
const [loadingRequest, setLoadingRequest] = (0, react_1.useState)(false);
|
|
100
102
|
// CONTEXTS
|
|
101
103
|
const scRoutingContext = (0, react_core_1.useSCRouting)();
|
|
104
|
+
const scContext = (0, react_core_1.useSCContext)();
|
|
105
|
+
const scUserContext = (0, react_core_1.useSCUser)();
|
|
102
106
|
// HOOKS
|
|
103
107
|
const { scCourse, setSCCourse } = (0, react_core_1.useSCFetchCourse)({ id: courseId, course });
|
|
104
108
|
const intl = (0, react_intl_1.useIntl)();
|
|
@@ -140,6 +144,9 @@ function Student(inProps) {
|
|
|
140
144
|
utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
|
|
141
145
|
});
|
|
142
146
|
}, [scCourse, sentRequest, setLoadingRequest]);
|
|
147
|
+
const handleAnonymousAction = (0, react_1.useCallback)(() => {
|
|
148
|
+
scContext.settings.handleAnonymousAction();
|
|
149
|
+
}, [scContext.settings.handleAnonymousAction]);
|
|
143
150
|
// MEMOS
|
|
144
151
|
const actionButton = (0, react_1.useMemo)(() => {
|
|
145
152
|
if (!scCourse) {
|
|
@@ -153,7 +160,7 @@ function Student(inProps) {
|
|
|
153
160
|
? BUTTON_MESSAGES.start
|
|
154
161
|
: scCourse.user_completion_rate === 100
|
|
155
162
|
? BUTTON_MESSAGES.review
|
|
156
|
-
: BUTTON_MESSAGES.continue, to: scCourse.join_status !== null ? scRoutingContext.url(react_core_1.SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlNextLesson(scCourse)) : undefined, disabled: scCourse.join_status !== null ? getIsNextLessonLocked(scCourse) : undefined, color: scCourse.user_completion_rate === 100 ? 'inherit' : undefined, variant: scCourse.user_completion_rate === 100 ? 'outlined' : undefined, loading: scCourse.join_status === null ? loadingRequest : undefined, onClick: scCourse.join_status === null ? handleRequest : undefined })), scCourse.privacy === types_1.SCCoursePrivacyType.PRIVATE &&
|
|
163
|
+
: BUTTON_MESSAGES.continue, to: scCourse.join_status !== null ? scRoutingContext.url(react_core_1.SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlNextLesson(scCourse)) : undefined, disabled: scCourse.join_status !== null ? getIsNextLessonLocked(scCourse) : undefined, color: scCourse.user_completion_rate === 100 ? 'inherit' : undefined, variant: scCourse.user_completion_rate === 100 ? 'outlined' : undefined, loading: scCourse.join_status === null ? loadingRequest : undefined, onClick: !scUserContext.user ? handleAnonymousAction : scCourse.join_status === null ? handleRequest : undefined })), scCourse.privacy === types_1.SCCoursePrivacyType.PRIVATE &&
|
|
157
164
|
(scCourse.join_status === null || scCourse.join_status === types_1.SCCourseJoinStatusType.REQUESTED) && ((0, jsx_runtime_1.jsx)(ActionButton_1.default, { labelId: sentRequest ? BUTTON_MESSAGES.cancel : BUTTON_MESSAGES.request, color: "inherit", variant: "outlined", loading: loadingRequest, onClick: handleRequest }))] })));
|
|
158
165
|
}, [scCourse, sentRequest, loadingRequest, handleRequest]);
|
|
159
166
|
if (!scCourse) {
|
|
@@ -71,6 +71,7 @@ function CourseParticipantsButton(inProps) {
|
|
|
71
71
|
const { className, courseId, course, hideCaption = false, DialogProps = {} } = props, rest = tslib_1.__rest(props, ["className", "courseId", "course", "hideCaption", "DialogProps"]);
|
|
72
72
|
// STATE
|
|
73
73
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
74
|
+
const [count, setCount] = (0, react_1.useState)(0);
|
|
74
75
|
const [next, setNext] = (0, react_1.useState)(null);
|
|
75
76
|
const [offset, setOffset] = (0, react_1.useState)(null);
|
|
76
77
|
const [enrolled, setEnrolled] = (0, react_1.useState)([]);
|
|
@@ -87,17 +88,18 @@ function CourseParticipantsButton(inProps) {
|
|
|
87
88
|
if (!scCourse) {
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
|
-
if (!
|
|
91
|
+
if (!count && participantsAvailable) {
|
|
91
92
|
api_services_1.CourseService.getCourseJoinedUsers(scCourse.id, { limit: 3 }).then((res) => {
|
|
92
93
|
setEnrolled([...res.results]);
|
|
93
94
|
setOffset(4);
|
|
95
|
+
setCount(res.count);
|
|
94
96
|
setLoading(false);
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
else {
|
|
98
100
|
setOffset(0);
|
|
99
101
|
}
|
|
100
|
-
}, [scCourse, participantsAvailable,
|
|
102
|
+
}, [scCourse, participantsAvailable, count, setEnrolled, setOffset, setLoading]);
|
|
101
103
|
(0, react_1.useEffect)(() => {
|
|
102
104
|
if (open && offset !== null) {
|
|
103
105
|
setLoading(true);
|
|
@@ -108,7 +110,7 @@ function CourseParticipantsButton(inProps) {
|
|
|
108
110
|
setOffset(null);
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
|
-
}, [open, enrolled, offset]);
|
|
113
|
+
}, [open, enrolled, offset, setLoading, setNext]);
|
|
112
114
|
/**
|
|
113
115
|
* Memoized fetchEnrolledUsers
|
|
114
116
|
*/
|
|
@@ -127,8 +129,8 @@ function CourseParticipantsButton(inProps) {
|
|
|
127
129
|
})
|
|
128
130
|
.catch((error) => utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error))
|
|
129
131
|
.then(() => setLoading(false));
|
|
130
|
-
}, [enrolled,
|
|
131
|
-
const renderSurplus = (0, react_1.useCallback)(() => (0, buttonCounters_1.numberFormatter)(
|
|
132
|
+
}, [enrolled, next, setLoading]);
|
|
133
|
+
const renderSurplus = (0, react_1.useCallback)(() => (0, buttonCounters_1.numberFormatter)(count), [count]);
|
|
132
134
|
/**
|
|
133
135
|
* Opens participants dialog
|
|
134
136
|
*/
|
|
@@ -141,8 +143,8 @@ function CourseParticipantsButton(inProps) {
|
|
|
141
143
|
if (!participantsAvailable) {
|
|
142
144
|
return (0, jsx_runtime_1.jsx)(HiddenPlaceholder_1.default, {});
|
|
143
145
|
}
|
|
144
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className), onClick: handleToggleDialogOpen, disabled: loading || !scCourse ||
|
|
146
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className), onClick: handleToggleDialogOpen, disabled: loading || !scCourse || count === 0,
|
|
145
147
|
// @ts-expect-error this is needed to use enrolled into SCCourseParticipantsButton
|
|
146
|
-
enrolled: enrolled }, rest, { children: [!hideCaption && ((0, jsx_runtime_1.jsxs)(material_1.Typography, Object.assign({ className: classes.participants }, { children: [(0, jsx_runtime_1.jsx)(material_1.Icon, { children: "people_alt" }), (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.participants", id: "ui.courseParticipantsButton.participants", values: { total:
|
|
148
|
+
enrolled: enrolled }, rest, { children: [!hideCaption && ((0, jsx_runtime_1.jsxs)(material_1.Typography, Object.assign({ className: classes.participants }, { children: [(0, jsx_runtime_1.jsx)(material_1.Icon, { children: "people_alt" }), (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.participants", id: "ui.courseParticipantsButton.participants", values: { total: count } })] }))), !count && (loading || !scCourse) ? ((0, jsx_runtime_1.jsx)(AvatarGroupSkeleton_1.default, Object.assign({}, rest, (!participantsAvailable && { skeletonsAnimation: false }), { count: 4 }))) : ((0, jsx_runtime_1.jsx)(material_1.AvatarGroup, Object.assign({ total: count, renderSurplus: renderSurplus }, { children: enrolled.map((c) => ((0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: c.username, src: c.avatar }, c.id))) })))] })), open && ((0, jsx_runtime_1.jsx)(DialogRoot, Object.assign({ className: classes.dialogRoot, title: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.dialogTitle", id: "ui.courseParticipantsButton.dialogTitle", values: { total: count } }), onClose: handleToggleDialogOpen, open: true }, DialogProps, { children: (0, jsx_runtime_1.jsx)(InfiniteScroll_1.default, Object.assign({ dataLength: count, next: fetchEnrolledUsers, hasMoreNext: next !== null || loading, loaderNext: (0, jsx_runtime_1.jsx)(User_1.UserSkeleton, { elevation: 0 }), className: classes.infiniteScroll, endMessage: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ className: classes.endMessage }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseParticipantsButton.noOtherParticipants", defaultMessage: "ui.courseParticipantsButton.noOtherParticipants" }) })) }, { children: (0, jsx_runtime_1.jsx)(material_1.List, { children: enrolled.map((e) => ((0, jsx_runtime_1.jsx)(material_1.ListItem, { children: (0, jsx_runtime_1.jsx)(User_1.default, { elevation: 0, user: e }) }, e.id))) }) })) })))] }));
|
|
147
149
|
}
|
|
148
150
|
exports.default = CourseParticipantsButton;
|
|
@@ -46,7 +46,6 @@ function Users(props) {
|
|
|
46
46
|
} } = props;
|
|
47
47
|
// STATES
|
|
48
48
|
const [state, dispatch] = (0, react_1.useReducer)(widget_1.dataWidgetReducer, {
|
|
49
|
-
isLoadingPrevious: false,
|
|
50
49
|
isLoadingNext: false,
|
|
51
50
|
next: null,
|
|
52
51
|
cacheKey: react_core_1.SCCache.getWidgetStateCacheKey(react_core_1.SCCache.USER_PARTECIPANTS_COURSES_STATE_CACHE_PREFIX_KEY, course.id),
|
|
@@ -103,6 +102,10 @@ function Users(props) {
|
|
|
103
102
|
type: widget_1.actionWidgetTypes.LOAD_PREVIOUS_SUCCESS,
|
|
104
103
|
payload: { count: state.count + newUsers.length, results: newUsers, initialized: true }
|
|
105
104
|
});
|
|
105
|
+
enqueueSnackbar((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.editCourse.tab.users.table.snackbar.success", defaultMessage: "ui.editCourse.tab.users.table.snackbar.success" }), {
|
|
106
|
+
variant: 'success',
|
|
107
|
+
autoHideDuration: 3000
|
|
108
|
+
});
|
|
106
109
|
})
|
|
107
110
|
.catch((error) => {
|
|
108
111
|
dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
|
|
@@ -116,6 +119,6 @@ function Users(props) {
|
|
|
116
119
|
return ((0, jsx_runtime_1.jsxs)(material_1.Box, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h6", className: classes.contrastColor }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.editCourse.tab.users.title", defaultMessage: "ui.editCourse.tab.users.title", values: { usersNumber: state.results.length } }) })), (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.usersStatusWrapper }, { children: [(0, jsx_runtime_1.jsx)(Status_1.default, { course: course }), (0, jsx_runtime_1.jsx)(AddUsersButton_1.default, { label: "ui.editCourse.tab.users.addUsersButton.label", endpoint: {
|
|
117
120
|
url: () => api_services_1.Endpoints.GetCourseSuggestedUsers.url({ id: course.id }),
|
|
118
121
|
method: api_services_1.Endpoints.GetCourseSuggestedUsers.method
|
|
119
|
-
}, onConfirm: handleConfirm,
|
|
122
|
+
}, onConfirm: handleConfirm, className: classes.contrastBgColor })] })), (0, jsx_runtime_1.jsx)(CourseUsersTable_1.default, { course: course, state: state, dispatch: dispatch, headerCells: headerCells, mode: "edit", emptyStatusTitle: "ui.courseUsersTable.empty.users.title", emptyStatusDescription: "ui.courseUsersTable.empty.users.description" })] }));
|
|
120
123
|
}
|
|
121
124
|
exports.default = (0, react_1.memo)(Users);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
|
-
import { SCCourseType } from '@selfcommunity/types';
|
|
2
|
+
import { SCCourseJoinStatusType, SCCourseType } from '@selfcommunity/types';
|
|
3
3
|
export interface AccordionLessonsProps {
|
|
4
4
|
course: SCCourseType | null;
|
|
5
|
+
viewerJoinStatus?: SCCourseJoinStatusType;
|
|
5
6
|
className?: HTMLAttributes<HTMLDivElement>['className'];
|
|
6
7
|
}
|
|
7
8
|
export default function AccordionLessons(inProps: AccordionLessonsProps): JSX.Element;
|
|
@@ -7,16 +7,27 @@ const react_intl_1 = require("react-intl");
|
|
|
7
7
|
const classnames_1 = tslib_1.__importDefault(require("classnames"));
|
|
8
8
|
const react_1 = require("react");
|
|
9
9
|
const types_1 = require("@selfcommunity/types");
|
|
10
|
+
const react_core_1 = require("@selfcommunity/react-core");
|
|
10
11
|
const constants_1 = require("./constants");
|
|
11
12
|
const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
|
|
13
|
+
const react_core_2 = require("@selfcommunity/react-core");
|
|
12
14
|
const classes = {
|
|
13
15
|
root: `${constants_1.PREFIX}-root`,
|
|
14
16
|
empty: `${constants_1.PREFIX}-empty`,
|
|
15
17
|
accordion: `${constants_1.PREFIX}-accordion`,
|
|
16
18
|
summary: `${constants_1.PREFIX}-summary`,
|
|
17
19
|
details: `${constants_1.PREFIX}-details`,
|
|
18
|
-
circle: `${constants_1.PREFIX}-circle
|
|
20
|
+
circle: `${constants_1.PREFIX}-circle`,
|
|
21
|
+
link: `${constants_1.PREFIX}-link`
|
|
19
22
|
};
|
|
23
|
+
function getUrlLesson(course, section, lesson) {
|
|
24
|
+
return {
|
|
25
|
+
id: course.id,
|
|
26
|
+
slug: course.slug,
|
|
27
|
+
section_id: section.id,
|
|
28
|
+
lesson_id: lesson.id
|
|
29
|
+
};
|
|
30
|
+
}
|
|
20
31
|
const Root = (0, material_1.styled)(material_1.Box, {
|
|
21
32
|
name: constants_1.PREFIX,
|
|
22
33
|
slot: 'Root',
|
|
@@ -29,12 +40,14 @@ function AccordionLessons(inProps) {
|
|
|
29
40
|
props: inProps,
|
|
30
41
|
name: constants_1.PREFIX
|
|
31
42
|
});
|
|
32
|
-
const { course, className } = props;
|
|
43
|
+
const { course, viewerJoinStatus, className } = props;
|
|
33
44
|
//STATES
|
|
34
45
|
const [expanded, setExpanded] = (0, react_1.useState)(false);
|
|
35
46
|
// HOOKS
|
|
36
47
|
const theme = (0, material_1.useTheme)();
|
|
37
48
|
const isMobile = (0, material_1.useMediaQuery)(theme.breakpoints.down('sm'));
|
|
49
|
+
// CONTEXTS
|
|
50
|
+
const scRoutingContext = (0, react_core_1.useSCRouting)();
|
|
38
51
|
// HANDLERS
|
|
39
52
|
const handleChange = (0, react_1.useCallback)((panel) => (_, newExpanded) => {
|
|
40
53
|
setExpanded(newExpanded ? panel : false);
|
|
@@ -44,6 +57,6 @@ function AccordionLessons(inProps) {
|
|
|
44
57
|
}
|
|
45
58
|
return ((0, jsx_runtime_1.jsx)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, { children: ((_a = course.sections) === null || _a === void 0 ? void 0 : _a.length) > 0 ? (course.sections.map((section) => ((0, jsx_runtime_1.jsxs)(material_1.Accordion, Object.assign({ className: classes.accordion, expanded: expanded === section.id, onChange: handleChange(section.id), disableGutters: true, elevation: 0, square: true }, { children: [(0, jsx_runtime_1.jsxs)(material_1.AccordionSummary, Object.assign({ className: classes.summary, expandIcon: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "expand_less" }) }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ component: "span", variant: "body1" }, { children: section.name })), !isMobile && ((0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ component: "span", variant: "body1" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.course.table.lessons.title", defaultMessage: "ui.course.table.lessons.title", values: {
|
|
46
59
|
lessonsNumber: section.lessons.length
|
|
47
|
-
} }) })))] })), section.lessons.map((lesson) => ((0, jsx_runtime_1.jsxs)(material_1.AccordionDetails, Object.assign({ className: classes.details }, { children: [lesson.completion_status === types_1.SCCourseLessonCompletionStatusType.COMPLETED ? ((0, jsx_runtime_1.jsx)(material_1.Icon, Object.assign({ fontSize: "small", color: "primary" }, { children: "circle_checked" }))) : lesson.locked ? ((0, jsx_runtime_1.jsx)(material_1.Icon, { children: "private" })) : ((0, jsx_runtime_1.jsx)(material_1.Box, { className: classes.circle })), (0, jsx_runtime_1.jsx)(material_1.Typography, { children: lesson.name })] }), lesson.id)))] }), section.id)))) : ((0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", className: classes.empty }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.course.accordionLessons.empty", defaultMessage: "ui.course.accordionLessons.empty" }) }))) })));
|
|
60
|
+
} }) })))] })), section.lessons.map((lesson) => ((0, jsx_runtime_1.jsxs)(material_1.AccordionDetails, Object.assign({ className: classes.details }, { children: [lesson.completion_status === types_1.SCCourseLessonCompletionStatusType.COMPLETED ? ((0, jsx_runtime_1.jsx)(material_1.Icon, Object.assign({ fontSize: "small", color: "primary" }, { children: "circle_checked" }))) : lesson.locked ? ((0, jsx_runtime_1.jsx)(material_1.Icon, { children: "private" })) : ((0, jsx_runtime_1.jsx)(material_1.Box, { className: classes.circle })), course.join_status === null || viewerJoinStatus === types_1.SCCourseJoinStatusType.CREATOR || lesson.locked ? ((0, jsx_runtime_1.jsx)(material_1.Typography, { children: lesson.name })) : ((0, jsx_runtime_1.jsx)(material_1.Button, Object.assign({ component: react_core_2.Link, to: scRoutingContext.url(react_core_1.SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlLesson(course, section, lesson)), variant: "text", color: "inherit", className: classes.link }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, { children: lesson.name }) })))] }), lesson.id)))] }), section.id)))) : ((0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", className: classes.empty }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.course.accordionLessons.empty", defaultMessage: "ui.course.accordionLessons.empty" }) }))) })));
|
|
48
61
|
}
|
|
49
62
|
exports.default = AccordionLessons;
|
|
@@ -4,11 +4,6 @@ import { SCUserType } from '@selfcommunity/types';
|
|
|
4
4
|
import { EndpointType } from '@selfcommunity/api-services';
|
|
5
5
|
export interface AddUsersButtonProps extends ButtonProps {
|
|
6
6
|
label: string;
|
|
7
|
-
/**
|
|
8
|
-
* Handles component update
|
|
9
|
-
* @default false
|
|
10
|
-
*/
|
|
11
|
-
isUpdating?: boolean;
|
|
12
7
|
/**
|
|
13
8
|
* Event API Endpoint
|
|
14
9
|
* @default Endpoints.GetCourseSuggestedUsers
|
|
@@ -43,7 +43,7 @@ function AddUsersButton(inProps) {
|
|
|
43
43
|
props: inProps,
|
|
44
44
|
name: PREFIX
|
|
45
45
|
});
|
|
46
|
-
const { label, variant = 'outlined', color = 'inherit', size = 'small',
|
|
46
|
+
const { label, variant = 'outlined', color = 'inherit', size = 'small', endpoint = api_services_1.Endpoints.GetCourseSuggestedUsers, endpointQueryParams = { limit: Pagination_1.DEFAULT_PAGINATION_LIMIT, offset: Pagination_1.DEFAULT_PAGINATION_OFFSET, search: '' }, onConfirm, className } = props, rest = tslib_1.__rest(props, ["label", "variant", "color", "size", "endpoint", "endpointQueryParams", "onConfirm", "className"]);
|
|
47
47
|
// STATES
|
|
48
48
|
const [openDialog, setOpenDialog] = (0, react_1.useState)(false);
|
|
49
49
|
const [invited, setInvited] = (0, react_1.useState)([]);
|
|
@@ -86,17 +86,16 @@ function AddUsersButton(inProps) {
|
|
|
86
86
|
* @param reason
|
|
87
87
|
*/
|
|
88
88
|
const handleToggleDialogOpen = (0, react_1.useCallback)(() => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
}, [isUpdating, setOpenDialog]);
|
|
89
|
+
setOpenDialog((prev) => !prev);
|
|
90
|
+
}, [setOpenDialog]);
|
|
93
91
|
/**
|
|
94
92
|
* Handles action confirm
|
|
95
93
|
*/
|
|
96
94
|
const handleConfirm = (0, react_1.useCallback)(() => {
|
|
97
95
|
onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(invited);
|
|
98
96
|
setInvited([]);
|
|
99
|
-
|
|
97
|
+
handleToggleDialogOpen();
|
|
98
|
+
}, [invited, onConfirm, handleToggleDialogOpen]);
|
|
100
99
|
// HANDLERS AUTOCOMPLETE
|
|
101
100
|
const handleInputChange = (0, react_1.useCallback)((_, newValue, reason) => {
|
|
102
101
|
switch (reason) {
|
|
@@ -129,7 +128,7 @@ function AddUsersButton(inProps) {
|
|
|
129
128
|
const handleDelete = (0, react_1.useCallback)((userToDelete) => {
|
|
130
129
|
setInvited((prev) => prev.filter((user) => user.id !== userToDelete.id));
|
|
131
130
|
}, [setInvited]);
|
|
132
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Root, Object.assign({ onClick: handleToggleDialogOpen, variant: variant, color: color, size: size, className: (0, classnames_1.default)(classes.root, className) }, rest, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: label, defaultMessage: label }) })), openDialog && ((0, jsx_runtime_1.jsx)(DialogRoot, Object.assign({ DialogContentProps: { dividers: false }, open: true, onClose: handleToggleDialogOpen, title: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h5" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.addUserButton.dialog.title", defaultMessage: "ui.addUserButton.dialog.title" }) })), actions: (0, jsx_runtime_1.jsx)(lab_1.LoadingButton, Object.assign({ onClick: handleConfirm, size: "medium", variant: "contained", autoFocus: true, disabled: !invited.length
|
|
131
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Root, Object.assign({ onClick: handleToggleDialogOpen, variant: variant, color: color, size: size, className: (0, classnames_1.default)(classes.root, className) }, rest, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: label, defaultMessage: label }) })), openDialog && ((0, jsx_runtime_1.jsx)(DialogRoot, Object.assign({ DialogContentProps: { dividers: false }, open: true, onClose: handleToggleDialogOpen, title: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h5" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.addUserButton.dialog.title", defaultMessage: "ui.addUserButton.dialog.title" }) })), actions: (0, jsx_runtime_1.jsx)(lab_1.LoadingButton, Object.assign({ onClick: handleConfirm, size: "medium", variant: "contained", autoFocus: true, disabled: !invited.length }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.addUserButton.dialog.confirm", defaultMessage: "ui.addUserButton.dialog.confirm" }) })) })), className: classes.dialogRoot }, { children: (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.dialogAutocompleteWrapper }, { children: [(0, jsx_runtime_1.jsx)(material_1.Autocomplete, { loading: loading, size: "small", multiple: true, options: suggested, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, filterOptions: filterOptions, value: invited, getOptionLabel: (option) => (option === null || option === void 0 ? void 0 : option.username) || '...', isOptionEqualToValue: (option, value) => (option === null || option === void 0 ? void 0 : option.id) === value.id, loadingText: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.addUserButton.autocomplete.loading", defaultMessage: "ui.addUserButton.autocomplete.loading" }), noOptionsText: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.addUserButton.autocomplete.noResults", defaultMessage: "ui.addUserButton.autocomplete.noResults" }), renderTags: () => null, popupIcon: null, disableClearable: true, renderOption: (props, option) => ((0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ component: "li", flexDirection: "row", gap: "5px" }, props, { children: [(0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: option.username, src: option.avatar }), (0, jsx_runtime_1.jsx)(material_1.Typography, { children: option.username })] }))), renderInput: (params) => ((0, jsx_runtime_1.jsx)(material_1.TextField, Object.assign({}, params, { variant: "outlined", placeholder: `${intl.formatMessage(messages.placeholder)}`, InputProps: Object.assign({}, params.InputProps) }))) }), (0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ className: classes.dialogChipWrapper }, { children: invited.map((option, index) => ((0, jsx_runtime_1.jsx)(material_1.Chip, { avatar: (0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: option.username, src: option.avatar }), label: option.username, onDelete: () => {
|
|
133
132
|
handleDelete(option);
|
|
134
133
|
} }, index))) }))] })) })))] }));
|
|
135
134
|
}
|
|
@@ -12,6 +12,7 @@ const AccordionLessons_1 = tslib_1.__importDefault(require("../AccordionLessons"
|
|
|
12
12
|
const api_services_1 = require("@selfcommunity/api-services");
|
|
13
13
|
const utils_1 = require("@selfcommunity/utils");
|
|
14
14
|
const Errors_1 = require("../../constants/Errors");
|
|
15
|
+
const UserAvatar_1 = tslib_1.__importDefault(require("../UserAvatar"));
|
|
15
16
|
const classes = {
|
|
16
17
|
dialogRoot: `${constants_1.PREFIX}-dialog-root`,
|
|
17
18
|
contentWrapper: `${constants_1.PREFIX}-content-wrapper`,
|
|
@@ -48,6 +49,8 @@ function SeeProgressButton(props) {
|
|
|
48
49
|
const handleToggleOpen = (0, react_1.useCallback)(() => {
|
|
49
50
|
setOpen((prev) => !prev);
|
|
50
51
|
}, [setOpen]);
|
|
51
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [isMobile ? ((0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ size: "small", color: "inherit", onClick: handleToggleOpen }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "chevron_right" }) }))) : ((0, jsx_runtime_1.jsx)(material_1.Button, Object.assign({ variant: "outlined", size: "small", color: "inherit", onClick: handleToggleOpen }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.action.btn.label", defaultMessage: "ui.courseUsersTable.action.btn.label" }) })) }))), open && ((0, jsx_runtime_1.jsx)(DialogRoot, Object.assign({ DialogContentProps: { dividers: isMobile }, open: true, scroll: "paper", onClose: handleToggleOpen, title: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h3" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.dialog.title", defaultMessage: "ui.courseUsersTable.dialog.title" }) })), className: classes.dialogRoot }, { children: (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.contentWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.infoOuterWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.infoInnerWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.avatarWrapper }, { children: [(0, jsx_runtime_1.jsx)(
|
|
52
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [isMobile ? ((0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ size: "small", color: "inherit", onClick: handleToggleOpen }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "chevron_right" }) }))) : ((0, jsx_runtime_1.jsx)(material_1.Button, Object.assign({ variant: "outlined", size: "small", color: "inherit", onClick: handleToggleOpen }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.action.btn.label", defaultMessage: "ui.courseUsersTable.action.btn.label" }) })) }))), open && ((0, jsx_runtime_1.jsx)(DialogRoot, Object.assign({ DialogContentProps: { dividers: isMobile }, open: true, scroll: "paper", onClose: handleToggleOpen, title: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h3" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.dialog.title", defaultMessage: "ui.courseUsersTable.dialog.title" }) })), className: classes.dialogRoot }, { children: (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.contentWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.infoOuterWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.infoInnerWrapper }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.avatarWrapper }, { children: [(0, jsx_runtime_1.jsx)(react_core_1.Link, Object.assign({}, (!course.created_by.deleted && {
|
|
53
|
+
to: scRoutingContext.url(react_core_1.SCRoutes.USER_PROFILE_ROUTE_NAME, course.created_by)
|
|
54
|
+
}), { children: (0, jsx_runtime_1.jsx)(UserAvatar_1.default, Object.assign({ hide: !course.created_by.community_badge, smaller: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Avatar, { className: classes.avatar, src: user.avatar, alt: user.username }) })) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1" }, { children: user.username }))] })), (0, jsx_runtime_1.jsx)(material_1.Button, Object.assign({ component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, user), variant: "outlined", size: "small", color: "inherit" }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.dialog.btn.label", defaultMessage: "ui.courseUsersTable.dialog.btn.label" }) })) }))] })), student ? ((0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.dialog.info.text1", defaultMessage: "ui.courseUsersTable.dialog.info.text1", values: { lessonsCompleted: student.num_lessons_completed } }) }))) : ((0, jsx_runtime_1.jsx)(material_1.Skeleton, { animation: "wave", variant: "text", width: "100px", height: "21px" })), student ? ((0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courseUsersTable.dialog.info.text2", defaultMessage: "ui.courseUsersTable.dialog.info.text2", values: { courseCompleted: student.user_completion_rate } }) }))) : ((0, jsx_runtime_1.jsx)(material_1.Skeleton, { animation: "wave", variant: "text", width: "100px", height: "21px" }))] })), (0, jsx_runtime_1.jsx)(AccordionLessons_1.default, { course: student, viewerJoinStatus: course.join_status })] })) })))] }));
|
|
52
55
|
}
|
|
53
56
|
exports.default = (0, react_1.memo)(SeeProgressButton);
|
|
@@ -9,7 +9,7 @@ import { SCCourseJoinStatusType, SCCourseLessonCompletionStatusType, SCCoursePri
|
|
|
9
9
|
import { FormattedMessage, useIntl } from 'react-intl';
|
|
10
10
|
import ActionButton from './Student/ActionButton';
|
|
11
11
|
import { CLAPPING } from '../../assets/courses/clapping';
|
|
12
|
-
import { SCRoutes, useSCFetchCourse, useSCRouting, Link } from '@selfcommunity/react-core';
|
|
12
|
+
import { SCRoutes, useSCFetchCourse, useSCRouting, Link, useSCUser, useSCContext } from '@selfcommunity/react-core';
|
|
13
13
|
import AccordionLessons from '../../shared/AccordionLessons';
|
|
14
14
|
import { CourseService } from '@selfcommunity/api-services';
|
|
15
15
|
import { Logger } from '@selfcommunity/utils';
|
|
@@ -50,6 +50,7 @@ const classes = {
|
|
|
50
50
|
contrastBgColor: `${PREFIX}-contrast-bg-color`
|
|
51
51
|
};
|
|
52
52
|
function getUrlNextLesson(course) {
|
|
53
|
+
var _a;
|
|
53
54
|
const data = {
|
|
54
55
|
id: course.id,
|
|
55
56
|
slug: course.slug
|
|
@@ -61,7 +62,7 @@ function getUrlNextLesson(course) {
|
|
|
61
62
|
});
|
|
62
63
|
return data;
|
|
63
64
|
}
|
|
64
|
-
course.sections.some((section) => {
|
|
65
|
+
(_a = course.sections) === null || _a === void 0 ? void 0 : _a.some((section) => {
|
|
65
66
|
const isNextLessonInThisSection = section.num_lessons_completed < section.num_lessons;
|
|
66
67
|
if (isNextLessonInThisSection) {
|
|
67
68
|
Object.assign(data, {
|
|
@@ -74,7 +75,8 @@ function getUrlNextLesson(course) {
|
|
|
74
75
|
return data;
|
|
75
76
|
}
|
|
76
77
|
function getIsNextLessonLocked(course) {
|
|
77
|
-
|
|
78
|
+
var _a;
|
|
79
|
+
return (_a = course.sections) === null || _a === void 0 ? void 0 : _a.every((section) => {
|
|
78
80
|
var _a, _b;
|
|
79
81
|
return (section.num_lessons_completed < section.num_lessons &&
|
|
80
82
|
((_b = (_a = section.lessons) === null || _a === void 0 ? void 0 : _a.find((lesson) => lesson.completion_status === SCCourseLessonCompletionStatusType.UNCOMPLETED)) === null || _b === void 0 ? void 0 : _b.locked));
|
|
@@ -97,6 +99,8 @@ function Student(inProps) {
|
|
|
97
99
|
const [loadingRequest, setLoadingRequest] = useState(false);
|
|
98
100
|
// CONTEXTS
|
|
99
101
|
const scRoutingContext = useSCRouting();
|
|
102
|
+
const scContext = useSCContext();
|
|
103
|
+
const scUserContext = useSCUser();
|
|
100
104
|
// HOOKS
|
|
101
105
|
const { scCourse, setSCCourse } = useSCFetchCourse({ id: courseId, course });
|
|
102
106
|
const intl = useIntl();
|
|
@@ -138,6 +142,9 @@ function Student(inProps) {
|
|
|
138
142
|
Logger.error(SCOPE_SC_UI, error);
|
|
139
143
|
});
|
|
140
144
|
}, [scCourse, sentRequest, setLoadingRequest]);
|
|
145
|
+
const handleAnonymousAction = useCallback(() => {
|
|
146
|
+
scContext.settings.handleAnonymousAction();
|
|
147
|
+
}, [scContext.settings.handleAnonymousAction]);
|
|
141
148
|
// MEMOS
|
|
142
149
|
const actionButton = useMemo(() => {
|
|
143
150
|
if (!scCourse) {
|
|
@@ -151,7 +158,7 @@ function Student(inProps) {
|
|
|
151
158
|
? BUTTON_MESSAGES.start
|
|
152
159
|
: scCourse.user_completion_rate === 100
|
|
153
160
|
? BUTTON_MESSAGES.review
|
|
154
|
-
: BUTTON_MESSAGES.continue, to: scCourse.join_status !== null ? scRoutingContext.url(SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlNextLesson(scCourse)) : undefined, disabled: scCourse.join_status !== null ? getIsNextLessonLocked(scCourse) : undefined, color: scCourse.user_completion_rate === 100 ? 'inherit' : undefined, variant: scCourse.user_completion_rate === 100 ? 'outlined' : undefined, loading: scCourse.join_status === null ? loadingRequest : undefined, onClick: scCourse.join_status === null ? handleRequest : undefined })), scCourse.privacy === SCCoursePrivacyType.PRIVATE &&
|
|
161
|
+
: BUTTON_MESSAGES.continue, to: scCourse.join_status !== null ? scRoutingContext.url(SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlNextLesson(scCourse)) : undefined, disabled: scCourse.join_status !== null ? getIsNextLessonLocked(scCourse) : undefined, color: scCourse.user_completion_rate === 100 ? 'inherit' : undefined, variant: scCourse.user_completion_rate === 100 ? 'outlined' : undefined, loading: scCourse.join_status === null ? loadingRequest : undefined, onClick: !scUserContext.user ? handleAnonymousAction : scCourse.join_status === null ? handleRequest : undefined })), scCourse.privacy === SCCoursePrivacyType.PRIVATE &&
|
|
155
162
|
(scCourse.join_status === null || scCourse.join_status === SCCourseJoinStatusType.REQUESTED) && (_jsx(ActionButton, { labelId: sentRequest ? BUTTON_MESSAGES.cancel : BUTTON_MESSAGES.request, color: "inherit", variant: "outlined", loading: loadingRequest, onClick: handleRequest }))] })));
|
|
156
163
|
}, [scCourse, sentRequest, loadingRequest, handleRequest]);
|
|
157
164
|
if (!scCourse) {
|
|
@@ -69,6 +69,7 @@ export default function CourseParticipantsButton(inProps) {
|
|
|
69
69
|
const { className, courseId, course, hideCaption = false, DialogProps = {} } = props, rest = __rest(props, ["className", "courseId", "course", "hideCaption", "DialogProps"]);
|
|
70
70
|
// STATE
|
|
71
71
|
const [loading, setLoading] = useState(true);
|
|
72
|
+
const [count, setCount] = useState(0);
|
|
72
73
|
const [next, setNext] = useState(null);
|
|
73
74
|
const [offset, setOffset] = useState(null);
|
|
74
75
|
const [enrolled, setEnrolled] = useState([]);
|
|
@@ -85,17 +86,18 @@ export default function CourseParticipantsButton(inProps) {
|
|
|
85
86
|
if (!scCourse) {
|
|
86
87
|
return;
|
|
87
88
|
}
|
|
88
|
-
if (!
|
|
89
|
+
if (!count && participantsAvailable) {
|
|
89
90
|
CourseService.getCourseJoinedUsers(scCourse.id, { limit: 3 }).then((res) => {
|
|
90
91
|
setEnrolled([...res.results]);
|
|
91
92
|
setOffset(4);
|
|
93
|
+
setCount(res.count);
|
|
92
94
|
setLoading(false);
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
97
|
else {
|
|
96
98
|
setOffset(0);
|
|
97
99
|
}
|
|
98
|
-
}, [scCourse, participantsAvailable,
|
|
100
|
+
}, [scCourse, participantsAvailable, count, setEnrolled, setOffset, setLoading]);
|
|
99
101
|
useEffect(() => {
|
|
100
102
|
if (open && offset !== null) {
|
|
101
103
|
setLoading(true);
|
|
@@ -106,7 +108,7 @@ export default function CourseParticipantsButton(inProps) {
|
|
|
106
108
|
setOffset(null);
|
|
107
109
|
});
|
|
108
110
|
}
|
|
109
|
-
}, [open, enrolled, offset]);
|
|
111
|
+
}, [open, enrolled, offset, setLoading, setNext]);
|
|
110
112
|
/**
|
|
111
113
|
* Memoized fetchEnrolledUsers
|
|
112
114
|
*/
|
|
@@ -125,8 +127,8 @@ export default function CourseParticipantsButton(inProps) {
|
|
|
125
127
|
})
|
|
126
128
|
.catch((error) => Logger.error(SCOPE_SC_UI, error))
|
|
127
129
|
.then(() => setLoading(false));
|
|
128
|
-
}, [enrolled,
|
|
129
|
-
const renderSurplus = useCallback(() => numberFormatter(
|
|
130
|
+
}, [enrolled, next, setLoading]);
|
|
131
|
+
const renderSurplus = useCallback(() => numberFormatter(count), [count]);
|
|
130
132
|
/**
|
|
131
133
|
* Opens participants dialog
|
|
132
134
|
*/
|
|
@@ -139,7 +141,7 @@ export default function CourseParticipantsButton(inProps) {
|
|
|
139
141
|
if (!participantsAvailable) {
|
|
140
142
|
return _jsx(HiddenPlaceholder, {});
|
|
141
143
|
}
|
|
142
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Root, Object.assign({ className: classNames(classes.root, className), onClick: handleToggleDialogOpen, disabled: loading || !scCourse ||
|
|
144
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Root, Object.assign({ className: classNames(classes.root, className), onClick: handleToggleDialogOpen, disabled: loading || !scCourse || count === 0,
|
|
143
145
|
// @ts-expect-error this is needed to use enrolled into SCCourseParticipantsButton
|
|
144
|
-
enrolled: enrolled }, rest, { children: [!hideCaption && (_jsxs(Typography, Object.assign({ className: classes.participants }, { children: [_jsx(Icon, { children: "people_alt" }), _jsx(FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.participants", id: "ui.courseParticipantsButton.participants", values: { total:
|
|
146
|
+
enrolled: enrolled }, rest, { children: [!hideCaption && (_jsxs(Typography, Object.assign({ className: classes.participants }, { children: [_jsx(Icon, { children: "people_alt" }), _jsx(FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.participants", id: "ui.courseParticipantsButton.participants", values: { total: count } })] }))), !count && (loading || !scCourse) ? (_jsx(AvatarGroupSkeleton, Object.assign({}, rest, (!participantsAvailable && { skeletonsAnimation: false }), { count: 4 }))) : (_jsx(AvatarGroup, Object.assign({ total: count, renderSurplus: renderSurplus }, { children: enrolled.map((c) => (_jsx(Avatar, { alt: c.username, src: c.avatar }, c.id))) })))] })), open && (_jsx(DialogRoot, Object.assign({ className: classes.dialogRoot, title: _jsx(FormattedMessage, { defaultMessage: "ui.courseParticipantsButton.dialogTitle", id: "ui.courseParticipantsButton.dialogTitle", values: { total: count } }), onClose: handleToggleDialogOpen, open: true }, DialogProps, { children: _jsx(InfiniteScroll, Object.assign({ dataLength: count, next: fetchEnrolledUsers, hasMoreNext: next !== null || loading, loaderNext: _jsx(UserSkeleton, { elevation: 0 }), className: classes.infiniteScroll, endMessage: _jsx(Typography, Object.assign({ className: classes.endMessage }, { children: _jsx(FormattedMessage, { id: "ui.courseParticipantsButton.noOtherParticipants", defaultMessage: "ui.courseParticipantsButton.noOtherParticipants" }) })) }, { children: _jsx(List, { children: enrolled.map((e) => (_jsx(ListItem, { children: _jsx(User, { elevation: 0, user: e }) }, e.id))) }) })) })))] }));
|
|
145
147
|
}
|
|
@@ -43,7 +43,6 @@ function Users(props) {
|
|
|
43
43
|
} } = props;
|
|
44
44
|
// STATES
|
|
45
45
|
const [state, dispatch] = useReducer(dataWidgetReducer, {
|
|
46
|
-
isLoadingPrevious: false,
|
|
47
46
|
isLoadingNext: false,
|
|
48
47
|
next: null,
|
|
49
48
|
cacheKey: SCCache.getWidgetStateCacheKey(SCCache.USER_PARTECIPANTS_COURSES_STATE_CACHE_PREFIX_KEY, course.id),
|
|
@@ -100,6 +99,10 @@ function Users(props) {
|
|
|
100
99
|
type: actionWidgetTypes.LOAD_PREVIOUS_SUCCESS,
|
|
101
100
|
payload: { count: state.count + newUsers.length, results: newUsers, initialized: true }
|
|
102
101
|
});
|
|
102
|
+
enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.editCourse.tab.users.table.snackbar.success", defaultMessage: "ui.editCourse.tab.users.table.snackbar.success" }), {
|
|
103
|
+
variant: 'success',
|
|
104
|
+
autoHideDuration: 3000
|
|
105
|
+
});
|
|
103
106
|
})
|
|
104
107
|
.catch((error) => {
|
|
105
108
|
dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
|
|
@@ -113,6 +116,6 @@ function Users(props) {
|
|
|
113
116
|
return (_jsxs(Box, { children: [_jsx(Typography, Object.assign({ variant: "h6", className: classes.contrastColor }, { children: _jsx(FormattedMessage, { id: "ui.editCourse.tab.users.title", defaultMessage: "ui.editCourse.tab.users.title", values: { usersNumber: state.results.length } }) })), _jsxs(Stack, Object.assign({ className: classes.usersStatusWrapper }, { children: [_jsx(Status, { course: course }), _jsx(AddUsersButton, { label: "ui.editCourse.tab.users.addUsersButton.label", endpoint: {
|
|
114
117
|
url: () => Endpoints.GetCourseSuggestedUsers.url({ id: course.id }),
|
|
115
118
|
method: Endpoints.GetCourseSuggestedUsers.method
|
|
116
|
-
}, onConfirm: handleConfirm,
|
|
119
|
+
}, onConfirm: handleConfirm, className: classes.contrastBgColor })] })), _jsx(CourseUsersTable, { course: course, state: state, dispatch: dispatch, headerCells: headerCells, mode: "edit", emptyStatusTitle: "ui.courseUsersTable.empty.users.title", emptyStatusDescription: "ui.courseUsersTable.empty.users.description" })] }));
|
|
117
120
|
}
|
|
118
121
|
export default memo(Users);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
|
-
import { SCCourseType } from '@selfcommunity/types';
|
|
2
|
+
import { SCCourseJoinStatusType, SCCourseType } from '@selfcommunity/types';
|
|
3
3
|
export interface AccordionLessonsProps {
|
|
4
4
|
course: SCCourseType | null;
|
|
5
|
+
viewerJoinStatus?: SCCourseJoinStatusType;
|
|
5
6
|
className?: HTMLAttributes<HTMLDivElement>['className'];
|
|
6
7
|
}
|
|
7
8
|
export default function AccordionLessons(inProps: AccordionLessonsProps): JSX.Element;
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Accordion, AccordionDetails, AccordionSummary, Box, Icon, styled, Typography, useMediaQuery, useTheme, useThemeProps } from '@mui/material';
|
|
2
|
+
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Icon, styled, Typography, useMediaQuery, useTheme, useThemeProps } from '@mui/material';
|
|
3
3
|
import { FormattedMessage } from 'react-intl';
|
|
4
4
|
import classNames from 'classnames';
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
|
-
import { SCCourseLessonCompletionStatusType } from '@selfcommunity/types';
|
|
6
|
+
import { SCCourseJoinStatusType, SCCourseLessonCompletionStatusType } from '@selfcommunity/types';
|
|
7
|
+
import { SCRoutes, useSCRouting } from '@selfcommunity/react-core';
|
|
7
8
|
import { PREFIX } from './constants';
|
|
8
9
|
import AccordionLessonSkeleton from './Skeleton';
|
|
10
|
+
import { Link } from '@selfcommunity/react-core';
|
|
9
11
|
const classes = {
|
|
10
12
|
root: `${PREFIX}-root`,
|
|
11
13
|
empty: `${PREFIX}-empty`,
|
|
12
14
|
accordion: `${PREFIX}-accordion`,
|
|
13
15
|
summary: `${PREFIX}-summary`,
|
|
14
16
|
details: `${PREFIX}-details`,
|
|
15
|
-
circle: `${PREFIX}-circle
|
|
17
|
+
circle: `${PREFIX}-circle`,
|
|
18
|
+
link: `${PREFIX}-link`
|
|
16
19
|
};
|
|
20
|
+
function getUrlLesson(course, section, lesson) {
|
|
21
|
+
return {
|
|
22
|
+
id: course.id,
|
|
23
|
+
slug: course.slug,
|
|
24
|
+
section_id: section.id,
|
|
25
|
+
lesson_id: lesson.id
|
|
26
|
+
};
|
|
27
|
+
}
|
|
17
28
|
const Root = styled(Box, {
|
|
18
29
|
name: PREFIX,
|
|
19
30
|
slot: 'Root',
|
|
@@ -26,12 +37,14 @@ export default function AccordionLessons(inProps) {
|
|
|
26
37
|
props: inProps,
|
|
27
38
|
name: PREFIX
|
|
28
39
|
});
|
|
29
|
-
const { course, className } = props;
|
|
40
|
+
const { course, viewerJoinStatus, className } = props;
|
|
30
41
|
//STATES
|
|
31
42
|
const [expanded, setExpanded] = useState(false);
|
|
32
43
|
// HOOKS
|
|
33
44
|
const theme = useTheme();
|
|
34
45
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
46
|
+
// CONTEXTS
|
|
47
|
+
const scRoutingContext = useSCRouting();
|
|
35
48
|
// HANDLERS
|
|
36
49
|
const handleChange = useCallback((panel) => (_, newExpanded) => {
|
|
37
50
|
setExpanded(newExpanded ? panel : false);
|
|
@@ -41,5 +54,5 @@ export default function AccordionLessons(inProps) {
|
|
|
41
54
|
}
|
|
42
55
|
return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, { children: ((_a = course.sections) === null || _a === void 0 ? void 0 : _a.length) > 0 ? (course.sections.map((section) => (_jsxs(Accordion, Object.assign({ className: classes.accordion, expanded: expanded === section.id, onChange: handleChange(section.id), disableGutters: true, elevation: 0, square: true }, { children: [_jsxs(AccordionSummary, Object.assign({ className: classes.summary, expandIcon: _jsx(Icon, { children: "expand_less" }) }, { children: [_jsx(Typography, Object.assign({ component: "span", variant: "body1" }, { children: section.name })), !isMobile && (_jsx(Typography, Object.assign({ component: "span", variant: "body1" }, { children: _jsx(FormattedMessage, { id: "ui.course.table.lessons.title", defaultMessage: "ui.course.table.lessons.title", values: {
|
|
43
56
|
lessonsNumber: section.lessons.length
|
|
44
|
-
} }) })))] })), section.lessons.map((lesson) => (_jsxs(AccordionDetails, Object.assign({ className: classes.details }, { children: [lesson.completion_status === SCCourseLessonCompletionStatusType.COMPLETED ? (_jsx(Icon, Object.assign({ fontSize: "small", color: "primary" }, { children: "circle_checked" }))) : lesson.locked ? (_jsx(Icon, { children: "private" })) : (_jsx(Box, { className: classes.circle })), _jsx(Typography, { children: lesson.name })] }), lesson.id)))] }), section.id)))) : (_jsx(Typography, Object.assign({ variant: "body1", className: classes.empty }, { children: _jsx(FormattedMessage, { id: "ui.course.accordionLessons.empty", defaultMessage: "ui.course.accordionLessons.empty" }) }))) })));
|
|
57
|
+
} }) })))] })), section.lessons.map((lesson) => (_jsxs(AccordionDetails, Object.assign({ className: classes.details }, { children: [lesson.completion_status === SCCourseLessonCompletionStatusType.COMPLETED ? (_jsx(Icon, Object.assign({ fontSize: "small", color: "primary" }, { children: "circle_checked" }))) : lesson.locked ? (_jsx(Icon, { children: "private" })) : (_jsx(Box, { className: classes.circle })), course.join_status === null || viewerJoinStatus === SCCourseJoinStatusType.CREATOR || lesson.locked ? (_jsx(Typography, { children: lesson.name })) : (_jsx(Button, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.COURSE_LESSON_ROUTE_NAME, getUrlLesson(course, section, lesson)), variant: "text", color: "inherit", className: classes.link }, { children: _jsx(Typography, { children: lesson.name }) })))] }), lesson.id)))] }), section.id)))) : (_jsx(Typography, Object.assign({ variant: "body1", className: classes.empty }, { children: _jsx(FormattedMessage, { id: "ui.course.accordionLessons.empty", defaultMessage: "ui.course.accordionLessons.empty" }) }))) })));
|
|
45
58
|
}
|
|
@@ -4,11 +4,6 @@ import { SCUserType } from '@selfcommunity/types';
|
|
|
4
4
|
import { EndpointType } from '@selfcommunity/api-services';
|
|
5
5
|
export interface AddUsersButtonProps extends ButtonProps {
|
|
6
6
|
label: string;
|
|
7
|
-
/**
|
|
8
|
-
* Handles component update
|
|
9
|
-
* @default false
|
|
10
|
-
*/
|
|
11
|
-
isUpdating?: boolean;
|
|
12
7
|
/**
|
|
13
8
|
* Event API Endpoint
|
|
14
9
|
* @default Endpoints.GetCourseSuggestedUsers
|
|
@@ -41,7 +41,7 @@ function AddUsersButton(inProps) {
|
|
|
41
41
|
props: inProps,
|
|
42
42
|
name: PREFIX
|
|
43
43
|
});
|
|
44
|
-
const { label, variant = 'outlined', color = 'inherit', size = 'small',
|
|
44
|
+
const { label, variant = 'outlined', color = 'inherit', size = 'small', endpoint = Endpoints.GetCourseSuggestedUsers, endpointQueryParams = { limit: DEFAULT_PAGINATION_LIMIT, offset: DEFAULT_PAGINATION_OFFSET, search: '' }, onConfirm, className } = props, rest = __rest(props, ["label", "variant", "color", "size", "endpoint", "endpointQueryParams", "onConfirm", "className"]);
|
|
45
45
|
// STATES
|
|
46
46
|
const [openDialog, setOpenDialog] = useState(false);
|
|
47
47
|
const [invited, setInvited] = useState([]);
|
|
@@ -84,17 +84,16 @@ function AddUsersButton(inProps) {
|
|
|
84
84
|
* @param reason
|
|
85
85
|
*/
|
|
86
86
|
const handleToggleDialogOpen = useCallback(() => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
}, [isUpdating, setOpenDialog]);
|
|
87
|
+
setOpenDialog((prev) => !prev);
|
|
88
|
+
}, [setOpenDialog]);
|
|
91
89
|
/**
|
|
92
90
|
* Handles action confirm
|
|
93
91
|
*/
|
|
94
92
|
const handleConfirm = useCallback(() => {
|
|
95
93
|
onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(invited);
|
|
96
94
|
setInvited([]);
|
|
97
|
-
|
|
95
|
+
handleToggleDialogOpen();
|
|
96
|
+
}, [invited, onConfirm, handleToggleDialogOpen]);
|
|
98
97
|
// HANDLERS AUTOCOMPLETE
|
|
99
98
|
const handleInputChange = useCallback((_, newValue, reason) => {
|
|
100
99
|
switch (reason) {
|
|
@@ -127,7 +126,7 @@ function AddUsersButton(inProps) {
|
|
|
127
126
|
const handleDelete = useCallback((userToDelete) => {
|
|
128
127
|
setInvited((prev) => prev.filter((user) => user.id !== userToDelete.id));
|
|
129
128
|
}, [setInvited]);
|
|
130
|
-
return (_jsxs(Fragment, { children: [_jsx(Root, Object.assign({ onClick: handleToggleDialogOpen, variant: variant, color: color, size: size, className: classNames(classes.root, className) }, rest, { children: _jsx(FormattedMessage, { id: label, defaultMessage: label }) })), openDialog && (_jsx(DialogRoot, Object.assign({ DialogContentProps: { dividers: false }, open: true, onClose: handleToggleDialogOpen, title: _jsx(Typography, Object.assign({ variant: "h5" }, { children: _jsx(FormattedMessage, { id: "ui.addUserButton.dialog.title", defaultMessage: "ui.addUserButton.dialog.title" }) })), actions: _jsx(LoadingButton, Object.assign({ onClick: handleConfirm, size: "medium", variant: "contained", autoFocus: true, disabled: !invited.length
|
|
129
|
+
return (_jsxs(Fragment, { children: [_jsx(Root, Object.assign({ onClick: handleToggleDialogOpen, variant: variant, color: color, size: size, className: classNames(classes.root, className) }, rest, { children: _jsx(FormattedMessage, { id: label, defaultMessage: label }) })), openDialog && (_jsx(DialogRoot, Object.assign({ DialogContentProps: { dividers: false }, open: true, onClose: handleToggleDialogOpen, title: _jsx(Typography, Object.assign({ variant: "h5" }, { children: _jsx(FormattedMessage, { id: "ui.addUserButton.dialog.title", defaultMessage: "ui.addUserButton.dialog.title" }) })), actions: _jsx(LoadingButton, Object.assign({ onClick: handleConfirm, size: "medium", variant: "contained", autoFocus: true, disabled: !invited.length }, { children: _jsx(Typography, Object.assign({ variant: "body1" }, { children: _jsx(FormattedMessage, { id: "ui.addUserButton.dialog.confirm", defaultMessage: "ui.addUserButton.dialog.confirm" }) })) })), className: classes.dialogRoot }, { children: _jsxs(Stack, Object.assign({ className: classes.dialogAutocompleteWrapper }, { children: [_jsx(Autocomplete, { loading: loading, size: "small", multiple: true, options: suggested, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, filterOptions: filterOptions, value: invited, getOptionLabel: (option) => (option === null || option === void 0 ? void 0 : option.username) || '...', isOptionEqualToValue: (option, value) => (option === null || option === void 0 ? void 0 : option.id) === value.id, loadingText: _jsx(FormattedMessage, { id: "ui.addUserButton.autocomplete.loading", defaultMessage: "ui.addUserButton.autocomplete.loading" }), noOptionsText: _jsx(FormattedMessage, { id: "ui.addUserButton.autocomplete.noResults", defaultMessage: "ui.addUserButton.autocomplete.noResults" }), renderTags: () => null, popupIcon: null, disableClearable: true, renderOption: (props, option) => (_jsxs(Stack, Object.assign({ component: "li", flexDirection: "row", gap: "5px" }, props, { children: [_jsx(Avatar, { alt: option.username, src: option.avatar }), _jsx(Typography, { children: option.username })] }))), renderInput: (params) => (_jsx(TextField, Object.assign({}, params, { variant: "outlined", placeholder: `${intl.formatMessage(messages.placeholder)}`, InputProps: Object.assign({}, params.InputProps) }))) }), _jsx(Stack, Object.assign({ className: classes.dialogChipWrapper }, { children: invited.map((option, index) => (_jsx(Chip, { avatar: _jsx(Avatar, { alt: option.username, src: option.avatar }), label: option.username, onDelete: () => {
|
|
131
130
|
handleDelete(option);
|
|
132
131
|
} }, index))) }))] })) })))] }));
|
|
133
132
|
}
|