From 742d5bedbcabb342c60942953ff859be0a0b6820 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Mon, 3 Jul 2023 04:57:45 -0700 Subject: [PATCH 01/21] modify get router --- src/commons/sagas/RequestsSaga.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index dd115f1445..6b2f724ff2 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -622,7 +622,7 @@ export const getAssessment = async ( q.library.globals = Object.entries(q.library.globals as object).map(entry => { try { entry[1] = (window as any).eval(entry[1]); - } catch (e) {} + } catch (e) { } return entry; }); @@ -1076,8 +1076,7 @@ export const putAssessmentConfigs = async ( overrideCourseId?: number ): Promise => { const resp = await request( - `${ - overrideCourseId != null ? `courses/${overrideCourseId}` : courseId() + `${overrideCourseId != null ? `courses/${overrideCourseId}` : courseId() }/admin/config/assessment_configs`, 'PUT', { @@ -1196,7 +1195,8 @@ export const putTimeOptions = async ( export const getNotificationConfigs = async ( tokens: Tokens ): Promise => { - const resp = await request(`notifications/config/${courseIdWithoutPrefix()}`, 'GET', { + //const resp = await request(`notifications/config/${courseIdWithoutPrefix()}`, 'GET', { + const resp = await request(`courses/${courseIdWithoutPrefix()}/admin/notifications/config`, 'GET', { ...tokens, shouldRefresh: true }); @@ -1211,7 +1211,8 @@ export const getConfigurableNotificationConfigs = async ( tokens: Tokens, courseRegId: number ): Promise => { - const resp = await request(`notifications/config/user/${courseRegId}`, 'GET', { + //const resp = await request(`notifications/config/user/${courseRegId}`, 'GET', { + const resp = await request(`courses/${courseIdWithoutPrefix()}/notifications/config/user/${courseRegId}`, 'GET', { ...tokens, shouldRefresh: true }); @@ -1516,11 +1517,10 @@ export const request = async ( showWarningMessage( opts.errorMessage ? opts.errorMessage - : `Error while communicating with backend: ${resp.status} ${resp.statusText}${ - resp.status === 401 || resp.status === 403 - ? '; try logging in again, after manually saving any work.' - : '' - }` + : `Error while communicating with backend: ${resp.status} ${resp.statusText}${resp.status === 401 || resp.status === 403 + ? '; try logging in again, after manually saving any work.' + : '' + }` ); return null; } @@ -1573,8 +1573,8 @@ const computeGradingStatus = ( ? numGraded === 0 ? 'none' : numGraded === numQuestions - ? 'graded' - : 'grading' + ? 'graded' + : 'grading' : 'excluded'; const courseId: () => string = () => { @@ -1597,4 +1597,4 @@ const courseIdWithoutPrefix: () => string = () => { showWarningMessage(`No course selected!`, 1000); throw new Error(`No course selected`); } -}; +}; \ No newline at end of file From 3bc8be45edf359e3d488b88ea6fe84027bd94260 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Thu, 6 Jul 2023 03:04:40 -0700 Subject: [PATCH 02/21] 5routers --- src/commons/sagas/RequestsSaga.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index 6b2f724ff2..cd24b86925 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -1091,11 +1091,12 @@ export const putAssessmentConfigs = async ( return resp; }; +//Notification related request export const putNotificationConfigs = async ( tokens: Tokens, notificationConfigs: NotificationConfiguration[] ) => { - return await request(`notifications/config`, 'PUT', { + return await request(`courses/${courseIdWithoutPrefix()}/admin/notifications/config`, 'PUT', { ...tokens, body: notificationConfigs, noHeaderAccept: true, @@ -1166,7 +1167,7 @@ export const removeTimeOptions = async ( tokens: Tokens, timeOptionIds: number[] ): Promise => { - const resp = await request(`notifications/options`, 'DELETE', { + const resp = await request(`courses/${courseIdWithoutPrefix()}/admin/notifications/options`, 'DELETE', { ...tokens, body: timeOptionIds, noHeaderAccept: true, @@ -1181,7 +1182,7 @@ export const putTimeOptions = async ( tokens: Tokens, timeOptions: TimeOption[] ): Promise => { - const resp = await request(`notifications/options`, 'PUT', { + const resp = await request(`courses/${courseIdWithoutPrefix()}/notifications/options`, 'PUT', { ...tokens, body: timeOptions, noHeaderAccept: true, From 83260a5c50256507ee040623a61b59e575562882 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Thu, 6 Jul 2023 22:49:46 -0700 Subject: [PATCH 03/21] all routers --- src/commons/sagas/RequestsSaga.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index cd24b86925..acda15e1bf 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -1163,6 +1163,7 @@ export const removeAssessmentConfig = async ( return resp; }; + export const removeTimeOptions = async ( tokens: Tokens, timeOptionIds: number[] @@ -1246,12 +1247,13 @@ export const postNotificationPreference = async ( return resp; }; + export const putNotificationPreferences = async ( tokens: Tokens, notiPrefs: NotificationPreference[], courseRegId: number ): Promise => { - const resp = await request(`notifications/preferences`, 'PUT', { + const resp = await request(`courses/${courseIdWithoutPrefix()}/notifications/preferences`, 'PUT', { ...tokens, body: notiPrefs.map(pref => { return { ...pref, courseRegId: courseRegId }; From b5a04718acf9d44d9e0eb6325e954ec085f90a46 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 18 Jul 2023 23:33:39 -0700 Subject: [PATCH 04/21] modify admin colum --- .../adminPanel/subcomponents/NotificationConfigPanel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx index d687bb795c..46a7e73d34 100644 --- a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx @@ -15,7 +15,7 @@ import { NotificationConfiguration, TimeOption } from 'src/commons/application/t import { useTypedSelector } from 'src/commons/utils/Hooks'; import BooleanCell from './assessmentConfigPanel/BooleanCell'; -import SelectCell from './notificationConfigPanel/SelectCell'; +//import SelectCell from './notificationConfigPanel/SelectCell'; import TimeOptionCell from './notificationConfigPanel/TimeOptionCell'; const NotificationConfigPanel = () => { @@ -120,7 +120,7 @@ const NotificationConfigPanel = () => { // field: 'notificationType.id' // }, { - headerName: 'Reminder Time Options (hours)', + headerName: 'Default Reminder Time(hours)', field: 'timeOptions', cellRendererFramework: TimeOptionCell, cellRendererParams: { @@ -129,6 +129,7 @@ const NotificationConfigPanel = () => { field: 'timeOptions' } }, + /* { headerName: 'Default Reminder (hours)', field: 'timeOptions', @@ -138,6 +139,7 @@ const NotificationConfigPanel = () => { field: 'timeOptions' } }, + */ { headerName: 'Enabled', field: 'isEnabled', From ab04f184123a37fe22e6bc1de4e025fa1aaa4399 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Wed, 19 Jul 2023 00:13:53 -0700 Subject: [PATCH 05/21] add default time col for user page --- .../academy/notiPreference/NotiPreference.tsx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/pages/academy/notiPreference/NotiPreference.tsx b/src/pages/academy/notiPreference/NotiPreference.tsx index 578a2c54cc..a45cf34d1d 100644 --- a/src/pages/academy/notiPreference/NotiPreference.tsx +++ b/src/pages/academy/notiPreference/NotiPreference.tsx @@ -87,6 +87,24 @@ const NotiPreference: React.FC = () => { return params.data!.notificationType.forStaff ? 'Staff' : 'Student'; }; + const defaultTimeFormatter: ValueFormatterFunc = params => { + const timeOptions = params.data!.timeOptions; + timeOptions.sort((to1, to2) => to1.minutes - to2.minutes); + + const getUserFriendlyText = (option: TimeOption) => + option.minutes >= 60 + ? `${Math.round((option.minutes / 60) * 100) / 100} hour(s)` + : `${option.minutes} minute(s)`; + + let result = ""; + for (const timeOption of timeOptions) { + result += getUserFriendlyText(timeOption); + result += " " + } + + return result; + }; + const columnDefs = [ { headerName: 'Notification Type', @@ -103,6 +121,11 @@ const NotiPreference: React.FC = () => { field: 'notificationType.forStaff', valueFormatter: recipientFormatter }, + { + headerName: 'Default Time', + field: 'timeOptions', + valueFormatter: defaultTimeFormatter + }, { headerName: 'Reminder', field: 'timeOptions', @@ -177,7 +200,7 @@ const NotiPreference: React.FC = () => { ); - return {}} display={data} fullWidth={false} />; + return { }} display={data} fullWidth={false} />; }; export default NotiPreference; From 9aa26b77d9cea16cb951fd690cb91168988d3d63 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Wed, 19 Jul 2023 00:22:43 -0700 Subject: [PATCH 06/21] swap type col and assessment col --- src/pages/academy/notiPreference/NotiPreference.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/academy/notiPreference/NotiPreference.tsx b/src/pages/academy/notiPreference/NotiPreference.tsx index a45cf34d1d..fcaba095ea 100644 --- a/src/pages/academy/notiPreference/NotiPreference.tsx +++ b/src/pages/academy/notiPreference/NotiPreference.tsx @@ -107,14 +107,14 @@ const NotiPreference: React.FC = () => { const columnDefs = [ { - headerName: 'Notification Type', - field: 'notificationType.name', + headerName: 'Assessment Type', + field: 'assessmentConfig.type', + valueFormatter: assessmentTypeFormatter, rowDrag: true }, { - headerName: 'Assessment Type', - field: 'assessmentConfig.type', - valueFormatter: assessmentTypeFormatter + headerName: 'Notification Type', + field: 'notificationType.name', }, { headerName: 'Recipients', From 0fe7176953614bee3e8857a4286677a44a279f67 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Wed, 19 Jul 2023 00:30:42 -0700 Subject: [PATCH 07/21] swap type col and assessment col --- .../subcomponents/NotificationConfigPanel.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx index 46a7e73d34..531e24e9c7 100644 --- a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx @@ -96,15 +96,16 @@ const NotificationConfigPanel = () => { const columnDefs = [ { - headerName: 'Notification Type', - field: 'notificationType.name', + headerName: 'Assessment Type', + field: 'assessmentConfig.type', + valueFormatter: assessmentTypeFormatter, rowDrag: true }, { - headerName: 'Assessment Type', - field: 'assessmentConfig.type', - valueFormatter: assessmentTypeFormatter + headerName: 'Notification Type', + field: 'notificationType.name', }, + { headerName: 'Recipients', field: 'notificationType.forStaff', From e2a816d62c2bf24890e192a5637964ca22427566 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Wed, 19 Jul 2023 05:41:14 -0700 Subject: [PATCH 08/21] NA logic --- .../subcomponents/NotificationConfigPanel.tsx | 15 +++++++++++++-- .../notificationConfigPanel/TimeOptionCell.tsx | 7 ++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx index 531e24e9c7..5fb572adec 100644 --- a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx @@ -18,6 +18,7 @@ import BooleanCell from './assessmentConfigPanel/BooleanCell'; //import SelectCell from './notificationConfigPanel/SelectCell'; import TimeOptionCell from './notificationConfigPanel/TimeOptionCell'; + const NotificationConfigPanel = () => { const gridApi = React.useRef(); @@ -94,6 +95,13 @@ const NotificationConfigPanel = () => { return params.data!.notificationType.forStaff ? 'Staff' : 'Student'; }; + + const notificationTypeId: ValueFormatterFunc = params => { + const id = params.data!.notificationType?.id || 0; + return String(id); + }; + + const columnDefs = [ { headerName: 'Assessment Type', @@ -105,7 +113,6 @@ const NotificationConfigPanel = () => { headerName: 'Notification Type', field: 'notificationType.name', }, - { headerName: 'Recipients', field: 'notificationType.forStaff', @@ -120,6 +127,7 @@ const NotificationConfigPanel = () => { // headerName: 'Past 30 Days', // field: 'notificationType.id' // }, + { headerName: 'Default Reminder Time(hours)', field: 'timeOptions', @@ -127,9 +135,12 @@ const NotificationConfigPanel = () => { cellRendererParams: { setStateHandler: setTimeOptions, setDelete: addTimeOptionsToDelete, - field: 'timeOptions' + field: 'timeOptions', + typeId: notificationTypeId } }, + + /* { headerName: 'Default Reminder (hours)', diff --git a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx index ff556790e3..4ede10d354 100644 --- a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx +++ b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx @@ -12,6 +12,7 @@ type OwnProps = { field: KeysOfType; setStateHandler: (rowIndex: number, value: TimeOption[]) => void; setDelete: (timeOption: TimeOption) => void; + typeId: string; }; const TimeOptionCell: React.FC = props => { @@ -55,7 +56,11 @@ const TimeOptionCell: React.FC = props => { } }; - return ; + if (props.typeId === '1' || props.typeId === '2') { + return ; + } else { + return NA; + } }; export default TimeOptionCell; From c115c128ad06d60ab31b114179ffdd5d0abbdd4c Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Mon, 7 Aug 2023 20:30:05 -0700 Subject: [PATCH 09/21] change to reminder col --- src/commons/sagas/RequestsSaga.ts | 86 ++++++++++------- .../subcomponents/NotificationConfigPanel.tsx | 6 +- .../TimeOptionCell.tsx | 16 ++-- .../academy/notiPreference/NotiPreference.tsx | 94 +++++++++++++++++-- .../subcomponents/TimeOptionCell.tsx | 69 ++++++++++++++ 5 files changed, 214 insertions(+), 57 deletions(-) create mode 100644 src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index acda15e1bf..866ed8aac5 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -622,7 +622,7 @@ export const getAssessment = async ( q.library.globals = Object.entries(q.library.globals as object).map(entry => { try { entry[1] = (window as any).eval(entry[1]); - } catch (e) { } + } catch (e) {} return entry; }); @@ -1076,7 +1076,8 @@ export const putAssessmentConfigs = async ( overrideCourseId?: number ): Promise => { const resp = await request( - `${overrideCourseId != null ? `courses/${overrideCourseId}` : courseId() + `${ + overrideCourseId != null ? `courses/${overrideCourseId}` : courseId() }/admin/config/assessment_configs`, 'PUT', { @@ -1163,18 +1164,21 @@ export const removeAssessmentConfig = async ( return resp; }; - export const removeTimeOptions = async ( tokens: Tokens, timeOptionIds: number[] ): Promise => { - const resp = await request(`courses/${courseIdWithoutPrefix()}/admin/notifications/options`, 'DELETE', { - ...tokens, - body: timeOptionIds, - noHeaderAccept: true, - shouldAutoLogout: false, - shouldRefresh: true - }); + const resp = await request( + `courses/${courseIdWithoutPrefix()}/admin/notifications/options`, + 'DELETE', + { + ...tokens, + body: timeOptionIds, + noHeaderAccept: true, + shouldAutoLogout: false, + shouldRefresh: true + } + ); return resp; }; @@ -1198,10 +1202,14 @@ export const getNotificationConfigs = async ( tokens: Tokens ): Promise => { //const resp = await request(`notifications/config/${courseIdWithoutPrefix()}`, 'GET', { - const resp = await request(`courses/${courseIdWithoutPrefix()}/admin/notifications/config`, 'GET', { - ...tokens, - shouldRefresh: true - }); + const resp = await request( + `courses/${courseIdWithoutPrefix()}/admin/notifications/config`, + 'GET', + { + ...tokens, + shouldRefresh: true + } + ); if (!resp || !resp.ok) { return null; } @@ -1214,10 +1222,14 @@ export const getConfigurableNotificationConfigs = async ( courseRegId: number ): Promise => { //const resp = await request(`notifications/config/user/${courseRegId}`, 'GET', { - const resp = await request(`courses/${courseIdWithoutPrefix()}/notifications/config/user/${courseRegId}`, 'GET', { - ...tokens, - shouldRefresh: true - }); + const resp = await request( + `courses/${courseIdWithoutPrefix()}/notifications/config/user/${courseRegId}`, + 'GET', + { + ...tokens, + shouldRefresh: true + } + ); if (!resp || !resp.ok) { return null; } @@ -1247,21 +1259,24 @@ export const postNotificationPreference = async ( return resp; }; - export const putNotificationPreferences = async ( tokens: Tokens, notiPrefs: NotificationPreference[], courseRegId: number ): Promise => { - const resp = await request(`courses/${courseIdWithoutPrefix()}/notifications/preferences`, 'PUT', { - ...tokens, - body: notiPrefs.map(pref => { - return { ...pref, courseRegId: courseRegId }; - }), - noHeaderAccept: true, - shouldAutoLogout: false, - shouldRefresh: true - }); + const resp = await request( + `courses/${courseIdWithoutPrefix()}/notifications/preferences`, + 'PUT', + { + ...tokens, + body: notiPrefs.map(pref => { + return { ...pref, courseRegId: courseRegId }; + }), + noHeaderAccept: true, + shouldAutoLogout: false, + shouldRefresh: true + } + ); return resp; }; @@ -1520,10 +1535,11 @@ export const request = async ( showWarningMessage( opts.errorMessage ? opts.errorMessage - : `Error while communicating with backend: ${resp.status} ${resp.statusText}${resp.status === 401 || resp.status === 403 - ? '; try logging in again, after manually saving any work.' - : '' - }` + : `Error while communicating with backend: ${resp.status} ${resp.statusText}${ + resp.status === 401 || resp.status === 403 + ? '; try logging in again, after manually saving any work.' + : '' + }` ); return null; } @@ -1576,8 +1592,8 @@ const computeGradingStatus = ( ? numGraded === 0 ? 'none' : numGraded === numQuestions - ? 'graded' - : 'grading' + ? 'graded' + : 'grading' : 'excluded'; const courseId: () => string = () => { @@ -1600,4 +1616,4 @@ const courseIdWithoutPrefix: () => string = () => { showWarningMessage(`No course selected!`, 1000); throw new Error(`No course selected`); } -}; \ No newline at end of file +}; diff --git a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx index 5fb572adec..9d056ade13 100644 --- a/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/NotificationConfigPanel.tsx @@ -18,7 +18,6 @@ import BooleanCell from './assessmentConfigPanel/BooleanCell'; //import SelectCell from './notificationConfigPanel/SelectCell'; import TimeOptionCell from './notificationConfigPanel/TimeOptionCell'; - const NotificationConfigPanel = () => { const gridApi = React.useRef(); @@ -95,13 +94,11 @@ const NotificationConfigPanel = () => { return params.data!.notificationType.forStaff ? 'Staff' : 'Student'; }; - const notificationTypeId: ValueFormatterFunc = params => { const id = params.data!.notificationType?.id || 0; return String(id); }; - const columnDefs = [ { headerName: 'Assessment Type', @@ -111,7 +108,7 @@ const NotificationConfigPanel = () => { }, { headerName: 'Notification Type', - field: 'notificationType.name', + field: 'notificationType.name' }, { headerName: 'Recipients', @@ -140,7 +137,6 @@ const NotificationConfigPanel = () => { } }, - /* { headerName: 'Default Reminder (hours)', diff --git a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx index 4ede10d354..01a86906ff 100644 --- a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx +++ b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx @@ -46,7 +46,7 @@ const TimeOptionCell: React.FC = props => { const newTimeOption: TimeOption = { id: -1, minutes: parseFloat(value as string) * 60, - isDefault: false + isDefault: true }; setValues([...values, value]); @@ -56,11 +56,13 @@ const TimeOptionCell: React.FC = props => { } }; - if (props.typeId === '1' || props.typeId === '2') { - return ; - } else { - return NA; - } + return ; + /* +if (props.typeId === '1' || props.typeId === '2') { + return ; +} else { + return NA; +} +*/ }; - export default TimeOptionCell; diff --git a/src/pages/academy/notiPreference/NotiPreference.tsx b/src/pages/academy/notiPreference/NotiPreference.tsx index fcaba095ea..72ad1c92d2 100644 --- a/src/pages/academy/notiPreference/NotiPreference.tsx +++ b/src/pages/academy/notiPreference/NotiPreference.tsx @@ -5,8 +5,11 @@ import { cloneDeep } from 'lodash'; import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { + deleteTimeOptions, fetchConfigurableNotificationConfigs, - updateNotificationPreferences + updateNotificationConfigs, + updateNotificationPreferences, + updateTimeOptions } from 'src/commons/application/actions/SessionActions'; import { NotificationConfiguration, @@ -17,13 +20,17 @@ import ContentDisplay from 'src/commons/ContentDisplay'; import { useTypedSelector } from 'src/commons/utils/Hooks'; import BooleanCell from './subcomponents/BooleanCell'; -import SelectCell from './subcomponents/SelectCell'; +//import SelectCell from './subcomponents/SelectCell'; +import TimeOptionCell from './subcomponents/TimeOptionCell'; const NotiPreference: React.FC = () => { + const session = useTypedSelector(state => state.session); + const notificationConfig = React.useRef( + session.notificationConfigs + ); const gridApi = React.useRef(); const dispatch = useDispatch(); - const session = useTypedSelector(state => state.session); const [hasChanges, setHasChanges] = useState(false); @@ -55,6 +62,22 @@ const NotiPreference: React.FC = () => { }); }, [session]); + //新加的 + const [timeOptionsToDelete, setTimeOptionsToDelete] = useState([]); + const addTimeOptionsToDelete = (deletedElement: TimeOption) => { + // If it is not a newly created row that is yet to be persisted in the backend + if (deletedElement.id !== -1) { + const temp = [...timeOptionsToDelete]; + temp.push(deletedElement); + setTimeOptionsToDelete(temp); + } + }; + + const notificationTypeId: ValueFormatterFunc = params => { + const id = params.data!.notificationType?.id || 0; + return String(id); + }; + const setIsEnabled = (index: number, value: boolean) => { const temp = [...(configurableNotificationConfigs.current ?? [])]; @@ -96,10 +119,10 @@ const NotiPreference: React.FC = () => { ? `${Math.round((option.minutes / 60) * 100) / 100} hour(s)` : `${option.minutes} minute(s)`; - let result = ""; + let result = ''; for (const timeOption of timeOptions) { result += getUserFriendlyText(timeOption); - result += " " + result += ' '; } return result; @@ -114,7 +137,7 @@ const NotiPreference: React.FC = () => { }, { headerName: 'Notification Type', - field: 'notificationType.name', + field: 'notificationType.name' }, { headerName: 'Recipients', @@ -126,13 +149,43 @@ const NotiPreference: React.FC = () => { field: 'timeOptions', valueFormatter: defaultTimeFormatter }, + /* + { + headerName: 'Default Reminder Time(hours)', + field: 'timeOptions', + cellRendererFramework: TimeOptionCell, + cellRendererParams: { + setStateHandler: setTimeOptions, + setDelete: addTimeOptionsToDelete, + field: 'timeOptions', + typeId: notificationTypeId + } + }, + + */ + + /* + 之前的 + { + headerName: 'Reminder', + field: 'timeOptions', + cellRendererFramework: SelectCell, + cellRendererParams: { + setStateHandler: setTimeOption, + field: 'timeOptions' + } + }, + */ + { headerName: 'Reminder', field: 'timeOptions', - cellRendererFramework: SelectCell, + cellRendererFramework: TimeOptionCell, cellRendererParams: { setStateHandler: setTimeOption, - field: 'timeOptions' + setDelete: addTimeOptionsToDelete, + field: 'timeOptions', + typeId: notificationTypeId } }, { @@ -158,7 +211,7 @@ const NotiPreference: React.FC = () => { }; const submitHandler = () => { - if (!hasChanges) return; + //if (!hasChanges) return; const preferences: NotificationPreference[] = configurableNotificationConfigs.current?.map(config => { @@ -169,7 +222,28 @@ const NotiPreference: React.FC = () => { }) ?? []; dispatch(updateNotificationPreferences(preferences, session.courseRegId!)); - setHasChanges(false); + const allTimeOptions: TimeOption[] = []; + notificationConfig.current?.forEach(curr => { + const timeOptions = curr.timeOptions.map(timeOption => { + return { + ...timeOption, + notificationConfigId: curr.id + }; + }); + allTimeOptions.push(...timeOptions); + }); + + if (allTimeOptions.length > 0) { + dispatch(updateTimeOptions(allTimeOptions)); + } + + if (timeOptionsToDelete.length > 0) { + dispatch(deleteTimeOptions(timeOptionsToDelete.map(timeOption => timeOption.id))); + setTimeOptionsToDelete([]); + } + dispatch(updateNotificationConfigs(notificationConfig.current ?? [])); + + //setHasChanges(false); }; const data = ( diff --git a/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx b/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx new file mode 100644 index 0000000000..d27d43329d --- /dev/null +++ b/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx @@ -0,0 +1,69 @@ +import { TagInput } from '@blueprintjs/core'; +import { isInteger } from 'lodash'; +import React, { useState } from 'react'; +import { NotificationConfiguration, TimeOption } from 'src/commons/application/types/SessionTypes'; +import { KeysOfType } from 'src/commons/utils/TypeHelper'; + +type TimeOptionCellProps = OwnProps; + +type OwnProps = { + data: NotificationConfiguration; + rowIndex: number; + field: KeysOfType; + setStateHandler: (rowIndex: number, value: TimeOption[]) => void; + setDelete: (timeOption: TimeOption) => void; + typeId: string; +}; + +const TimeOptionCell: React.FC = props => { + const timeOptions: TimeOption[] = props.data[props.field]; + const [values, setValues] = useState( + timeOptions.map((timeOption: TimeOption) => (timeOption.minutes / 60).toFixed(2).toString()) + ); + + function isValidTimeOption(n: string) { + const num = parseFloat(n); + const minutes = num * 60; + if (!isInteger(minutes)) return false; + // Check if time option already exists + if (timeOptions.some((timeOption: TimeOption) => timeOption.minutes === minutes)) return false; + + return !isNaN(num) && isFinite(num) && num >= 0; + } + + const onRemove = (value: React.ReactNode, index: number) => { + // TODO: Show Warning Dialog + setValues(values.filter(i => i !== value)); + props.setDelete(timeOptions[index]); + props.setStateHandler( + props.rowIndex, + timeOptions.filter((_, i) => i !== index) + ); + }; + + const onAdd = (value: React.ReactNode) => { + if (isValidTimeOption(value as string)) { + const newTimeOption: TimeOption = { + id: -1, + minutes: parseFloat(value as string) * 60, + isDefault: false + }; + + setValues([...values, value]); + props.setStateHandler(props.rowIndex, [...timeOptions, newTimeOption]); + } else { + // TODO: Toaster if posisble + } + }; + + return ; + /* + if (props.typeId === '1' || props.typeId === '2') { + return ; + } else { + return NA; + } + */ +}; + +export default TimeOptionCell; From 2411f32ffb0fc5e16805759ee94236a7b625c3d0 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Mon, 7 Aug 2023 21:05:56 -0700 Subject: [PATCH 10/21] show NA --- .../notificationConfigPanel/TimeOptionCell.tsx | 16 ++++++++-------- .../subcomponents/TimeOptionCell.tsx | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx index 01a86906ff..b858525baa 100644 --- a/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx +++ b/src/pages/academy/adminPanel/subcomponents/notificationConfigPanel/TimeOptionCell.tsx @@ -56,13 +56,13 @@ const TimeOptionCell: React.FC = props => { } }; - return ; - /* -if (props.typeId === '1' || props.typeId === '2') { - return ; -} else { - return NA; -} -*/ + //return ; + + if (props.typeId === '1' || props.typeId === '2') { + return ; + } else { + return NA; + } + }; export default TimeOptionCell; diff --git a/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx b/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx index d27d43329d..187c342b6b 100644 --- a/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx +++ b/src/pages/academy/notiPreference/subcomponents/TimeOptionCell.tsx @@ -56,14 +56,14 @@ const TimeOptionCell: React.FC = props => { } }; - return ; - /* - if (props.typeId === '1' || props.typeId === '2') { - return ; - } else { - return NA; - } - */ + //return ; + + if (props.typeId === '1' || props.typeId === '2') { + return ; + } else { + return NA; + } + }; export default TimeOptionCell; From cac06310e09f99f7af27e86ecaf2662256ade64e Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 15 Aug 2023 04:14:41 -0700 Subject: [PATCH 11/21] fix the prefer page box --- .../academy/notiPreference/NotiPreference.tsx | 129 ++++++++---------- .../subcomponents/TimeOptionCell.tsx | 18 +-- 2 files changed, 64 insertions(+), 83 deletions(-) diff --git a/src/pages/academy/notiPreference/NotiPreference.tsx b/src/pages/academy/notiPreference/NotiPreference.tsx index 72ad1c92d2..821b269f61 100644 --- a/src/pages/academy/notiPreference/NotiPreference.tsx +++ b/src/pages/academy/notiPreference/NotiPreference.tsx @@ -20,7 +20,6 @@ import ContentDisplay from 'src/commons/ContentDisplay'; import { useTypedSelector } from 'src/commons/utils/Hooks'; import BooleanCell from './subcomponents/BooleanCell'; -//import SelectCell from './subcomponents/SelectCell'; import TimeOptionCell from './subcomponents/TimeOptionCell'; const NotiPreference: React.FC = () => { @@ -33,6 +32,12 @@ const NotiPreference: React.FC = () => { const dispatch = useDispatch(); const [hasChanges, setHasChanges] = useState(false); + const [hasChangesNotificationConfig, setHasChangesNotificationConfig] = useState(false); + + const setNotificationConfig = (val: NotificationConfiguration[]) => { + notificationConfig.current = val; + setHasChangesNotificationConfig(true); + }; const configurableNotificationConfigs = React.useRef( session.configurableNotificationConfigs @@ -72,6 +77,17 @@ const NotiPreference: React.FC = () => { setTimeOptionsToDelete(temp); } }; + const setTimeOptions = (index: number, value: TimeOption[]) => { + const temp = [...(notificationConfig.current ?? [])]; + + temp[index] = { + ...temp[index], + timeOptions: value + }; + setNotificationConfig(temp); + gridApi.current?.getDisplayedRowAtIndex(index)?.setDataValue('timeOptions', value); + setHasChangesNotificationConfig(true); + }; const notificationTypeId: ValueFormatterFunc = params => { const id = params.data!.notificationType?.id || 0; @@ -90,17 +106,6 @@ const NotiPreference: React.FC = () => { setHasChanges(true); }; - const setTimeOption = (index: number, value: TimeOption) => { - const temp = [...(configurableNotificationConfigs.current ?? [])]; - - temp[index]['notificationPreference'].timeOptionId = value.id; - - configurableNotificationConfigs.current = temp; - gridApi.current - ?.getDisplayedRowAtIndex(index) - ?.setDataValue('timeOptions', temp[index]['timeOptions']); - setHasChanges(true); - }; const assessmentTypeFormatter: ValueFormatterFunc = params => { return params.data!.assessmentConfig?.type || '-'; @@ -121,8 +126,10 @@ const NotiPreference: React.FC = () => { let result = ''; for (const timeOption of timeOptions) { - result += getUserFriendlyText(timeOption); - result += ' '; + if (timeOption.isDefault) { + result += getUserFriendlyText(timeOption); + result += ' '; + } } return result; @@ -149,40 +156,12 @@ const NotiPreference: React.FC = () => { field: 'timeOptions', valueFormatter: defaultTimeFormatter }, - /* - { - headerName: 'Default Reminder Time(hours)', - field: 'timeOptions', - cellRendererFramework: TimeOptionCell, - cellRendererParams: { - setStateHandler: setTimeOptions, - setDelete: addTimeOptionsToDelete, - field: 'timeOptions', - typeId: notificationTypeId - } - }, - - */ - - /* - 之前的 - { - headerName: 'Reminder', - field: 'timeOptions', - cellRendererFramework: SelectCell, - cellRendererParams: { - setStateHandler: setTimeOption, - field: 'timeOptions' - } - }, - */ - { headerName: 'Reminder', field: 'timeOptions', cellRendererFramework: TimeOptionCell, cellRendererParams: { - setStateHandler: setTimeOption, + setStateHandler: setTimeOptions, setDelete: addTimeOptionsToDelete, field: 'timeOptions', typeId: notificationTypeId @@ -211,39 +190,41 @@ const NotiPreference: React.FC = () => { }; const submitHandler = () => { - //if (!hasChanges) return; + if (hasChanges) { + const preferences: NotificationPreference[] = + configurableNotificationConfigs.current?.map(config => { + return { + ...config.notificationPreference, + notificationConfigId: config.id + }; + }) ?? []; + dispatch(updateNotificationPreferences(preferences, session.courseRegId!)); + setHasChanges(false); + } - const preferences: NotificationPreference[] = - configurableNotificationConfigs.current?.map(config => { - return { - ...config.notificationPreference, - notificationConfigId: config.id - }; - }) ?? []; - dispatch(updateNotificationPreferences(preferences, session.courseRegId!)); - - const allTimeOptions: TimeOption[] = []; - notificationConfig.current?.forEach(curr => { - const timeOptions = curr.timeOptions.map(timeOption => { - return { - ...timeOption, - notificationConfigId: curr.id - }; + if (hasChangesNotificationConfig) { + setHasChangesNotificationConfig(false); + const allTimeOptions: TimeOption[] = []; + notificationConfig.current?.forEach(curr => { + const timeOptions = curr.timeOptions.map(timeOption => { + return { + ...timeOption, + notificationConfigId: curr.id + }; + }); + allTimeOptions.push(...timeOptions); }); - allTimeOptions.push(...timeOptions); - }); - if (allTimeOptions.length > 0) { - dispatch(updateTimeOptions(allTimeOptions)); - } + if (allTimeOptions.length > 0) { + dispatch(updateTimeOptions(allTimeOptions)); + } - if (timeOptionsToDelete.length > 0) { - dispatch(deleteTimeOptions(timeOptionsToDelete.map(timeOption => timeOption.id))); - setTimeOptionsToDelete([]); + if (timeOptionsToDelete.length > 0) { + dispatch(deleteTimeOptions(timeOptionsToDelete.map(timeOption => timeOption.id))); + setTimeOptionsToDelete([]); + } + dispatch(updateNotificationConfigs(notificationConfig.current ?? [])); } - dispatch(updateNotificationConfigs(notificationConfig.current ?? [])); - - //setHasChanges(false); }; const data = ( @@ -267,8 +248,8 @@ const NotiPreference: React.FC = () => {