diff --git a/src/commons/XMLParser/XMLParserHelper.ts b/src/commons/XMLParser/XMLParserHelper.ts index eeca016af3..1321abb9bd 100644 --- a/src/commons/XMLParser/XMLParserHelper.ts +++ b/src/commons/XMLParser/XMLParserHelper.ts @@ -85,7 +85,8 @@ const makeAssessmentOverview = (result: any, maxXpVal: number): AssessmentOvervi story: rawOverview.story, xp: 0, gradingStatus: 'none' as GradingStatuses, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }; }; diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index 6cf331c79f..e3c8f99a2b 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -335,6 +335,7 @@ test('setAssessmentConfigurations generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -344,6 +345,7 @@ test('setAssessmentConfigurations generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -353,6 +355,7 @@ test('setAssessmentConfigurations generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } @@ -541,7 +544,8 @@ test('updateAssessmentOverviews generates correct action object', () => { story: null, xp: 0, gradingStatus: 'none', - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false } ]; const action = updateAssessmentOverviews(overviews); @@ -769,6 +773,7 @@ test('updateAssessmentTypes generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -778,6 +783,7 @@ test('updateAssessmentTypes generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -787,6 +793,7 @@ test('updateAssessmentTypes generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -796,6 +803,7 @@ test('updateAssessmentTypes generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -805,6 +813,7 @@ test('updateAssessmentTypes generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } @@ -823,6 +832,7 @@ test('deleteAssessmentConfig generates correct action object', () => { isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }; diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index ac4ddb4e81..e5a9007c93 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -159,7 +159,8 @@ test('SET_ASSESSMENT_CONFIGURATIONS works correctly', () => { earlySubmissionXp: 200, isManuallyGraded: false, displayInDashboard: true, - hasTokenCounter: false + hasTokenCounter: false, + hasVotingFeatures: false }, { assessmentConfigId: 1, @@ -171,7 +172,8 @@ test('SET_ASSESSMENT_CONFIGURATIONS works correctly', () => { earlySubmissionXp: 200, isManuallyGraded: false, displayInDashboard: true, - hasTokenCounter: false + hasTokenCounter: false, + hasVotingFeatures: false }, { assessmentConfigId: 1, @@ -183,7 +185,8 @@ test('SET_ASSESSMENT_CONFIGURATIONS works correctly', () => { earlySubmissionXp: 200, isManuallyGraded: false, displayInDashboard: true, - hasTokenCounter: false + hasTokenCounter: false, + hasVotingFeatures: false } ]; @@ -339,7 +342,8 @@ const assessmentOverviewsTest1: AssessmentOverview[] = [ story: null, xp: 0, gradingStatus: GradingStatuses.none, - maxTeamSize: 5 + maxTeamSize: 5, + hasVotingFeatures: false } ]; @@ -359,7 +363,8 @@ const assessmentOverviewsTest2: AssessmentOverview[] = [ story: null, xp: 1, gradingStatus: GradingStatuses.grading, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false } ]; diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 49e8777338..2fdfc1675b 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -62,6 +62,8 @@ export type AssessmentOverview = { gradingStatus: GradingStatus; id: number; isPublished?: boolean; + hasVotingFeatures: boolean; + hasTokenCounter?: boolean; maxXp: number; number?: string; // For mission control openAt: string; @@ -98,6 +100,7 @@ export type AssessmentConfiguration = { hoursBeforeEarlyXpDecay: number; earlySubmissionXp: number; hasTokenCounter: boolean; + hasVotingFeatures: boolean; }; export interface IProgrammingQuestion extends BaseQuestion { @@ -246,7 +249,8 @@ export const overviewTemplate = (): AssessmentOverview => { story: 'mission', xp: 0, gradingStatus: 'none', - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }; }; diff --git a/src/commons/assessment/__tests__/Assessment.tsx b/src/commons/assessment/__tests__/Assessment.tsx index bb7aee31c9..1863ffe91e 100644 --- a/src/commons/assessment/__tests__/Assessment.tsx +++ b/src/commons/assessment/__tests__/Assessment.tsx @@ -18,6 +18,7 @@ const mockAssessmentProps = assertType()({ isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index aa8abec360..268ff781dd 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -274,16 +274,12 @@ const AssessmentWorkspace: React.FC = props => { * Handles toggling enabling and disabling token counter depending on assessment properties */ useEffect(() => { - if (props.assessmentConfiguration.hasTokenCounter) { + if (assessment?.hasTokenCounter) { handleEnableTokenCounter(); } else { handleDisableTokenCounter(); } - }, [ - props.assessmentConfiguration.hasTokenCounter, - handleEnableTokenCounter, - handleDisableTokenCounter - ]); + }, [assessment?.hasTokenCounter, handleEnableTokenCounter, handleDisableTokenCounter]); /** * Handles toggling of relevant SideContentTabs when mobile breakpoint it hit diff --git a/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx index 6ffcf33954..34206febe5 100644 --- a/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx @@ -27,6 +27,7 @@ const defaultProps = assertType()({ isManuallyGraded: true, displayInDashboard: true, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, diff --git a/src/commons/mocks/AssessmentMocks.ts b/src/commons/mocks/AssessmentMocks.ts index bc2ef9850a..953c21124a 100644 --- a/src/commons/mocks/AssessmentMocks.ts +++ b/src/commons/mocks/AssessmentMocks.ts @@ -23,6 +23,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -32,6 +33,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -41,6 +43,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -49,7 +52,8 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, - hasTokenCounter: true, + hasTokenCounter: false, + hasVotingFeatures: true, earlySubmissionXp: 200 }, { @@ -59,6 +63,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 } ], @@ -70,6 +75,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -79,6 +85,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -88,6 +95,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 } ] @@ -109,7 +117,8 @@ const mockUnopenedAssessmentsOverviews: AssessmentOverview[] = [ story: 'mission-1', xp: 0, gradingStatus: GradingStatuses.none, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false } ]; @@ -141,7 +150,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ story: 'mission-1', xp: 1, gradingStatus: GradingStatuses.none, - maxTeamSize: 4 + maxTeamSize: 4, + hasVotingFeatures: false }, { type: 'Missions', @@ -158,7 +168,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ story: 'mission-2', xp: 2, gradingStatus: GradingStatuses.none, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }, { type: 'Quests', @@ -175,7 +186,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ story: 'sidequest-2.1', xp: 3, gradingStatus: GradingStatuses.none, - maxTeamSize: 2 + maxTeamSize: 2, + hasVotingFeatures: false }, { type: 'Paths', @@ -192,7 +204,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ story: null, xp: 0, gradingStatus: GradingStatuses.excluded, - maxTeamSize: 2 + maxTeamSize: 2, + hasVotingFeatures: false }, { type: 'Others', @@ -210,7 +223,8 @@ const mockOpenedAssessmentsOverviews: AssessmentOverview[] = [ xp: 3, gradingStatus: GradingStatuses.none, private: true, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false } ]; @@ -230,7 +244,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ story: 'mission-3', xp: 800, gradingStatus: GradingStatuses.grading, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }, { type: 'Quests', @@ -247,7 +262,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ story: null, xp: 500, gradingStatus: GradingStatuses.none, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }, { type: 'Quests', @@ -264,7 +280,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ story: null, xp: 150, gradingStatus: GradingStatuses.graded, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false }, { type: 'Quests', @@ -281,7 +298,8 @@ const mockClosedAssessmentOverviews: AssessmentOverview[] = [ story: null, xp: 100, gradingStatus: GradingStatuses.excluded, - maxTeamSize: 1 + maxTeamSize: 1, + hasVotingFeatures: false } ]; diff --git a/src/commons/profile/__tests__/Profile.tsx b/src/commons/profile/__tests__/Profile.tsx index 55583cb291..d12e179e09 100644 --- a/src/commons/profile/__tests__/Profile.tsx +++ b/src/commons/profile/__tests__/Profile.tsx @@ -26,6 +26,7 @@ const assessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: false, displayInDashboard: false, hasTokenCounter: false, + hasVotingFeatures: false, hoursBeforeEarlyXpDecay: 0, earlySubmissionXp: 0 })); diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index 47021c4e79..924a968011 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -18,6 +18,7 @@ import { import { CHANGE_DATE_ASSESSMENT, CHANGE_TEAM_SIZE_ASSESSMENT, + CONFIGURE_ASSESSMENT, DELETE_ASSESSMENT, PUBLISH_ASSESSMENT, UPLOAD_ASSESSMENT @@ -1160,6 +1161,7 @@ function* BackendSaga(): SagaIterator { displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 0 } ]; @@ -1361,6 +1363,28 @@ function* BackendSaga(): SagaIterator { yield put(actions.fetchAssessmentOverviews()); } ); + + yield takeEvery( + CONFIGURE_ASSESSMENT, + function* (action: ReturnType): any { + const tokens: Tokens = yield selectTokens(); + const id = action.payload.id; + const hasVotingFeatures = action.payload.hasVotingFeatures; + const hasTokenCounter = action.payload.hasTokenCounter; + + const resp: Response | null = yield updateAssessment( + id, + { hasVotingFeatures: hasVotingFeatures, hasTokenCounter: hasTokenCounter }, + tokens + ); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); + } + + yield put(actions.fetchAssessmentOverviews()); + yield call(showSuccessMessage, 'Updated successfully!', 1000); + } + ); } function* handleReautogradeResponse(resp: Response | null): any { diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index 189ff86468..8654b249db 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -1133,7 +1133,14 @@ export const deleteSourcecastEntry = async ( */ export const updateAssessment = async ( id: number, - body: { openAt?: string; closeAt?: string; isPublished?: boolean; maxTeamSize?: number }, + body: { + openAt?: string; + closeAt?: string; + isPublished?: boolean; + maxTeamSize?: number; + hasTokenCounter?: boolean; + hasVotingFeatures?: boolean; + }, tokens: Tokens ): Promise => { const resp = await request(`${courseId()}/admin/assessments/${id}`, 'POST', { diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 145b4f87f5..add54c78f8 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -231,6 +231,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -240,6 +241,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -249,6 +251,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 }, { @@ -257,7 +260,8 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: false, displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, - hasTokenCounter: true, + hasTokenCounter: false, + hasVotingFeatures: true, earlySubmissionXp: 200 }, { @@ -267,6 +271,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 200 } ]; @@ -1068,6 +1073,7 @@ describe('Test CREATE_COURSE action', () => { displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 0 } ]; diff --git a/src/features/groundControl/GroundControlActions.ts b/src/features/groundControl/GroundControlActions.ts index b4dd6076e7..24c11f8966 100644 --- a/src/features/groundControl/GroundControlActions.ts +++ b/src/features/groundControl/GroundControlActions.ts @@ -3,6 +3,7 @@ import { createAction } from '@reduxjs/toolkit'; import { CHANGE_DATE_ASSESSMENT, CHANGE_TEAM_SIZE_ASSESSMENT, + CONFIGURE_ASSESSMENT, DELETE_ASSESSMENT, PUBLISH_ASSESSMENT, UPLOAD_ASSESSMENT @@ -31,3 +32,10 @@ export const uploadAssessment = createAction( payload: { file, forceUpdate, assessmentConfigId } }) ); + +export const configureAssessment = createAction( + CONFIGURE_ASSESSMENT, + (id: number, hasVotingFeatures: boolean, hasTokenCounter: boolean) => ({ + payload: { id, hasVotingFeatures, hasTokenCounter } + }) +); diff --git a/src/features/groundControl/GroundControlTypes.ts b/src/features/groundControl/GroundControlTypes.ts index 6881b4e036..fe33589af5 100644 --- a/src/features/groundControl/GroundControlTypes.ts +++ b/src/features/groundControl/GroundControlTypes.ts @@ -3,3 +3,4 @@ export const CHANGE_TEAM_SIZE_ASSESSMENT = 'CHANGE_TEAM_SIZE_ASSESSMENT'; export const DELETE_ASSESSMENT = 'DELETE_ASSESSMENT'; export const PUBLISH_ASSESSMENT = 'PUBLISH_ASSESSMENT'; export const UPLOAD_ASSESSMENT = 'UPLOAD_ASSESSMENT'; +export const CONFIGURE_ASSESSMENT = 'CONFIGURE_ASSESSMENT'; diff --git a/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx index 0871fb53af..3d10e8818b 100644 --- a/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx @@ -61,6 +61,16 @@ const AssessmentConfigPanel: React.FC = ({ gridApi.current?.getDisplayedRowAtIndex(index)?.setDataValue('hasTokenCounter', value); }; + const setHasVotingFeatures = (index: number, value: boolean) => { + const temp = [...assessmentConfig.current]; + temp[index] = { + ...temp[index], + hasVotingFeatures: value + }; + setAssessmentConfig(temp); + gridApi.current?.getDisplayedRowAtIndex(index)?.setDataValue('hasVotingFeatures', value); + }; + const setEarlyXp = (index: number, value: number) => { const temp = [...assessmentConfig.current]; temp[index] = { @@ -95,6 +105,7 @@ const AssessmentConfigPanel: React.FC = ({ displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, hasTokenCounter: false, + hasVotingFeatures: false, earlySubmissionXp: 0 }); setAssessmentConfig(temp); @@ -140,7 +151,16 @@ const AssessmentConfigPanel: React.FC = ({ } }, { - headerName: 'Has Token Counter', + headerName: 'Voting Features*', + field: 'hasVotingFeatures', + cellRenderer: BooleanCell, + cellRendererParams: { + setStateHandler: setHasVotingFeatures, + field: 'hasVotingFeatures' + } + }, + { + headerName: 'Token Counter*', field: 'hasTokenCounter', cellRenderer: BooleanCell, cellRendererParams: { @@ -248,6 +268,11 @@ const AssessmentConfigPanel: React.FC = ({ onRowDragEnd={onRowDragLeaveOrEnd} onCellValueChanged={onCellValueChanged} /> +
+ *If you create an assessment with these toggles enabled, they will be activated within the + assessment by default. However, you can also visit ground control to manually + override these settings if needed. +
); diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index e740c88168..4a5e2ccd0b 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -19,6 +19,7 @@ import { useSession } from 'src/commons/utils/Hooks'; import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; import ContentDisplay from '../../../commons/ContentDisplay'; import DefaultChapterSelect from './subcomponents/DefaultChapterSelect'; +import ConfigureCell from './subcomponents/GroundControlConfigureCell'; import DeleteCell from './subcomponents/GroundControlDeleteCell'; import Dropzone from './subcomponents/GroundControlDropzone'; import EditCell from './subcomponents/GroundControlEditCell'; @@ -34,6 +35,11 @@ export type DispatchProps = { 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; }; @@ -129,7 +135,7 @@ const GroundControl: React.FC = props => { cellRendererParams: { handlePublishAssessment: props.handlePublishAssessment }, - width: 100, + width: 80, filter: false, resizable: false, sortable: false, @@ -144,7 +150,22 @@ const GroundControl: React.FC = props => { cellRendererParams: { handleDeleteAssessment: props.handleDeleteAssessment }, - width: 100, + width: 80, + filter: false, + resizable: false, + sortable: false, + cellStyle: { + padding: 0 + } + }, + { + headerName: 'Configure', + field: 'placeholderConfigure' as any, + cellRenderer: ConfigureCell, + cellRendererParams: { + handleConfigureAssessment: props.handleConfigureAssessment + }, + width: 80, filter: false, resizable: false, sortable: false, diff --git a/src/pages/academy/groundControl/GroundControlContainer.ts b/src/pages/academy/groundControl/GroundControlContainer.ts index 14cb19a8a1..1eec25b7b6 100644 --- a/src/pages/academy/groundControl/GroundControlContainer.ts +++ b/src/pages/academy/groundControl/GroundControlContainer.ts @@ -9,6 +9,7 @@ import { OverallState } from '../../../commons/application/ApplicationTypes'; import { changeDateAssessment, changeTeamSizeAssessment, + configureAssessment, deleteAssessment, publishAssessment, uploadAssessment @@ -26,7 +27,8 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleDeleteAssessment: deleteAssessment, handleUploadAssessment: uploadAssessment, handlePublishAssessment: publishAssessment, - handleFetchCourseConfigs: fetchCourseConfig + handleFetchCourseConfigs: fetchCourseConfig, + handleConfigureAssessment: configureAssessment }, dispatch ); diff --git a/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx b/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx new file mode 100644 index 0000000000..e8a300ab2e --- /dev/null +++ b/src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx @@ -0,0 +1,145 @@ +import { + Collapse, + Dialog, + DialogBody, + DialogFooter, + Divider, + Intent, + NumericInput, + Switch +} from '@blueprintjs/core'; +import { IconNames, Team } from '@blueprintjs/icons'; +import React, { useCallback, useState } from 'react'; + +import { AssessmentOverview } from '../../../../commons/assessment/AssessmentTypes'; +import ControlButton from '../../../../commons/ControlButton'; + +type Props = { + handleConfigureAssessment: ( + id: number, + hasVotingFeatures: boolean, + hasTokenCounter: boolean + ) => void; + data: AssessmentOverview; +}; + +const ConfigureCell: React.FC = ({ handleConfigureAssessment, data }) => { + const [isDialogOpen, setDialogState] = useState(false); + const [hasVotingFeatures, setHasVotingFeatures] = useState(!!data.hasVotingFeatures); + const [hasTokenCounter, setHasTokenCounter] = useState(!!data.hasTokenCounter); + const [isTeamAssessment, setIsTeamAssessment] = useState(false); + + const handleOpenDialog = useCallback(() => setDialogState(true), []); + const handleCloseDialog = useCallback(() => setDialogState(false), []); + + const handleConfigure = useCallback(() => { + const { id } = data; + handleConfigureAssessment(id, hasVotingFeatures, hasTokenCounter); + handleCloseDialog(); + }, [data, handleCloseDialog, handleConfigureAssessment, hasTokenCounter, hasVotingFeatures]); + + const toggleHasTokenCounter = useCallback(() => setHasTokenCounter(prev => !prev), []); + const toggleVotingFeatures = useCallback(() => setHasVotingFeatures(prev => !prev), []); + const toggleIsTeamAssessment = useCallback(() => setIsTeamAssessment(prev => !prev), []); + + return ( + <> + + + +

+ This configuration tool allows you to fine-tune this assessment. Any changes made + here will override any assessment configurations in the admin panel. +

+
+

+ General Configurations +

+ + +
+
+

+ Team-Related Configurations +

+ + + +
+ +

Max team size

+ +
+
+
+
+

+ Voting-Related Configurations +

+ + + +
+
+ +
+
+ +
+ +
+
+
+
+ + } + > +
+ + ); +}; + +export default ConfigureCell; diff --git a/src/styles/_academy.scss b/src/styles/_academy.scss index 6058cc9477..985afb1fba 100644 --- a/src/styles/_academy.scss +++ b/src/styles/_academy.scss @@ -438,6 +438,11 @@ right: 0; } } + + .footer-text { + margin-top: 12px; + text-align: left; + } } .users-configuration { diff --git a/src/styles/_groundcontrol.scss b/src/styles/_groundcontrol.scss index 859b2c78f2..8328139e8e 100644 --- a/src/styles/_groundcontrol.scss +++ b/src/styles/_groundcontrol.scss @@ -67,3 +67,34 @@ width: 28px; } } + +.voting-related-configs, +.general-configs, +.team-related-configs { + margin-top: 20px; + + .voting-related-controls { + display: flex; + flex-direction: column; + + .control-button-container { + display: flex; + margin-left: 5px; + } + + .publish-voting { + margin-top: 5px; + } + } + + .numeric-input-container { + display: flex; + margin-left: 14px; + gap: 8px; + align-items: center; + + .max-team-size { + margin-top: 10px; + } + } +}