diff --git a/src/commons/workspace/WorkspaceActions.ts b/src/commons/workspace/WorkspaceActions.ts index 9883816f80..38d6790103 100644 --- a/src/commons/workspace/WorkspaceActions.ts +++ b/src/commons/workspace/WorkspaceActions.ts @@ -26,6 +26,7 @@ import { CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, CLEAR_REPL_OUTPUT_LAST, + DECREMENT_REQUEST_COUNTER, DISABLE_TOKEN_COUNTER, EditorTabState, ENABLE_TOKEN_COUNTER, @@ -35,6 +36,7 @@ import { EVAL_REPL, EVAL_TESTCASE, GradingColumnVisibility, + INCREMENT_REQUEST_COUNTER, MOVE_CURSOR, NAV_DECLARATION, PLAYGROUND_EXTERNAL_SELECT, @@ -67,7 +69,6 @@ import { UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, - UPDATE_REQUEST_COUNTER, UPDATE_STEPSTOTAL, UPDATE_SUBLANGUAGE, UPDATE_SUBMISSIONS_TABLE_FILTERS, @@ -396,10 +397,13 @@ export const setIsEditorReadonly = createAction( }) ); -export const updateRequestCounter = createAction( - UPDATE_REQUEST_COUNTER, - (requestCount: number) => ({ payload: { requestCount } }) -); +export const increaseRequestCounter = createAction(INCREMENT_REQUEST_COUNTER, () => ({ + payload: {} +})); + +export const decreaseRequestCounter = createAction(DECREMENT_REQUEST_COUNTER, () => ({ + payload: {} +})); export const updateSubmissionsTableFilters = createAction( UPDATE_SUBMISSIONS_TABLE_FILTERS, diff --git a/src/commons/workspace/WorkspaceReducer.ts b/src/commons/workspace/WorkspaceReducer.ts index de402afa31..739d7be569 100644 --- a/src/commons/workspace/WorkspaceReducer.ts +++ b/src/commons/workspace/WorkspaceReducer.ts @@ -47,12 +47,14 @@ import { import { ADD_EDITOR_TAB, CHANGE_EXTERNAL_LIBRARY, + DECREMENT_REQUEST_COUNTER, DISABLE_TOKEN_COUNTER, EditorTabState, ENABLE_TOKEN_COUNTER, END_CLEAR_CONTEXT, EVAL_EDITOR, EVAL_REPL, + INCREMENT_REQUEST_COUNTER, MOVE_CURSOR, REMOVE_EDITOR_TAB, REMOVE_EDITOR_TAB_FOR_FILE, @@ -79,7 +81,6 @@ import { UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, - UPDATE_REQUEST_COUNTER, UPDATE_STEPSTOTAL, UPDATE_SUBLANGUAGE, UPDATE_SUBMISSIONS_TABLE_FILTERS, @@ -659,12 +660,20 @@ const oldWorkspaceReducer: Reducer = ( currentQuestion: action.payload.questionId } }; - case UPDATE_REQUEST_COUNTER: + case INCREMENT_REQUEST_COUNTER: return { ...state, grading: { ...state.grading, - requestCounter: action.payload.requestCount + requestCounter: state.grading.requestCounter + 1 + } + }; + case DECREMENT_REQUEST_COUNTER: + return { + ...state, + grading: { + ...state.grading, + requestCounter: Math.max(0, state.grading.requestCounter - 1) } }; case SET_FOLDER_MODE: diff --git a/src/commons/workspace/WorkspaceTypes.ts b/src/commons/workspace/WorkspaceTypes.ts index 10842e3d36..1cd35abcce 100644 --- a/src/commons/workspace/WorkspaceTypes.ts +++ b/src/commons/workspace/WorkspaceTypes.ts @@ -23,12 +23,14 @@ export const CLEAR_REPL_OUTPUT_LAST = 'CLEAR_REPL_OUTPUT_LAST'; export const END_CLEAR_CONTEXT = 'END_CLEAR_CONTEXT'; export const ENABLE_TOKEN_COUNTER = 'ENABLE_TOKEN_COUNTER'; export const DISABLE_TOKEN_COUNTER = 'DISABLE_TOKEN_COUNTER'; +export const DECREMENT_REQUEST_COUNTER = 'DECREMENT_REQUEST_COUNTER'; export const EVAL_EDITOR = 'EVAL_EDITOR'; export const EVAL_REPL = 'EVAL_REPL'; export const PROMPT_AUTOCOMPLETE = 'PROMPT_AUTOCOMPLETE'; export const EVAL_SILENT = 'EVAL_SILENT'; export const EVAL_TESTCASE = 'EVAL_TESTCASE'; export const EVAL_EDITOR_AND_TESTCASES = 'EVAL_EDITOR_AND_TESTCASES'; +export const INCREMENT_REQUEST_COUNTER = 'INCREMENT_REQUEST_COUNTER'; export const MOVE_CURSOR = 'MOVE_CURSOR'; export const NAV_DECLARATION = 'NAV_DECLARATION'; export const PLAYGROUND_EXTERNAL_SELECT = 'PLAYGROUND_EXTERNAL_SELECT '; @@ -40,7 +42,6 @@ export const TOGGLE_EDITOR_AUTORUN = 'TOGGLE_EDITOR_AUTORUN'; export const TOGGLE_USING_SUBST = 'TOGGLE_USING_SUBST'; export const TOGGLE_USING_CSE = 'TOGGLE_USING_CSE'; export const TOGGLE_UPDATE_CSE = 'TOGGLE_UPDATE_CSE'; -export const UPDATE_REQUEST_COUNTER = 'UPDATE_REQUEST_COUNTER'; export const UPDATE_SUBMISSIONS_TABLE_FILTERS = 'UPDATE_SUBMISSIONS_TABLE_FILTERS'; export const UPDATE_GRADING_COLUMN_VISIBILITY = 'UPDATE_GRADING_COLUMN_VISIBILITY'; export const UPDATE_CURRENT_ASSESSMENT_ID = 'UPDATE_CURRENT_ASSESSMENT_ID'; diff --git a/src/pages/academy/grading/Grading.tsx b/src/pages/academy/grading/Grading.tsx index aa06b8f05a..2f3e4943a7 100644 --- a/src/pages/academy/grading/Grading.tsx +++ b/src/pages/academy/grading/Grading.tsx @@ -10,7 +10,7 @@ import { fetchGradingOverviews } from 'src/commons/application/actions/SessionAc import { Role } from 'src/commons/application/ApplicationTypes'; import SimpleDropdown from 'src/commons/SimpleDropdown'; import { useSession, useTypedSelector } from 'src/commons/utils/Hooks'; -import { updateRequestCounter } from 'src/commons/workspace/WorkspaceActions'; +import { decreaseRequestCounter, increaseRequestCounter } from 'src/commons/workspace/WorkspaceActions'; import { numberRegExp } from 'src/features/academy/AcademyTypes'; import { exportGradingCSV, @@ -55,8 +55,7 @@ const Grading: React.FC = () => { const updateGradingOverviewsCallback = useCallback( (page: number, filterParams: Object) => { - console.log("+1 parent"); - dispatch(updateRequestCounter(requestCounter + 1)); + dispatch(increaseRequestCounter()); dispatch( fetchGradingOverviews( showAllGroups, @@ -70,7 +69,12 @@ const Grading: React.FC = () => { ); useEffect(() => { - dispatch(updateRequestCounter(Math.max(0, requestCounter - 1))); + console.log(requestCounter); + }, [requestCounter]); + + useEffect(() => { + console.log("minus 11"); + dispatch(decreaseRequestCounter()); }, [gradingOverviews]); // If submissionId or questionId is defined but not numeric, redirect back to the Grading overviews page diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 09728a7e7a..8505212df5 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -35,8 +35,8 @@ import GradingFlex from 'src/commons/grading/GradingFlex'; import GradingText from 'src/commons/grading/GradingText'; import { useTypedSelector } from 'src/commons/utils/Hooks'; import { + increaseRequestCounter, updateGradingColumnVisibility, - updateRequestCounter, updateSubmissionsTableFilters } from 'src/commons/workspace/WorkspaceActions'; import { GradingColumnVisibility } from 'src/commons/workspace/WorkspaceTypes'; @@ -156,12 +156,34 @@ const GradingSubmissionTable: React.FC = ({ tableMargins: string; } + enum ColumnFields { + assessmentName = "assessmentName", + assessmentType = "assessmentType", + studentName = "studentName", + studentUsername = "studentUsername", + groupName = "groupName", + submissionStatus = "submissionStatus", + gradingStatus = "gradingStatus", + xp = "xp", + actionsIndex = "actionsIndex", + } + const defaultColumnDefs: ColDef = { filter: false, resizable: false, sortable: true }; + const disabledEditModeCols: string[] = [ + ColumnFields.actionsIndex, + ] + + const disabledFilterModeCols: string[] = [ + ColumnFields.gradingStatus, + ColumnFields.xp, + ColumnFields.actionsIndex, + ] + const ROW_HEIGHT: number = 60; // in px, declared here to calculate table height const tableProperties: ITableProperties = { @@ -184,115 +206,133 @@ const GradingSubmissionTable: React.FC = ({ const gridRef = useRef>(null); - const generateCols = (resetPage: () => void) => { + const generateCols = () => { const cols: ColDef[] = []; - cols.push({ headerName: "Name", field: "assessmentName", flex: 3, cellStyle: defaultCellStyle({textAlign: "left", cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Name", + field: ColumnFields.assessmentName, + flex: 3, + cellClass: "grading-def-cell grading-def-cell-pointer grading-cell-align-left", + headerClass: "grading-default-headers grading-left-align", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "assessmentName", value: params.data.assessmentName, - onClick: resetPage, - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses("grading-left-align") }); + }, + }); - cols.push({ headerName: "Type", field: "assessmentType", flex: 1, cellStyle: defaultCellStyle({cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Type", + field: ColumnFields.assessmentType, + flex: 1, + cellClass: "grading-def-cell grading-def-cell-pointer", + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "assessmentType", value: params.data.assessmentType, - onClick: resetPage, children: [], - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); - cols.push({ headerName: "Student", field: "studentName", flex: 1.5, cellStyle: defaultCellStyle({textAlign: "left", cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Student", + field: ColumnFields.studentName, + flex: 1.5, + cellClass: "grading-def-cell grading-def-cell-pointer grading-cell-align-left", + headerClass: "grading-default-headers grading-left-align", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "studentName", value: params.data.studentName, - onClick: resetPage, - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses("grading-left-align") }); + }, + }); - cols.push({ headerName: "Username", field: "studentUsername", flex: 1, cellStyle: defaultCellStyle({cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Username", + field: ColumnFields.studentUsername, + flex: 1, + cellClass: "grading-def-cell grading-def-cell-pointer", + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "studentUsername", value: params.data.studentUsername, - onClick: resetPage, - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); - cols.push({ headerName: "Group", field: "groupName", flex: 0.75, cellStyle: defaultCellStyle({cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Group", + field: ColumnFields.groupName, + flex: 0.75, + cellClass: "grading-def-cell grading-def-cell-pointer", + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "groupName", value: params.data.groupName, - onClick: resetPage, - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); - cols.push({ headerName: "Progress", field: "submissionStatus", flex: 1, cellStyle: defaultCellStyle({cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Progress", + field: ColumnFields.submissionStatus, + flex: 1, + cellClass: "grading-def-cell grading-def-cell-pointer", + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: FilterableNew, params: { - setColumnFilters: setColumnFilters, - id: "submissionStatus", - value: params.data.submissionStatus, - onClick: resetPage, + value: params.data.submissionStatus, children: [], - submissionID: params.data.actionsIndex, - courseID: params.data.courseID, filterMode: filterMode, } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); - cols.push({ headerName: "Grading", field: "gradingStatus", flex: 1, cellStyle: defaultCellStyle({cursor: (!filterMode ? "pointer" : "")}), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Grading", + field: ColumnFields.gradingStatus, + flex: 1, + cellClass: "grading-def-cell" + (!filterMode ? " grading-def-cell-pointer" : ""), + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: GradingStatusBadge, @@ -301,11 +341,24 @@ const GradingSubmissionTable: React.FC = ({ } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); - cols.push({ headerName: "Raw XP (+Bonus)", field: "xp", flex: 1, cellStyle: defaultCellStyle(), headerClass: defaultHeaderClasses() }); + cols.push({ + headerName: "Raw XP (+Bonus)", + field: ColumnFields.xp, + flex: 1, + cellClass: "grading-def-cell" + (!filterMode ? " grading-def-cell-pointer" : " grading-def-cell-selectable"), + headerClass: "grading-default-headers", + }); - cols.push({ headerName: "Actions", field: "actionsIndex", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + cols.push({ + headerName: "Actions", + field: ColumnFields.actionsIndex, + flex: 1, + cellClass: "grading-def-cell", + headerClass: "grading-default-headers", + cellRendererSelector: (params: ICellRendererParams) => { return (params.data !== undefined) ? { component: GradingActions, @@ -315,27 +368,12 @@ const GradingSubmissionTable: React.FC = ({ } } : undefined; - }, headerClass: defaultHeaderClasses() }); + }, + }); return cols; } - const defaultCellStyle = (style?: React.CSSProperties) => { - return { - textAlign: "center", - display: "flex", - justifyContent: "center", - flexDirection: "column", - fontSize: "0.875rem", - borderBottom: "1px solid rgba(0, 0, 0, 0.075)", - ...style, - } - }; - - const defaultHeaderClasses = (extraClass?: string) => { - return ("grading-default-headers " + (extraClass !== undefined ? extraClass : "")); - }; - const showLoading = useCallback(() => { gridRef.current!.api.showLoadingOverlay(); }, []) @@ -344,10 +382,20 @@ const GradingSubmissionTable: React.FC = ({ gridRef.current!.api.hideOverlay(); }, []) + const showNoRows = useCallback(() => { + gridRef.current!.api.showNoRowsOverlay(); + }, []) + const cellClickedEvent = (event: CellClickedEvent) => { - if (filterMode === false && event.colDef.field !== "xp" && event.colDef.field !== "actionsIndex") { + + const colClicked: string = event.colDef.field ? event.colDef.field : ""; + + if (!filterMode && !disabledEditModeCols.includes(colClicked)) { navigate(`/courses/${courseId}/grading/${event.data.actionsIndex}`); + } else if (filterMode && !disabledFilterModeCols.includes(colClicked)) { + handleFilterAdd({id: colClicked, value: event.data[colClicked]}); } + }; // Start of Original Code @@ -391,7 +439,7 @@ const GradingSubmissionTable: React.FC = ({ // Converts the columnFilters array into backend query parameters. const backendFilterParams = useMemo(() => { const filters: Array<{ [key: string]: any }> = [ - { id: 'assessmentName', value: searchValue }, + { id: ColumnFields.assessmentName, value: searchValue }, ...columnFilters ].map(convertFilterToBackendParams); @@ -421,11 +469,27 @@ const GradingSubmissionTable: React.FC = ({ getPaginationRowModel: getPaginationRowModel() }); + const handleFilterAdd = ({ id, value }: ColumnFilter) => { + dispatch(increaseRequestCounter()); + setColumnFilters((prev: ColumnFiltersState) => { + const alreadyExists = prev.reduce((acc, curr) => acc || (curr.id === id && curr.value === value), false); + return alreadyExists + ? [...prev] + : [ + ...prev, + { + id: id, + value: value + } + ]; + }); + resetPage(); + }; + + const handleFilterRemove = ({ id, value }: ColumnFilter) => { const newFilters = columnFilters.filter(filter => filter.id !== id && filter.value !== value); - // updateIsLoading(true); - console.log("+1"); - dispatch(updateRequestCounter(requestCounter + 1)); + dispatch(increaseRequestCounter()); setColumnFilters(newFilters); resetPage(); }; @@ -466,53 +530,48 @@ const GradingSubmissionTable: React.FC = ({ useEffect(() => { - let sameData: boolean = true; + if (gridRef.current?.api) { - const newData: IRow[] = submissions.map((submission, index): IRow => { - if (sameData && submission.submissionId !== rowData?.[index]?.actionsIndex) { - sameData = false; - } - return { - assessmentName: submission.assessmentName, - assessmentType: submission.assessmentType, - studentName: submission.studentName, - studentUsername: submission.studentUsername, - groupName: submission.groupName, - submissionStatus: submission.submissionStatus, - gradingStatus: submission.gradingStatus, - xp: submission.currentXp + " (+" + submission.xpBonus + ") / " + submission.maxXp, - actionsIndex: submission.submissionId, - courseID: courseId!, - }; - }); - - if ((rowData?.length !== 0 && newData.length === 0) || !sameData) { // First 2 conditions for edge case due to multiple rerenders - setRowData(newData); - } + if (requestCounter <= 0) { + let sameData: boolean = submissions.length === rowData?.length; - }, [submissions, gridRef.current]); + const newData: IRow[] = submissions.map((submission, index): IRow => { + if (sameData && submission.submissionId !== rowData?.[index]?.actionsIndex) { + sameData = false; + } + return { + assessmentName: submission.assessmentName, + assessmentType: submission.assessmentType, + studentName: submission.studentName, + studentUsername: submission.studentUsername, + groupName: submission.groupName, + submissionStatus: submission.submissionStatus, + gradingStatus: submission.gradingStatus, + xp: submission.currentXp + " (+" + submission.xpBonus + ") / " + submission.maxXp, + actionsIndex: submission.submissionId, + courseID: courseId!, + }; + }); + + if (!sameData) { + setRowData(newData); + } - useEffect(() => { - if (gridRef.current?.api) { - if (requestCounter <= 0) { hideLoading(); + if (rowData?.length === 0) { + showNoRows(); + } + } else { showLoading(); } } - }, [requestCounter]); - useEffect(() => { - setColDefs(generateCols(() => { - console.log("+1"); - dispatch(updateRequestCounter(requestCounter + 1)); - resetPage(); - })); - }, [resetPage]); + }, [requestCounter, submissions, gridRef.current]); useEffect(() => { - setColDefs(generateCols(() => resetPage())); - }, [filterMode]); + setColDefs(generateCols()); + }, [resetPage, filterMode]); // Start of Original Code @@ -698,8 +757,6 @@ type FilterablePropsNew = { value: string; children?: React.ReactNode; onClick?: () => void; - courseID: number; - submissionID: number; filterMode: boolean; }; @@ -716,31 +773,9 @@ const Filterable: React.FC = ({ column, value, children, onClic ); }; -const FilterableNew: React.FC = ({ setColumnFilters, id, value, children, onClick, courseID, submissionID, filterMode }) => { - const handleFilterChange = () => { - setColumnFilters((prev: ColumnFiltersState) => { - const alreadyExists = prev.reduce((acc, curr) => acc || (curr.id === id && curr.value === value), false); - return alreadyExists - ? [...prev] - : [ - ...prev, - { - id: id, - value: value - } - ]; - }); - onClick?.(); - }; - +const FilterableNew: React.FC = ({ value, children, filterMode }) => { return ( - filterMode === false - ? - - : - ); diff --git a/src/styles/_academy.scss b/src/styles/_academy.scss index 259eca518f..2b833cc045 100644 --- a/src/styles/_academy.scss +++ b/src/styles/_academy.scss @@ -553,7 +553,7 @@ &:hover { // Buttons with bg - > div > span, &:has(> span), > a { + &:has(> div) { // Use of contrast due to unknown background color filter: contrast(0.9); } @@ -583,7 +583,7 @@ } } -.ag-header-cell.grading-default-headers { +.grading-default-headers { span.ag-header-cell-text { font-size: 0.8rem; color: #6b7280; @@ -623,4 +623,42 @@ &:hover { filter: contrast(0.9); } +} + +.grading-def-cell { + text-align: center; + display: flex!important; + justify-content: center; + flex-direction: column; + font-size: 0.875rem; + user-select: text; + border: 0px!important; + border-bottom: 1px solid rgba(0, 0, 0, 0.075)!important; + + &:hover:not(:has(> div)):has(button.grading-overview-filterable-btns) { + button { + text-decoration: underline; + + &:has(> div) { + filter: contrast(0.9); + } + } + } + + &:active { + border-style: outset; + } + + &.grading-def-cell-pointer { + cursor: pointer; + } + + &.grading-def-cell-selectable { + cursor: text; + border-style: outset; + } + + &.grading-cell-align-left { + text-align: left!important; + } } \ No newline at end of file