diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index a239f0bcba..1110304988 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/grading/GradingFlex.tsx b/src/commons/grading/GradingFlex.tsx new file mode 100644 index 0000000000..4fb56a4e82 --- /dev/null +++ b/src/commons/grading/GradingFlex.tsx @@ -0,0 +1,56 @@ +declare const twJustifyContentValues: readonly ["justify-start", "justify-end", "justify-center", "justify-between", "justify-around", "justify-evenly"]; +declare type JustifyContent = typeof twJustifyContentValues[number]; +declare const twAlignItemsValues: readonly ["items-start", "items-end", "items-center", "items-baseline", "items-stretch"]; +declare type AlignItems = typeof twAlignItemsValues[number]; +declare const twFlexDirectionValues: readonly ["row", "column"]; +declare type FlexDirection = typeof twFlexDirectionValues[number]; + +type GradingFlexProps = { + justifyContent?: JustifyContent; + alignItems?: AlignItems; + flexDirection?: FlexDirection; + children?: React.ReactNode; + style?: React.CSSProperties; + className?: string; +} & React.RefAttributes; + +const GradingFlex: React.FC = ({ justifyContent, alignItems, flexDirection, children, style, className, }: GradingFlexProps) => { + const defaultStyle: React.CSSProperties = { + display: "flex", + justifyContent: + (justifyContent === "justify-start" + ? "flex-start" + : justifyContent === "justify-end" + ? "flex-end" + : justifyContent === "justify-center" + ? "center" + : justifyContent === "justify-between" + ? "space-between" + : justifyContent === "justify-around" + ? "space-around" + : justifyContent === "justify-evenly" + ? "space-evenly" + : "" + ), + alignItems: + (alignItems === "items-start" + ? "start" + : alignItems === "items-end" + ? "end" + : alignItems === "items-center" + ? "center" + : alignItems === "items-baseline" + ? "baseline" + : "" + ), + flexDirection: flexDirection, + }; + + return ( +
+ {children} +
+ ); +} + +export default GradingFlex; \ No newline at end of file diff --git a/src/commons/grading/GradingText.tsx b/src/commons/grading/GradingText.tsx new file mode 100644 index 0000000000..9cf58440da --- /dev/null +++ b/src/commons/grading/GradingText.tsx @@ -0,0 +1,26 @@ +import { Text } from "@blueprintjs/core"; + +type GradingTextProps = { + children?: React.ReactNode; + style?: React.CSSProperties; + secondaryText?: boolean; + className?: string; +} & React.RefAttributes; + +const GradingText: React.FC = ({ children, style, secondaryText, className = "", }: GradingTextProps) => { + const defaultStyle: React.CSSProperties = { + width: "max-content", + margin: "auto 0" + }; + + return ( + + {children} + + ); +} + +export default GradingText; \ No newline at end of file diff --git a/src/commons/workspace/WorkspaceActions.ts b/src/commons/workspace/WorkspaceActions.ts index ddca7a04e7..9c63f9186c 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, @@ -63,6 +64,7 @@ import { UPDATE_CURRENTSTEP, UPDATE_EDITOR_BREAKPOINTS, UPDATE_EDITOR_VALUE, + UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, UPDATE_STEPSTOTAL, @@ -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 7e30ff1ec7..16680eddbc 100644 --- a/src/commons/workspace/WorkspaceReducer.ts +++ b/src/commons/workspace/WorkspaceReducer.ts @@ -76,6 +76,7 @@ import { UPDATE_CURRENTSTEP, UPDATE_EDITOR_BREAKPOINTS, UPDATE_EDITOR_VALUE, + UPDATE_GRADING_COLUMN_VISIBILITY, UPDATE_HAS_UNSAVED_CHANGES, UPDATE_REPL_VALUE, UPDATE_STEPSTOTAL, @@ -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 3f3722b3b9..00646c728c 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_CSE = 'TOGGLE_USING_CSE'; export const TOGGLE_UPDATE_CSE = 'TOGGLE_UPDATE_CSE'; 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/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/GradingBadges.tsx b/src/pages/academy/grading/subcomponents/GradingBadges.tsx index 32df016ca3..a1d6ade271 100644 --- a/src/pages/academy/grading/subcomponents/GradingBadges.tsx +++ b/src/pages/academy/grading/subcomponents/GradingBadges.tsx @@ -95,4 +95,32 @@ const FilterBadge: React.FC = ({ 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..33bd0e3d93 --- /dev/null +++ b/src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx @@ -0,0 +1,28 @@ +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..13d1d7f79d 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -1,6 +1,8 @@ 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 { Icon as BpIcon } from '@blueprintjs/core'; +import { Button, H6, Icon as BpIcon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { Column, @@ -14,9 +16,6 @@ import { useReactTable } from '@tanstack/react-table'; import { - Bold, - Button, - Flex, Footer, Table, TableBody, @@ -24,19 +23,27 @@ import { TableHead, TableHeaderCell, TableRow, - Text, 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'; +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 +91,13 @@ const makeColumns = (handleClick: () => void) => [ cell: info => { const { currentXp, xpBonus, maxXp } = info.getValue(); return ( - - + + {currentXp} (+{xpBonus}) - - / - {maxXp} - + + / + {maxXp} + ); } }), @@ -117,13 +124,169 @@ 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); 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 +348,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]); @@ -197,20 +380,68 @@ 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 ? ( + + + 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} /> - +
+ + {/* End of Original Code */} + +
+ +
+ + {/* Start of Original Code */} + {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())} + + ) + )} ))}
- +
- - <> - + ); }; @@ -296,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); @@ -309,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 6058cc9477..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; } @@ -510,7 +514,8 @@ } .grading-overview-filterable-btns { - padding: 0; + padding: 0 2px; + text-align: inherit; // Hides overflowed text for the buttons &, @@ -528,7 +533,7 @@ -0.875rem for icon margins -1.125rem for additional padding */ - p { + p, &:has(> span) > span { max-width: calc(20vw - 16px - 2rem); } } @@ -540,7 +545,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 +557,10 @@ } } +.grading-overview-filterable-btns, .grading-overview-rounded-btns { + border-radius: 9999px; +} + .grading-overview-footer-sibling { // Footer component ~ div ~ div { @@ -563,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