From 97a1ae473bf1f52aaa2cb627c4ffb769e3884960 Mon Sep 17 00:00:00 2001 From: Infinity <2poh.junkang@gmail.com> Date: Wed, 28 Feb 2024 18:09:51 +0800 Subject: [PATCH 1/5] added filterable columns in grading overview --- src/commons/application/ApplicationTypes.ts | 3 + src/commons/workspace/WorkspaceActions.ts | 7 ++ src/commons/workspace/WorkspaceReducer.ts | 9 ++ src/commons/workspace/WorkspaceTypes.ts | 6 + src/pages/academy/grading/Grading.tsx | 2 +- .../grading/subcomponents/GradingBadges.tsx | 30 ++++- .../subcomponents/GradingColumnFilters.tsx | 30 +++++ .../subcomponents/GradingSubmissionsTable.tsx | 116 +++++++++++++++--- 8 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 662099705c..042b7dc8db 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -410,6 +410,9 @@ export const defaultWorkspaceManager: WorkspaceManagerState = { submissionsTableFilters: { columnFilters: [] }, + columnVisiblity: { + columns: [] + }, currentSubmission: undefined, currentQuestion: undefined, hasUnsavedChanges: false diff --git a/src/commons/workspace/WorkspaceActions.ts b/src/commons/workspace/WorkspaceActions.ts index dd7e5c5de6..6d7a11ce67 100644 --- a/src/commons/workspace/WorkspaceActions.ts +++ b/src/commons/workspace/WorkspaceActions.ts @@ -34,6 +34,7 @@ import { EVAL_EDITOR_AND_TESTCASES, EVAL_REPL, EVAL_TESTCASE, + GradingColumnVisibility, MOVE_CURSOR, NAV_DECLARATION, PLAYGROUND_EXTERNAL_SELECT, @@ -64,6 +65,7 @@ import { UPDATE_EDITOR_VALUE, UPDATE_ENVSTEPS, UPDATE_ENVSTEPSTOTAL, + UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, UPDATE_SUBLANGUAGE, @@ -398,6 +400,11 @@ export const updateSubmissionsTableFilters = createAction( (filters: SubmissionsTableFilters) => ({ payload: { filters } }) ); +export const updateGradingColumnVisibility = createAction( + UPDATE_GRADING_COLUMN_VISIBILITY, + (filters: GradingColumnVisibility) => ({ payload: { filters } }) +); + export const updateCurrentAssessmentId = createAction( UPDATE_CURRENT_ASSESSMENT_ID, (assessmentId: number, questionId: number) => ({ payload: { assessmentId, questionId } }) diff --git a/src/commons/workspace/WorkspaceReducer.ts b/src/commons/workspace/WorkspaceReducer.ts index 47c3f4251c..2e11a73e70 100644 --- a/src/commons/workspace/WorkspaceReducer.ts +++ b/src/commons/workspace/WorkspaceReducer.ts @@ -77,6 +77,7 @@ import { UPDATE_EDITOR_VALUE, UPDATE_ENVSTEPS, UPDATE_ENVSTEPSTOTAL, + UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, UPDATE_SUBLANGUAGE, @@ -631,6 +632,14 @@ const oldWorkspaceReducer: Reducer = ( submissionsTableFilters: action.payload.filters } }; + case UPDATE_GRADING_COLUMN_VISIBILITY: + return { + ...state, + grading: { + ...state.grading, + columnVisiblity: action.payload.filters + } + }; case UPDATE_CURRENT_ASSESSMENT_ID: return { ...state, diff --git a/src/commons/workspace/WorkspaceTypes.ts b/src/commons/workspace/WorkspaceTypes.ts index e011734067..537e2849e7 100644 --- a/src/commons/workspace/WorkspaceTypes.ts +++ b/src/commons/workspace/WorkspaceTypes.ts @@ -41,6 +41,7 @@ export const TOGGLE_USING_SUBST = 'TOGGLE_USING_SUBST'; export const TOGGLE_USING_ENV = 'TOGGLE_USING_ENV'; export const TOGGLE_UPDATE_ENV = 'TOGGLE_UPDATE_ENV'; 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'; export const UPDATE_CURRENT_SUBMISSION_ID = 'UPDATE_CURRENT_SUBMISSION_ID'; export const TOGGLE_FOLDER_MODE = 'TOGGLE_FOLDER_MODE'; @@ -77,6 +78,7 @@ type AssessmentWorkspaceState = AssessmentWorkspaceAttr & WorkspaceState; type GradingWorkspaceAttr = { readonly submissionsTableFilters: SubmissionsTableFilters; + readonly columnVisiblity: GradingColumnVisibility; readonly currentSubmission?: number; readonly currentQuestion?: number; readonly hasUnsavedChanges: boolean; @@ -166,3 +168,7 @@ export type DebuggerContext = { export type SubmissionsTableFilters = { columnFilters: { id: string; value: unknown }[]; }; + +export type GradingColumnVisibility = { + columns: string[]; +}; diff --git a/src/pages/academy/grading/Grading.tsx b/src/pages/academy/grading/Grading.tsx index 8f4ae43087..82e24ce06b 100644 --- a/src/pages/academy/grading/Grading.tsx +++ b/src/pages/academy/grading/Grading.tsx @@ -141,7 +141,7 @@ const Grading: React.FC = () => { popoverProps={{ position: Position.BOTTOM }} buttonProps={{ minimal: true, rightIcon: 'caret-down' }} /> - entries per page. + entries per page = ({ filter, onRemove }) => { ); }; -export { AssessmentTypeBadge, FilterBadge, GradingStatusBadge, SubmissionStatusBadge }; +type ColumnFilterBadgeProps = { + filter: string; + onRemove: (toRemove: string) => void; + filtersName: string; +}; + +const ColumnFilterBadge: React.FC = ({ filter, onRemove, filtersName }) => { + return ( + + ); +}; + +export { + AssessmentTypeBadge, + ColumnFilterBadge, + FilterBadge, + GradingStatusBadge, + SubmissionStatusBadge +}; diff --git a/src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx b/src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx new file mode 100644 index 0000000000..4abaf8b1bc --- /dev/null +++ b/src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx @@ -0,0 +1,30 @@ +import { Flex } from '@tremor/react'; + +import { ColumnFilterBadge } from './GradingBadges'; + +type GradingSubmissionFiltersProps = { + filters: string[]; + onFilterRemove: (toRemove: string) => void; + filtersName: string[]; +}; + +const GradingColumnFilters: React.FC = ({ + filters, + onFilterRemove, + filtersName +}) => { + return ( + + {filters.map((filter, index) => ( + + ))} + + ); +}; + +export default GradingColumnFilters; diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 353d414758..554953d613 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -31,12 +31,17 @@ import { debounce } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import { updateSubmissionsTableFilters } from 'src/commons/workspace/WorkspaceActions'; +import { + updateGradingColumnVisibility, + updateSubmissionsTableFilters +} from 'src/commons/workspace/WorkspaceActions'; +import { GradingColumnVisibility } from 'src/commons/workspace/WorkspaceTypes'; import { GradingOverview } from 'src/features/grading/GradingTypes'; import { convertFilterToBackendParams } from 'src/features/grading/GradingUtils'; import GradingActions from './GradingActions'; import { AssessmentTypeBadge, GradingStatusBadge, SubmissionStatusBadge } from './GradingBadges'; +import GradingColumnFilters from './GradingColumnFilters'; import GradingSubmissionFilters from './GradingSubmissionFilters'; const columnHelper = createColumnHelper(); @@ -119,11 +124,16 @@ const GradingSubmissionTable: React.FC = ({ }) => { const dispatch = useDispatch(); const tableFilters = useTypedSelector(state => state.workspaces.grading.submissionsTableFilters); + const columnVisibility = useTypedSelector(state => state.workspaces.grading.columnVisiblity); const [columnFilters, setColumnFilters] = useState([ ...tableFilters.columnFilters ]); + const [hiddenColumns, setHiddenColumns] = useState( + columnVisibility ? columnVisibility : { columns: [] } + ); + const [page, setPage] = useState(0); const maxPage = useMemo(() => Math.ceil(totalRows / pageSize) - 1, [totalRows, pageSize]); const resetPage = useCallback(() => setPage(0), [setPage]); @@ -185,10 +195,30 @@ const GradingSubmissionTable: React.FC = ({ resetPage(); }; + const handleColumnFilterRemove = (toRemove: string) => { + setHiddenColumns((prev: GradingColumnVisibility) => { + return { + columns: prev.columns.filter(column => column !== toRemove) + }; + }); + }; + + const handleColumnFilterAdd = (toAdd: string) => { + setHiddenColumns((prev: GradingColumnVisibility) => { + return { + columns: [...prev.columns, toAdd] + }; + }); + }; + useEffect(() => { dispatch(updateSubmissionsTableFilters({ columnFilters })); }, [columnFilters, dispatch]); + useEffect(() => { + dispatch(updateGradingColumnVisibility(hiddenColumns)); + }, [hiddenColumns, dispatch]); + useEffect(() => { resetPage(); }, [updateEntries, resetPage, searchValue]); @@ -206,7 +236,10 @@ const GradingSubmissionTable: React.FC = ({ {columnFilters.length > 0 ? 'Filters: ' - : 'No filters applied. Click on any cell to filter by its value.'}{' '} + : 'No filters applied. Click on any cell to filter by its value.' + + (hiddenColumns.columns.length === 0 + ? ' Click on any column header to hide it.' + : '')}{' '} @@ -220,28 +253,83 @@ const GradingSubmissionTable: React.FC = ({ onChange={handleSearchQueryUpdate} /> + + {hiddenColumns.columns.length > 0 ? ( + + +
+ Columns Hidden: +
+ { + const headerTexts = columns.filter( + col => col['accessorKey'] === id || col['header'] === id + ); + return headerTexts[0]['header'] ? headerTexts[0]['header'].toString() : ''; + })} + onFilterRemove={handleColumnFilterRemove} + /> +
+
+ ) : ( + <> + )} + {table.getHeaderGroups().map(headerGroup => ( - {headerGroup.headers.map(header => ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ))} + {headerGroup.headers.map(header => + hiddenColumns.columns.reduce( + (accumulator, currentValue) => accumulator || header.id.includes(currentValue), + false + ) ? ( + <> + ) : ( + + + + ) + )} ))} {table.getRowModel().rows.map(row => ( - {row.getVisibleCells().map(cell => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} + {row + .getVisibleCells() + .map(cell => + hiddenColumns.columns.reduce( + (accumulator, currentValue) => accumulator || cell.id.includes(currentValue), + false + ) ? ( + <> + ) : ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + )} ))} From 567a47127778de8a376e3a9553ea46e5c92fb8cc Mon Sep 17 00:00:00 2001 From: Infinity <2poh.junkang@gmail.com> Date: Wed, 28 Feb 2024 20:06:35 +0800 Subject: [PATCH 2/5] some cleanup code --- src/pages/academy/grading/Grading.tsx | 2 +- .../subcomponents/GradingSubmissionsTable.tsx | 52 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/pages/academy/grading/Grading.tsx b/src/pages/academy/grading/Grading.tsx index 82e24ce06b..8f4ae43087 100644 --- a/src/pages/academy/grading/Grading.tsx +++ b/src/pages/academy/grading/Grading.tsx @@ -141,7 +141,7 @@ const Grading: React.FC = () => { popoverProps={{ position: Position.BOTTOM }} buttonProps={{ minimal: true, rightIcon: 'caret-down' }} /> - entries per page + entries per page. = ({ return ( <> - - -
- - - {columnFilters.length > 0 - ? 'Filters: ' - : 'No filters applied. Click on any cell to filter by its value.' + - (hiddenColumns.columns.length === 0 - ? ' Click on any column header to hide it.' - : '')}{' '} - -
- -
- - } - placeholder="Search by assessment name" - value={searchQuery} - onChange={handleSearchQueryUpdate} - /> -
- {hiddenColumns.columns.length > 0 ? ( @@ -283,6 +258,31 @@ const GradingSubmissionTable: React.FC = ({ ) : ( <> )} + + + +
+ + + {columnFilters.length > 0 + ? 'Filters: ' + : 'No filters applied. Click on any cell to filter by its value.' + + (hiddenColumns.columns.length === 0 + ? ' Click on any column header to hide it.' + : '')}{' '} + +
+ +
+ + } + placeholder="Search by assessment name" + value={searchQuery} + onChange={handleSearchQueryUpdate} + /> +
@@ -299,7 +299,7 @@ const GradingSubmissionTable: React.FC = ({
{table.getHeaderGroups().map(headerGroup => ( - {headerGroup.headers.map(header => - hiddenColumns.columns.reduce( - (accumulator, currentValue) => accumulator || header.id.includes(currentValue), - false - ) ? ( - <> - ) : ( - - - - ) - )} + {headerGroup.headers.map(header => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} ))} {table.getRowModel().rows.map(row => ( - {row - .getVisibleCells() - .map(cell => - hiddenColumns.columns.reduce( - (accumulator, currentValue) => accumulator || cell.id.includes(currentValue), - false - ) ? ( - <> - ) : ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ) - )} + {row.getVisibleCells().map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} ))} diff --git a/src/styles/_academy.scss b/src/styles/_academy.scss index 6058cc9477..5ac1d24b47 100644 --- a/src/styles/_academy.scss +++ b/src/styles/_academy.scss @@ -510,7 +510,7 @@ } .grading-overview-filterable-btns { - padding: 0; + padding: 0 2px; // Hides overflowed text for the buttons &, @@ -528,7 +528,7 @@ -0.875rem for icon margins -1.125rem for additional padding */ - p { + p, &:has(> span) > span { max-width: calc(20vw - 16px - 2rem); } } @@ -540,7 +540,7 @@ &:hover { // Buttons with bg - > div > span { + > div > span, &:has(> span) { // Use of contrast due to unknown background color filter: contrast(0.9); } @@ -552,6 +552,10 @@ } } +.grading-overview-filterable-btns, .grading-overview-rounded-btns { + border-radius: 9999px; +} + .grading-overview-footer-sibling { // Footer component ~ div ~ div { From 852da3cd096062dbb2c6684e334b0f3061f92da8 Mon Sep 17 00:00:00 2001 From: Infinity <2poh.junkang@gmail.com> Date: Wed, 6 Mar 2024 14:57:03 +0800 Subject: [PATCH 4/5] missing code from previous commit --- .../subcomponents/GradingSubmissionsTable.tsx | 176 +++++++++++++----- 1 file changed, 125 insertions(+), 51 deletions(-) diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 353d414758..8afa3b9a49 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -1,6 +1,6 @@ import '@tremor/react/dist/esm/tremor.css'; -import { Icon as BpIcon } from '@blueprintjs/core'; +import { Button, H6, Icon as BpIcon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { Column, @@ -14,9 +14,6 @@ import { useReactTable } from '@tanstack/react-table'; import { - Bold, - Button, - Flex, Footer, Table, TableBody, @@ -24,19 +21,25 @@ import { TableHead, TableHeaderCell, TableRow, - Text, TextInput } from '@tremor/react'; import { debounce } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import GradingFlex from 'src/commons/grading/GradingFlex'; +import GradingText from 'src/commons/grading/GradingText'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import { updateSubmissionsTableFilters } from 'src/commons/workspace/WorkspaceActions'; +import { + updateGradingColumnVisibility, + updateSubmissionsTableFilters +} from 'src/commons/workspace/WorkspaceActions'; +import { GradingColumnVisibility } from 'src/commons/workspace/WorkspaceTypes'; import { GradingOverview } from 'src/features/grading/GradingTypes'; import { convertFilterToBackendParams } from 'src/features/grading/GradingUtils'; import GradingActions from './GradingActions'; import { AssessmentTypeBadge, GradingStatusBadge, SubmissionStatusBadge } from './GradingBadges'; +import GradingColumnFilters from './GradingColumnFilters'; import GradingSubmissionFilters from './GradingSubmissionFilters'; const columnHelper = createColumnHelper(); @@ -84,13 +87,13 @@ const makeColumns = (handleClick: () => void) => [ cell: info => { const { currentXp, xpBonus, maxXp } = info.getValue(); return ( - - + + {currentXp} (+{xpBonus}) - - / - {maxXp} - + + / + {maxXp} + ); } }), @@ -119,11 +122,16 @@ const GradingSubmissionTable: React.FC = ({ }) => { const dispatch = useDispatch(); const tableFilters = useTypedSelector(state => state.workspaces.grading.submissionsTableFilters); + const columnVisibility = useTypedSelector(state => state.workspaces.grading.columnVisiblity); const [columnFilters, setColumnFilters] = useState([ ...tableFilters.columnFilters ]); + const [hiddenColumns, setHiddenColumns] = useState( + columnVisibility ? columnVisibility : { columns: [] } + ); + const [page, setPage] = useState(0); const maxPage = useMemo(() => Math.ceil(totalRows / pageSize) - 1, [totalRows, pageSize]); const resetPage = useCallback(() => setPage(0), [setPage]); @@ -185,10 +193,30 @@ const GradingSubmissionTable: React.FC = ({ resetPage(); }; + const handleColumnFilterRemove = (toRemove: string) => { + setHiddenColumns((prev: GradingColumnVisibility) => { + return { + columns: prev.columns.filter(column => column !== toRemove) + }; + }); + }; + + const handleColumnFilterAdd = (toAdd: string) => { + setHiddenColumns((prev: GradingColumnVisibility) => { + return { + columns: [...prev.columns, toAdd] + }; + }); + }; + useEffect(() => { dispatch(updateSubmissionsTableFilters({ columnFilters })); }, [columnFilters, dispatch]); + useEffect(() => { + dispatch(updateGradingColumnVisibility(hiddenColumns)); + }, [hiddenColumns, dispatch]); + useEffect(() => { resetPage(); }, [updateEntries, resetPage, searchValue]); @@ -199,18 +227,41 @@ const GradingSubmissionTable: React.FC = ({ return ( <> - - -
+ {hiddenColumns.columns.length > 0 ? ( + + + Columns Hidden: + { + const headerTexts = columns.filter( + col => col['accessorKey'] === id || col['header'] === id + ); + return headerTexts[0]['header'] ? headerTexts[0]['header'].toString() : ''; + })} + onFilterRemove={handleColumnFilterRemove} + /> + + + ) : ( + <> + )} + + + +
- + {columnFilters.length > 0 ? 'Filters: ' - : 'No filters applied. Click on any cell to filter by its value.'}{' '} - + : 'No filters applied. Click on any cell to filter by its value.' + + (hiddenColumns.columns.length === 0 + ? ' Click on any column header to hide it.' + : '')}{' '} +
- +
= ({ value={searchQuery} onChange={handleSearchQueryUpdate} /> - +
+
{table.getHeaderGroups().map(headerGroup => ( - {headerGroup.headers.map(header => ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ))} + {headerGroup.headers.map(header => + hiddenColumns.columns.reduce( + (accumulator, currentValue) => accumulator || header.id.includes(currentValue), + false + ) ? ( + <> + ) : ( + + + + ) + )} ))} {table.getRowModel().rows.map(row => ( - {row.getVisibleCells().map(cell => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} + {row + .getVisibleCells() + .map(cell => + hiddenColumns.columns.reduce( + (accumulator, currentValue) => accumulator || cell.id.includes(currentValue), + false + ) ? ( + <> + ) : ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + )} ))}
- +
- - <> - + ); }; From c4f0ddf0e9de6d2926bd54b479738bd077f946e4 Mon Sep 17 00:00:00 2001 From: Infinity <2poh.junkang@gmail.com> Date: Wed, 13 Mar 2024 17:53:38 +0800 Subject: [PATCH 5/5] halfway done for porting tanstack/tremor to ag grid/blueprint --- .../grading/subcomponents/GradingActions.tsx | 11 +- .../subcomponents/GradingSubmissionsTable.tsx | 226 ++++++++++++++++++ src/styles/_academy.scss | 28 ++- 3 files changed, 259 insertions(+), 6 deletions(-) diff --git a/src/pages/academy/grading/subcomponents/GradingActions.tsx b/src/pages/academy/grading/subcomponents/GradingActions.tsx index 93e2400494..5a504e198b 100644 --- a/src/pages/academy/grading/subcomponents/GradingActions.tsx +++ b/src/pages/academy/grading/subcomponents/GradingActions.tsx @@ -1,20 +1,23 @@ import { Icon as BpIcon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { Flex, Icon } from '@tremor/react'; +import { Icon } from '@tremor/react'; +import React from 'react'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; import { reautogradeSubmission, unsubmitSubmission } from 'src/commons/application/actions/SessionActions'; +import GradingFlex from 'src/commons/grading/GradingFlex'; import { showSimpleConfirmDialog } from 'src/commons/utils/DialogHelper'; import { useTypedSelector } from 'src/commons/utils/Hooks'; type GradingActionsProps = { submissionId: number; + style?: React.CSSProperties; }; -const GradingActions: React.FC = ({ submissionId }) => { +const GradingActions: React.FC = ({ submissionId, style }) => { const dispatch = useDispatch(); const courseId = useTypedSelector(store => store.session.courseId); @@ -46,7 +49,7 @@ const GradingActions: React.FC = ({ submissionId }) => { }; return ( - + } variant="light" /> @@ -62,7 +65,7 @@ const GradingActions: React.FC = ({ submissionId }) => { - + ); }; diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 8afa3b9a49..13d1d7f79d 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -1,4 +1,6 @@ import '@tremor/react/dist/esm/tremor.css'; +import "ag-grid-community/styles/ag-grid.css"; +import "ag-grid-community/styles/ag-theme-quartz.css" import { Button, H6, Icon as BpIcon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; @@ -23,6 +25,8 @@ import { TableRow, TextInput } from '@tremor/react'; +import { ColDef, ICellRendererParams } from 'ag-grid-community'; +import { AgGridReact } from 'ag-grid-react'; import { debounce } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; @@ -120,6 +124,157 @@ const GradingSubmissionTable: React.FC = ({ submissions, updateEntries }) => { + + // End of Original Code + + interface IRow { + assessmentName: string; + assessmentType: string; + studentName: string; + studentUsername: string; + groupName: string; + submissionStatus: string; + gradingStatus: string; + xp: string; + actions: string; + index: number; + } + + const defaultColumnDefs: ColDef = { + filter: false, + resizable: false, + sortable: true + }; + + const [rowData, setRowData] = useState(); + + const [colDefs, setColDefs] = useState[]>(); + + const generateCols = (resetPage: () => void) => { + const cols: ColDef[] = []; + + cols.push({ headerName: "Name", field: "assessmentName", flex: 3, cellStyle: defaultCellStyle({textAlign: "left"}), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "assessmentName", + value: params.data.assessmentName, + onClick: resetPage, + } + } + : undefined; + }, headerClass: defaultHeaderClasses("grading-left-align") }); + cols.push({ headerName: "Type", field: "assessmentType", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "assessmentType", + value: params.data.assessmentType, + onClick: resetPage, + children: [] + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Student", field: "studentName", flex: 1.5, cellStyle: defaultCellStyle({textAlign: "left"}), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "studentName", + value: params.data.studentName, + onClick: resetPage, + } + } + : undefined; + }, headerClass: defaultHeaderClasses("grading-left-align") }); + cols.push({ headerName: "Username", field: "studentUsername", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "studentUsername", + value: params.data.studentUsername, + onClick: resetPage, + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Group", field: "groupName", flex: 0.75, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "groupName", + value: params.data.groupName, + onClick: resetPage, + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Progress", field: "submissionStatus", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: FilterableNew, + params: { + setColumnFilters: setColumnFilters, + id: "submissionStatus", + value: params.data.submissionStatus, + onClick: resetPage, + children: [] + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Grading", field: "gradingStatus", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: GradingStatusBadge, + params: { + status: params.data.gradingStatus + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Raw XP (+Bonus)", field: "xp", flex: 1, cellStyle: defaultCellStyle(), headerClass: defaultHeaderClasses() }); + cols.push({ headerName: "Actions", field: "actions", flex: 1, cellStyle: defaultCellStyle(), cellRendererSelector: (params: ICellRendererParams) => { + return (params.data !== undefined) + ? { + component: GradingActions, + params: { + submissionId: params.data.index, + style: {justifyContent: "center"} + } + } + : undefined; + }, headerClass: defaultHeaderClasses() }); + + return cols; + } + + const defaultCellStyle = (style?: React.CSSProperties) => { + return { + textAlign: "center", + display: "flex", + justifyContent: "center", + flexDirection: "column", + fontSize: "0.875rem", + ...style, + } + }; + + const defaultHeaderClasses = (extraClass?: string) => { + return ("grading-default-headers " + (extraClass !== undefined ? extraClass : "")); + }; + + // Start of Original Code const dispatch = useDispatch(); const tableFilters = useTypedSelector(state => state.workspaces.grading.submissionsTableFilters); const columnVisibility = useTypedSelector(state => state.workspaces.grading.columnVisiblity); @@ -225,6 +380,31 @@ const GradingSubmissionTable: React.FC = ({ updateEntries(page, backendFilterParams); }, [updateEntries, page, backendFilterParams]); + // End of Original Code + + useEffect(() => { + setRowData(submissions.map((submission): IRow => { + return { + assessmentName: submission.assessmentName, + assessmentType: submission.assessmentType, + studentName: submission.studentName, + studentUsername: submission.studentUsername, + groupName: submission.groupName, + submissionStatus: submission.submissionStatus, + gradingStatus: submission.gradingStatus, + xp: submission.initialXp + " (+" + submission.xpBonus + ") / " + submission.maxXp, + actions: "", + index: submission.submissionId, + }; + })); + }, [submissions]); + + useEffect(() => { + setColDefs(generateCols(resetPage)); + }, [resetPage]); + + // Start of Original Code + return ( <> {hiddenColumns.columns.length > 0 ? ( @@ -271,6 +451,23 @@ const GradingSubmissionTable: React.FC = ({ onChange={handleSearchQueryUpdate} /> + + {/* End of Original Code */} + +
+ +
+ + {/* Start of Original Code */} @@ -370,6 +567,14 @@ type FilterableProps = { onClick?: () => void; }; +type FilterablePropsNew = { + setColumnFilters: React.Dispatch>; + id: string; + value: string; + children?: React.ReactNode; + onClick?: () => void; +}; + const Filterable: React.FC = ({ column, value, children, onClick }) => { const handleFilterChange = () => { column.setFilterValue(value); @@ -383,4 +588,25 @@ const Filterable: React.FC = ({ column, value, children, onClic ); }; +const FilterableNew: React.FC = ({ setColumnFilters, id, value, children, onClick }) => { + const handleFilterChange = () => { + setColumnFilters((prev: ColumnFiltersState) => { + return [ + ...prev, + { + id: id, + value: value + } + ]; + }); + onClick?.(); + }; + + return ( + + ); +}; + export default GradingSubmissionTable; diff --git a/src/styles/_academy.scss b/src/styles/_academy.scss index 5ac1d24b47..5b225c5075 100644 --- a/src/styles/_academy.scss +++ b/src/styles/_academy.scss @@ -337,13 +337,17 @@ &:not(.ag-header-cell-sortable) .ag-header-cell-label { padding-right: 1em; } + + .ag-cell-label-container:not(:has(> span.ag-header-icon.ag-header-cell-menu-button)) > .ag-header-cell-label { + padding-right: 1em; + } } .ag-cell { font-size: 1.1em; /* Override to reduce ag-grid default padding */ - padding-left: 0.375em !important; - padding-right: 0.375em !important; + padding-left: 0.375em; + padding-right: 0.375em; text-align: center; } @@ -511,6 +515,7 @@ .grading-overview-filterable-btns { padding: 0 2px; + text-align: inherit; // Hides overflowed text for the buttons &, @@ -567,3 +572,22 @@ } } } + +.ag-header-cell.grading-default-headers { + span.ag-header-cell-text { + font-size: 0.8rem; + color: #6b7280; + font-weight: 600; + } +} + +.ag-header-cell.grading-left-align { + span.ag-header-cell-text { + margin: 0 auto 0 0; + } +} + +.ag-header-cell.hide-cols-btn { + width: 32px; + height: 32px; +} \ No newline at end of file