Skip to content

Commit

Permalink
Feat: LCFS - Improve Accessibility for AG Grid Filtering Components #…
Browse files Browse the repository at this point in the history
  • Loading branch information
prv-proton committed Jan 2, 2025
1 parent 64b5b3d commit b2f4d24
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import CheckBoxIcon from '@mui/icons-material/CheckBox'

const icon = <CheckBoxOutlineBlankIcon fontSize="small" />
const checkedIcon = <CheckBoxIcon fontSize="small" />

/**
* @deprecated
*/
export const BCColumnSetFilter = forwardRef((props, ref) => {
const { apiQuery, params } = props
const [options, setOptions] = useState([])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useState, useEffect, useCallback } from 'react'
import { FormControl, IconButton, InputAdornment } from '@mui/material'
import {
Clear as ClearIcon,
CalendarToday as CalendarIcon
} from '@mui/icons-material'
import { DatePicker } from '@mui/x-date-pickers'
import { format, isValid } from 'date-fns'
import { getDateFromDateSections } from '@mui/x-date-pickers/internals/hooks/useField/useField.utils'

export const BCDateFloatingFilter = ({
model,
onModelChange,
disabled = false,
initialFilterType = 'equals',
label = 'Select Date'
}) => {
const [selectedDate, setSelectedDate] = useState(null)
const [open, setOpen] = useState(false)

const handleChange = useCallback((newDate) => {
setSelectedDate(newDate)

if (newDate && isValid(newDate)) {
onModelChange({
type: initialFilterType,
dateFrom: format(newDate, 'yyyy-MM-dd'),
dateTo: null,
filterType: 'date'
})
} else {
onModelChange(null)
}
}, [])

const handleClear = (event) => {
event.stopPropagation()
setSelectedDate(null)
onModelChange(null)
}

const handleOpen = () => {
setOpen(true)
}

const handleClose = () => {
setOpen(false)
}

useEffect(() => {
if (!model) {
setSelectedDate(null)
return
}

if (model.filter) {
const date = new Date(model.dateFrom)
setSelectedDate(isValid(date) ? date : null)
}
}, [model])

return (
<FormControl
className="bc-column-date-filter"
fullWidth
size="small"
sx={{
border: 'none',
'& .MuiOutlinedInput-root': { p: 0 },
'& .MuiOutlinedInput-notchedOutline': { border: 'none' },
'& .Mui-focused': {
border: '1px solid #495057',
boxShadow: '0 0 0 1px #495057'
}
}}
>
<DatePicker
sx={{ border: 'none', borderBottom: '2px solid #495057' }}
value={selectedDate}
onChange={handleChange}
open={open}
onOpen={handleOpen}
onClose={handleClose}
disabled={disabled}
format="yyyy-MM-dd"
slotProps={{
textField: {
size: 'small',
label,
InputProps: {
startAdornment: (
<InputAdornment position="start">
<IconButton
size="small"
edge="start"
onClick={() => setOpen(true)}
>
<CalendarIcon fontSize="small" />
</IconButton>
</InputAdornment>
),
endAdornment: selectedDate && (
<InputAdornment position="end">
<IconButton
size="small"
onClick={handleClear}
onMouseDown={(event) => event.stopPropagation()}
edge="end"
>
<ClearIcon fontSize="small" />
</IconButton>
</InputAdornment>
)
}
}
}}
/>
</FormControl>
)
}

BCDateFloatingFilter.displayName = 'BCDateFloatingFilter'
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useState, useCallback, useEffect } from 'react'
import { IconButton } from '@mui/material'
import { Clear as ClearIcon } from '@mui/icons-material'
const ITEM_HEIGHT = 48
const ITEM_PADDING_TOP = 8

export const BCSelectFloatingFilter = ({
model,
onModelChange,
optionsQuery,
valueKey = 'value',
labelKey = 'label',
disabled = false,
params,
initialFilterType = 'equals',
multiple = false,
initialSelectedValues = []
}) => {
const [selectedValues, setSelectedValues] = useState([])
const { data: optionsData, isLoading, isError, error } = optionsQuery(params)

const handleChange = (event) => {
const { options } = event.target
const newValues = Array.from(options)
.filter((option) => option.selected)
.map((option) => option.value)

if (!multiple) {
setSelectedValues([newValues[0] || ''])
onModelChange(
!newValues[0] || newValues[0] === '0'
? null
: {
type: initialFilterType,
filter: newValues[0]
}
)
} else {
setSelectedValues(newValues)
onModelChange({
type: initialFilterType,
filter: newValues
})
}
}

const handleClear = (event) => {
event.stopPropagation()
setSelectedValues([])
onModelChange(null)
}

const renderSelectContent = useCallback(() => {
if (isLoading) {
return (
<option disabled value="">
Loading...
</option>
)
}

if (isError) {
return (
<option disabled value="">
Error loading options: {error?.message}
</option>
)
}

return (optionsData || []).map((option) => (
<option key={option[valueKey]} value={option[valueKey]}>
{option[labelKey]}
</option>
))
}, [isLoading, isError, optionsData, error])

useEffect(() => {
if (!model) {
setSelectedValues(initialSelectedValues)
} else {
setSelectedValues([model?.filter])
}
}, [model, initialSelectedValues])

return (
<div style={{ position: 'relative', width: '100%' }}>
<div
className="select-container"
style={{
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP
}}
>
<select
id="select-filter"
multiple={multiple}
value={selectedValues}
onChange={handleChange}
disabled={disabled || isLoading}
style={{
color: selectedValues.length > 0 ? '#999' : '#000'
}}
>
<option
value=""
disabled={!multiple}
style={{ display: multiple ? 'none' : 'block' }}
>
Select
</option>
{renderSelectContent()}
</select>
{selectedValues.length > 0 && (
<IconButton
size="small"
sx={{ mr: 2 }}
onClick={handleClear}
onMouseDown={(event) => event.stopPropagation()}
aria-label="Clear selection"
>
<ClearIcon fontSize="small" />
</IconButton>
)}
</div>
</div>
)
}

BCSelectFloatingFilter.displayName = 'BCSelectFloatingFilter'
2 changes: 2 additions & 0 deletions frontend/src/components/BCDataGrid/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export { BCPagination } from './StatusBar/BCPagination'
export { LargeTextareaEditor } from './Editors/LargeTextareaEditor'
export { TextCellEditor } from './Editors/TextCellEditor'
export { NumberEditor } from './Editors/NumberEditor'
export { BCDateFloatingFilter } from './Filters/BCDateFloatingFilter'
export { BCSelectFloatingFilter } from './Filters/BCSelectFloatingFilter'
30 changes: 30 additions & 0 deletions frontend/src/themes/base/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ const globals = {
fontWeight: 700,
color: grey[700]
},
'.select-container': {
fontFamily: "'BCSans', 'Noto Sans', 'Verdana', 'Arial', 'sans-serif'",
fontSize: '1.6rem',
width: '100%',
display: 'flex',
alignItems: 'center',
gap: '8px',
border: 'none',
borderBottom: '2px solid #495057',
borderRadius: '0px',
padding: '0px',
background: '#fff',
transition: 'border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out'
},
'.select-container:focus-within': {
borderColor: '2px solid #495057',
border: '0.01rem solid #495057',
},
'.select-container #select-filter': {
width: '100%',
padding: '11px',
border: 'none',
outline: 'none',
appearance: 'none',
background: 'transparent'
},
'.select-container option': {
fontSize: '1rem',
fontFamily: 'inherit'
},
// editor theme for ag-grid quertz theme
'.ag-theme-quartz': {
'--ag-borders': `0.5px solid ${grey[400]} !important`,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/Admin/AdminMenu/components/Users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Users = () => {
navigate(ROUTES.ADMIN_USERS_ADD)
}
const getRowId = useCallback((params) => {
return params.data.userProfileId
return params.data.userProfileId.toString()
}, [])

const handleRowClicked = useCallback((params) => {
Expand Down
36 changes: 19 additions & 17 deletions frontend/src/views/Admin/AdminMenu/components/_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import {
RoleRenderer,
StatusRenderer
} from '@/utils/grid/cellRenderers'
import { BCColumnSetFilter } from '@/components/BCDataGrid/components'
import { useRoleList } from '@/hooks/useRole'
import {
BCSelectFloatingFilter,
BCDateFloatingFilter
} from '@/components/BCDataGrid/components/index'

export const usersColumnDefs = (t) => [
{
Expand Down Expand Up @@ -44,14 +47,12 @@ export const usersColumnDefs = (t) => [
},
suppressFilterButton: true
},
floatingFilterComponent: BCColumnSetFilter,
floatingFilterComponent: BCSelectFloatingFilter,
floatingFilterComponentParams: {
apiQuery: useRoleList, // all data returned should be an array which includes an object of key 'name'
// Eg: [{id: 1, name: 'EntryListItem' }] except name all others are optional
optionsQuery: useRoleList,
params: 'government_roles_only=true',
key: 'admin-users',
disableCloseOnSelect: false,
multiple: false
valueKey: 'name',
labelKey: 'name'
},
cellRenderer: RoleRenderer,
cellClass: 'vertical-middle'
Expand Down Expand Up @@ -84,17 +85,17 @@ export const usersColumnDefs = (t) => [
},
cellRenderer: StatusRenderer,
cellClass: 'vertical-middle',
floatingFilterComponent: BCColumnSetFilter,
floatingFilterComponent: BCSelectFloatingFilter,
floatingFilterComponentParams: {
apiQuery: () => ({
optionsQuery: () => ({
data: [
{ id: 1, name: t('admin:userColLabels.active') },
{ id: 0, name: t('admin:userColLabels.inactive') }
],
isLoading: false
}),
disableCloseOnSelect: false,
multiple: false
valueKey: 'name',
labelKey: 'name'
},
minWidth: 120,
suppressHeaderMenuButton: false
Expand Down Expand Up @@ -169,6 +170,13 @@ export const userLoginHistoryColDefs = (t) => [
buttons: ['clear']
}
},
{
field: 'createDate',
headerName: t('admin:userLoginHistoryColLabels.createDate'),
cellDataType: 'dateString',
valueFormatter: timezoneFormatter,
floatingFilterComponent: BCDateFloatingFilter
},
{
field: 'keycloakEmail',
headerName: t('admin:userLoginHistoryColLabels.keycloakEmail'),
Expand All @@ -193,12 +201,6 @@ export const userLoginHistoryColDefs = (t) => [
field: 'loginErrorMessage',
headerName: t('admin:userLoginHistoryColLabels.loginErrorMessage'),
cellDataType: 'string'
},
{
field: 'createDate',
headerName: t('admin:userLoginHistoryColLabels.createDate'),
cellDataType: 'dateString',
valueFormatter: timezoneFormatter
}
]

Expand Down
Loading

0 comments on commit b2f4d24

Please sign in to comment.