From 33f370bbeabe8fda8820418e9435b8980f2d82cc Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:57:23 +0800 Subject: [PATCH 1/7] Fix Ground Control table resize The mutable state should have been migrated to use `useRef` when the component was transitioned to a functional component. --- src/pages/academy/groundControl/GroundControl.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 4a5e2ccd0b..7a7b0370bc 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -13,7 +13,7 @@ import { import { IconNames } from '@blueprintjs/icons'; import { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useSession } from 'src/commons/utils/Hooks'; import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; @@ -47,14 +47,14 @@ const GroundControl: React.FC = props => { const [showDropzone, setShowDropzone] = useState(false); const { assessmentOverviews, assessmentConfigurations } = useSession(); - let gridApi: GridApi | undefined; + const gridApi = useRef(); const onGridReady = (params: GridReadyEvent) => { - gridApi = params.api; - gridApi.sizeColumnsToFit(); + gridApi.current = params.api; + params.api.sizeColumnsToFit(); // Sort assessments by opening date, breaking ties by later of closing dates - gridApi.applyColumnState({ + params.api.applyColumnState({ state: [ { colId: 'openAt', sort: 'desc' }, { colId: 'closeAt', sort: 'desc' } @@ -63,9 +63,7 @@ const GroundControl: React.FC = props => { }; const resizeGrid = () => { - if (gridApi) { - gridApi.sizeColumnsToFit(); - } + gridApi.current?.sizeColumnsToFit() }; const toggleDropzone = () => { From 32257cd4012fb2821ffe5d332d192cd2964482e8 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 27 Mar 2024 01:04:21 +0800 Subject: [PATCH 2/7] Fix format --- src/pages/academy/groundControl/GroundControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 7a7b0370bc..409868427b 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -63,7 +63,7 @@ const GroundControl: React.FC = props => { }; const resizeGrid = () => { - gridApi.current?.sizeColumnsToFit() + gridApi.current?.sizeColumnsToFit(); }; const toggleDropzone = () => { From 15bb410b9b8c5a1ffbd23c1bb00bfac2917bbbef Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:13:12 +0800 Subject: [PATCH 3/7] Memoize GroundControl controls --- .../academy/groundControl/GroundControl.tsx | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 409868427b..40cbf5a6c5 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -13,7 +13,7 @@ import { import { IconNames } from '@blueprintjs/icons'; import { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; -import React, { useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { useSession } from 'src/commons/utils/Hooks'; import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; @@ -66,10 +66,6 @@ const GroundControl: React.FC = props => { gridApi.current?.sizeColumnsToFit(); }; - const toggleDropzone = () => { - setShowDropzone(!showDropzone); - }; - const columnDefs: ColDef[] = [ { field: 'number', @@ -179,21 +175,24 @@ const GroundControl: React.FC = props => { sortable: true }; - const controls = ( -
- - - -
+ const controls = useMemo( + () => ( +
+ + + +
+ ), + [props.handleAssessmentOverviewFetch, showDropzone] ); const dropzone = ( From 04a4dce1c9d04a5b32712cee940bf27837b54fa2 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:19:41 +0800 Subject: [PATCH 4/7] Migrate GroundControl to `useDispatch` --- src/pages/academy/Academy.tsx | 2 +- .../academy/groundControl/GroundControl.tsx | 86 ++++++++++++------- .../groundControl/GroundControlContainer.ts | 38 -------- 3 files changed, 58 insertions(+), 68 deletions(-) delete mode 100644 src/pages/academy/groundControl/GroundControlContainer.ts diff --git a/src/pages/academy/Academy.tsx b/src/pages/academy/Academy.tsx index 4dc179621b..3b1c8fecac 100644 --- a/src/pages/academy/Academy.tsx +++ b/src/pages/academy/Academy.tsx @@ -31,7 +31,7 @@ import Dashboard from './dashboard/Dashboard'; import Game from './game/Game'; import GameSimulator from './gameSimulator/GameSimulator'; import Grading from './grading/Grading'; -import GroundControl from './groundControl/GroundControlContainer'; +import GroundControl from './groundControl/GroundControl'; import NotiPreference from './notiPreference/NotiPreference'; import Sourcereel from './sourcereel/Sourcereel'; import TeamFormationForm from './teamFormation/subcomponents/TeamFormationForm'; diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 40cbf5a6c5..d136228e4d 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -14,10 +14,23 @@ import { IconNames } from '@blueprintjs/icons'; import { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; import React, { useMemo, useRef, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { useSession } from 'src/commons/utils/Hooks'; +import { + fetchAssessmentOverviews, + fetchCourseConfig +} from '../../../commons/application/actions/SessionActions'; import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; import ContentDisplay from '../../../commons/ContentDisplay'; +import { + changeDateAssessment, + changeTeamSizeAssessment, + configureAssessment, + deleteAssessment, + publishAssessment, + uploadAssessment +} from '../../../features/groundControl/GroundControlActions'; import DefaultChapterSelect from './subcomponents/DefaultChapterSelect'; import ConfigureCell from './subcomponents/GroundControlConfigureCell'; import DeleteCell from './subcomponents/GroundControlDeleteCell'; @@ -26,27 +39,42 @@ import EditCell from './subcomponents/GroundControlEditCell'; import EditTeamSizeCell from './subcomponents/GroundControlEditTeamSizeCell'; import PublishCell from './subcomponents/GroundControlPublishCell'; -type Props = DispatchProps; - -export type DispatchProps = { - handleAssessmentOverviewFetch: () => void; - handleDeleteAssessment: (id: number) => void; - handleUploadAssessment: (file: File, forceUpdate: boolean, assessmentConfigId: number) => void; - handlePublishAssessment: (togglePublishTo: boolean, id: number) => void; - handleAssessmentChangeDate: (id: number, openAt: string, closeAt: string) => void; - handleAssessmentChangeTeamSize: (id: number, maxTeamSize: number) => void; - handleConfigureAssessment: ( - id: number, - hasVotingFeatures: boolean, - hasTokenCounter: boolean - ) => void; - handleFetchCourseConfigs: () => void; -}; - -const GroundControl: React.FC = props => { +const GroundControl: React.FC = () => { const [showDropzone, setShowDropzone] = useState(false); const { assessmentOverviews, assessmentConfigurations } = useSession(); + const dispatch = useDispatch(); + const { + handleAssessmentChangeDate, + handleAssessmentChangeTeamSize, + handleAssessmentOverviewFetch, + handleConfigureAssessment, + handleDeleteAssessment, + handleFetchCourseConfigs, + handlePublishAssessment, + handleUploadAssessment + } = useMemo( + () => ({ + handleAssessmentChangeDate: (id: number, openAt: string, closeAt: string) => + dispatch(changeDateAssessment(id, openAt, closeAt)), + handleAssessmentChangeTeamSize: (id: number, maxTeamSize: number) => + dispatch(changeTeamSizeAssessment(id, maxTeamSize)), + handleAssessmentOverviewFetch: () => dispatch(fetchAssessmentOverviews()), + handleConfigureAssessment: ( + id: number, + hasVotingFeatures: boolean, + hasTokenCounter: boolean + ) => dispatch(configureAssessment(id, hasVotingFeatures, hasTokenCounter)), + handleDeleteAssessment: (id: number) => dispatch(deleteAssessment(id)), + handleFetchCourseConfigs: () => dispatch(fetchCourseConfig()), + handlePublishAssessment: (togglePublishTo: boolean, id: number) => + dispatch(publishAssessment(togglePublishTo, id)), + handleUploadAssessment: (file: File, forceUpdate: boolean, assessmentConfigId: number) => + dispatch(uploadAssessment(file, forceUpdate, assessmentConfigId)) + }), + [dispatch] + ); + const gridApi = useRef(); const onGridReady = (params: GridReadyEvent) => { @@ -92,7 +120,7 @@ const GroundControl: React.FC = props => { sortingOrder: ['desc', 'asc', null], cellRenderer: EditCell, cellRendererParams: { - handleAssessmentChangeDate: props.handleAssessmentChangeDate, + handleAssessmentChangeDate: handleAssessmentChangeDate, forOpenDate: true }, width: 150 @@ -108,7 +136,7 @@ const GroundControl: React.FC = props => { sortingOrder: ['desc', 'asc', null], cellRenderer: EditCell, cellRendererParams: { - handleAssessmentChangeDate: props.handleAssessmentChangeDate, + handleAssessmentChangeDate: handleAssessmentChangeDate, forOpenDate: false }, width: 150 @@ -118,7 +146,7 @@ const GroundControl: React.FC = props => { field: 'maxTeamSize', cellRenderer: EditTeamSizeCell, cellRendererParams: { - onTeamSizeChange: props.handleAssessmentChangeTeamSize + onTeamSizeChange: handleAssessmentChangeTeamSize }, width: 100 }, @@ -127,7 +155,7 @@ const GroundControl: React.FC = props => { field: 'placeholderPublish' as any, cellRenderer: PublishCell, cellRendererParams: { - handlePublishAssessment: props.handlePublishAssessment + handlePublishAssessment: handlePublishAssessment }, width: 80, filter: false, @@ -142,7 +170,7 @@ const GroundControl: React.FC = props => { field: 'placeholderDelete' as any, cellRenderer: DeleteCell, cellRendererParams: { - handleDeleteAssessment: props.handleDeleteAssessment + handleDeleteAssessment: handleDeleteAssessment }, width: 80, filter: false, @@ -157,7 +185,7 @@ const GroundControl: React.FC = props => { field: 'placeholderConfigure' as any, cellRenderer: ConfigureCell, cellRendererParams: { - handleConfigureAssessment: props.handleConfigureAssessment + handleConfigureAssessment: handleConfigureAssessment }, width: 80, filter: false, @@ -187,18 +215,18 @@ const GroundControl: React.FC = props => { Upload assessment - ), - [props.handleAssessmentOverviewFetch, showDropzone] + [handleAssessmentOverviewFetch, showDropzone] ); const dropzone = ( @@ -241,8 +269,8 @@ const GroundControl: React.FC = props => { const loadContent = () => { // Always load AssessmentOverviews and CourseConfigs to get the latest values (just in case) - props.handleAssessmentOverviewFetch(); - props.handleFetchCourseConfigs(); + handleAssessmentOverviewFetch(); + handleFetchCourseConfigs(); }; return ( diff --git a/src/pages/academy/groundControl/GroundControlContainer.ts b/src/pages/academy/groundControl/GroundControlContainer.ts deleted file mode 100644 index 1eec25b7b6..0000000000 --- a/src/pages/academy/groundControl/GroundControlContainer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import { - fetchAssessmentOverviews, - fetchCourseConfig -} from '../../../commons/application/actions/SessionActions'; -import { OverallState } from '../../../commons/application/ApplicationTypes'; -import { - changeDateAssessment, - changeTeamSizeAssessment, - configureAssessment, - deleteAssessment, - publishAssessment, - uploadAssessment -} from '../../../features/groundControl/GroundControlActions'; -import GroundControl, { DispatchProps } from './GroundControl'; - -const mapStateToProps: MapStateToProps<{}, {}, OverallState> = state => ({}); - -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - handleAssessmentChangeDate: changeDateAssessment, - handleAssessmentChangeTeamSize: changeTeamSizeAssessment, - handleAssessmentOverviewFetch: fetchAssessmentOverviews, - handleDeleteAssessment: deleteAssessment, - handleUploadAssessment: uploadAssessment, - handlePublishAssessment: publishAssessment, - handleFetchCourseConfigs: fetchCourseConfig, - handleConfigureAssessment: configureAssessment - }, - dispatch - ); - -const GroundControlContainer = connect(mapStateToProps, mapDispatchToProps)(GroundControl); - -export default GroundControlContainer; From a87db3e29fc383dcaf714beae9e747743e5c68db Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:52:12 +0800 Subject: [PATCH 5/7] Combine GroundControl actions together --- .../academy/groundControl/GroundControl.tsx | 31 ++++++------------- .../GroundControlConfigureCell.tsx | 8 +++-- .../subcomponents/GroundControlDeleteCell.tsx | 6 ++-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index d136228e4d..35f35970c2 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -166,28 +166,17 @@ const GroundControl: React.FC = () => { } }, { - headerName: 'Delete', - field: 'placeholderDelete' as any, - cellRenderer: DeleteCell, - cellRendererParams: { - handleDeleteAssessment: handleDeleteAssessment + headerName: 'Actions', + field: 'placeholderActions' as any, + cellRenderer: (data: AssessmentOverview) => { + return ( + <> + + + + ); }, - width: 80, - filter: false, - resizable: false, - sortable: false, - cellStyle: { - padding: 0 - } - }, - { - headerName: 'Configure', - field: 'placeholderConfigure' as any, - cellRenderer: ConfigureCell, - cellRendererParams: { - handleConfigureAssessment: handleConfigureAssessment - }, - width: 80, + width: 100, filter: false, resizable: false, sortable: false, diff --git a/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx b/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx index e8a300ab2e..ba407993f1 100644 --- a/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx +++ b/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx @@ -6,7 +6,9 @@ import { Divider, Intent, NumericInput, - Switch + Position, + Switch, + Tooltip } from '@blueprintjs/core'; import { IconNames, Team } from '@blueprintjs/icons'; import React, { useCallback, useState } from 'react'; @@ -44,7 +46,9 @@ const ConfigureCell: React.FC = ({ handleConfigureAssessment, data }) => return ( <> - + + + = ({ handleDeleteAssessment, data }) => { return ( <> - + + + Date: Sat, 13 Apr 2024 21:17:42 +0800 Subject: [PATCH 6/7] Fix conflicts and combine changes --- .../academy/groundControl/GroundControl.tsx | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index b54cd1ff12..63247d4e73 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -24,14 +24,14 @@ import { import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; import ContentDisplay from '../../../commons/ContentDisplay'; import { - // assignEntriesForVoting, + assignEntriesForVoting, changeDateAssessment, changeTeamSizeAssessment, configureAssessment, deleteAssessment, publishAssessment, - // publishGradingAll, - // unpublishGradingAll, + publishGradingAll, + unpublishGradingAll, uploadAssessment } from '../../../features/groundControl/GroundControlActions'; import DefaultChapterSelect from './subcomponents/DefaultChapterSelect'; @@ -52,10 +52,13 @@ const GroundControl: React.FC = () => { handleAssessmentChangeDate, handleAssessmentChangeTeamSize, handleAssessmentOverviewFetch, + handleAssignEntriesForVoting, handleConfigureAssessment, handleDeleteAssessment, handleFetchCourseConfigs, handlePublishAssessment, + handlePublishGradingAll, + handleUnpublishGradingAll, handleUploadAssessment } = useMemo( () => ({ @@ -64,6 +67,7 @@ const GroundControl: React.FC = () => { handleAssessmentChangeTeamSize: (id: number, maxTeamSize: number) => dispatch(changeTeamSizeAssessment(id, maxTeamSize)), handleAssessmentOverviewFetch: () => dispatch(fetchAssessmentOverviews()), + handleAssignEntriesForVoting: (id: number) => dispatch(assignEntriesForVoting(id)), handleConfigureAssessment: ( id: number, hasVotingFeatures: boolean, @@ -73,9 +77,8 @@ const GroundControl: React.FC = () => { handleFetchCourseConfigs: () => dispatch(fetchCourseConfig()), handlePublishAssessment: (togglePublishTo: boolean, id: number) => dispatch(publishAssessment(togglePublishTo, id)), - // handlePublishGradingAll: (id: number) => void; - // handleUnpublishGradingAll: (id: number) => void; - // handleAssignEntriesForVoting: (id: number) => void; + handlePublishGradingAll: (id: number) => dispatch(publishGradingAll(id)), + handleUnpublishGradingAll: (id: number) => dispatch(unpublishGradingAll(id)), handleUploadAssessment: (file: File, forceUpdate: boolean, assessmentConfigId: number) => dispatch(uploadAssessment(file, forceUpdate, assessmentConfigId)) }), @@ -172,22 +175,22 @@ const GroundControl: React.FC = () => { padding: 0 } }, - // { - // headerName: 'Release Grading', - // field: 'placeholderReleaseGrading' as any, - // cellRenderer: ReleaseGradingCell, - // cellRendererParams: { - // handlePublishGradingAll: props.handlePublishGradingAll, - // handleUnpublishGradingAll: props.handleUnpublishGradingAll - // }, - // width: 120, - // filter: false, - // resizable: false, - // sortable: false, - // cellStyle: { - // padding: 0 - // } - // }, + { + headerName: 'Release Grading', + field: 'placeholderReleaseGrading' as any, + cellRenderer: ReleaseGradingCell, + cellRendererParams: { + handlePublishGradingAll: handlePublishGradingAll, + handleUnpublishGradingAll: handleUnpublishGradingAll + }, + width: 120, + filter: false, + resizable: false, + sortable: false, + cellStyle: { + padding: 0 + } + }, { headerName: 'Actions', field: 'placeholderActions' as any, @@ -195,9 +198,11 @@ const GroundControl: React.FC = () => { return ( <> - + ); }, From fa593c5264999bbda7503199f26f5f8380920242 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Tue, 7 May 2024 01:58:42 +0800 Subject: [PATCH 7/7] Migrate changes post-merge --- src/pages/academy/academyRoutes.tsx | 2 +- src/pages/academy/groundControl/GroundControl.tsx | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pages/academy/academyRoutes.tsx b/src/pages/academy/academyRoutes.tsx index c74c9111b0..fa79979cd9 100644 --- a/src/pages/academy/academyRoutes.tsx +++ b/src/pages/academy/academyRoutes.tsx @@ -79,7 +79,7 @@ const getCommonAcademyRoutes = (): RouteObject[] => { ]; }; -const GroundControl = () => import('./groundControl/GroundControlContainer'); +const GroundControl = () => import('./groundControl/GroundControl'); const Grading = () => import('./grading/Grading'); const Sourcereel = () => import('./sourcereel/Sourcereel'); const GameSimulator = () => import('./gameSimulator/GameSimulator'); diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 63247d4e73..cc91f27fbb 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -17,10 +17,7 @@ import React, { useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useSession } from 'src/commons/utils/Hooks'; -import { - fetchAssessmentOverviews, - fetchCourseConfig -} from '../../../commons/application/actions/SessionActions'; +import SessionActions from '../../../commons/application/actions/SessionActions'; import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; import ContentDisplay from '../../../commons/ContentDisplay'; import { @@ -66,7 +63,7 @@ const GroundControl: React.FC = () => { dispatch(changeDateAssessment(id, openAt, closeAt)), handleAssessmentChangeTeamSize: (id: number, maxTeamSize: number) => dispatch(changeTeamSizeAssessment(id, maxTeamSize)), - handleAssessmentOverviewFetch: () => dispatch(fetchAssessmentOverviews()), + handleAssessmentOverviewFetch: () => dispatch(SessionActions.fetchAssessmentOverviews()), handleAssignEntriesForVoting: (id: number) => dispatch(assignEntriesForVoting(id)), handleConfigureAssessment: ( id: number, @@ -74,7 +71,7 @@ const GroundControl: React.FC = () => { hasTokenCounter: boolean ) => dispatch(configureAssessment(id, hasVotingFeatures, hasTokenCounter)), handleDeleteAssessment: (id: number) => dispatch(deleteAssessment(id)), - handleFetchCourseConfigs: () => dispatch(fetchCourseConfig()), + handleFetchCourseConfigs: () => dispatch(SessionActions.fetchCourseConfig()), handlePublishAssessment: (togglePublishTo: boolean, id: number) => dispatch(publishAssessment(togglePublishTo, id)), handlePublishGradingAll: (id: number) => dispatch(publishGradingAll(id)), @@ -308,4 +305,9 @@ const dateFilterComparator = (filterDate: Date, cellValue: string) => { return cellDate < filterDate ? -1 : cellDate > filterDate ? 1 : 0; }; +// react-router lazy loading +// https://reactrouter.com/en/main/route/lazy +export const Component = GroundControl; +Component.displayName = 'GroundControl'; + export default GroundControl;