From 76f21689d78cd1737dff9f2ca26a50efe260d8f2 Mon Sep 17 00:00:00 2001 From: Nitheesh T Ganesh Date: Wed, 29 Jan 2025 02:16:08 -0700 Subject: [PATCH 1/7] filters ui changes --- .../Shared/FilterSelect/FilterSelect.tsx | 57 +++++------------- .../Shared/FilterSelect/components/Menu.tsx | 4 +- .../components/MultiValueContainer.tsx | 48 ++++++--------- .../Shared/FilterSelect/components/Option.tsx | 15 ++++- .../components/SingleValueContainer.tsx | 37 ++++-------- .../MasterDataTable/DataTableNoData.tsx | 46 ++++++++++++++ .../MasterDataTable/MasterDataTable.tsx | 60 ++----------------- 7 files changed, 109 insertions(+), 158 deletions(-) create mode 100644 compliance-web/src/components/Shared/MasterDataTable/DataTableNoData.tsx diff --git a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx index 336e3544..27545172 100644 --- a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import Select from "react-select"; import Menu from "./components/Menu"; import Option from "./components/Option"; @@ -24,17 +24,6 @@ const FilterSelect = (props: SelectProps) => { const [menuStyle, setMenuStyle] = useState({}); const selectRef = useRef(null); - const selectAllOption = useMemo( - () => ({ - label: "Select All", - value: "", - }), - [] - ); - - const isSelectAllSelected = () => - selectedOptions.includes(selectAllOption.value); - const isOptionSelected = (o: OptionType) => isMulti ? selectedOptions.includes(o.value) : selectedOptions === o.value; @@ -50,35 +39,20 @@ const FilterSelect = (props: SelectProps) => { const { option } = actionMeta; if (option === undefined) return; - if (option.value === selectAllOption.value) { - if (isSelectAllSelected()) { - setSelectedOptions([]); - } else { - const options = [...(props.options?.map((o: any) => o.value) || [])]; - setSelectedOptions([selectAllOption.value, ...options]); - } + if (isOptionSelected(option)) { + setSelectedOptions( + selectedOptions.filter((o: string) => o !== option.value) + ); } else { - if (isOptionSelected(option)) { - setSelectedOptions( - selectedOptions.filter( - (o: string) => o !== option.value && o !== selectAllOption.value - ) - ); - } else { - let value = [...selectedOptions, option.value]; - value = Array.from(new Set(value)); - setSelectedOptions(value || []); - } + let value = [...selectedOptions, option.value]; + value = Array.from(new Set(value)); + setSelectedOptions(value || []); } }; const applyFilters = () => { if (props.filterAppliedCallback) { - const options = isMulti - ? (selectedOptions as string[]).filter( - (p) => p !== selectAllOption.value - ) - : selectedOptions; + const options = selectedOptions; props.filterAppliedCallback(options); } if (selectedOptions.length === 0) { @@ -146,10 +120,8 @@ const FilterSelect = (props: SelectProps) => { }, [isMulti, menuIsOpen, selectValue]); useEffect(() => { - let filterOptions = props.options as OptionType[]; - if (isMulti) filterOptions = [selectAllOption, ...filterOptions]; - setOptions(filterOptions); - }, [isMulti, props.options, selectAllOption]); + setOptions(props.options as OptionType[]); + }, [props.options]); const isSearchable = () => { if (props.isSearchable !== undefined) return props.isSearchable; @@ -229,11 +201,13 @@ const FilterSelect = (props: SelectProps) => { height: "2.25rem", minHeight: "2.25rem", borderWidth: "1px", - borderStyle: props.hasValue ? "none" : "solid", + // borderStyle: props.hasValue ? "none" : "solid", borderColor: props.isFocused || props.menuIsOpen ? BCDesignTokens.surfaceColorBorderActive - : BCDesignTokens.surfaceColorBorderDefault, + : props.hasValue + ? BCDesignTokens.themeBlue20 + : BCDesignTokens.surfaceColorBorderDefault, boxShadow: "none", ...(props.selectProps.filterProps?.variant === "bar" && { borderColor: props.isFocused @@ -247,6 +221,7 @@ const FilterSelect = (props: SelectProps) => { marginBlock: "0px", border: `1px solid ${BCDesignTokens.surfaceColorBorderDefault}`, borderRadius: "4px", + py: "0.25rem", ...menuStyle, }), placeholder: (base, props) => ({ diff --git a/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx b/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx index cb1a5646..99caec72 100644 --- a/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx @@ -3,7 +3,7 @@ import { BCDesignTokens } from "epic.theme"; import { components, MenuProps } from "react-select"; const Menu = (props: MenuProps) => { - const { filterProps } = props.selectProps; + const { filterProps } = props.selectProps; return ( <> { }} > - {props.isMulti && props.options.length > 5 && ( + {props.isMulti && props.options.length >= 7 && ( { const { filterProps } = props.selectProps; return ( - <> - {props.index === 0 && props.selectProps.value && ( - + {props.index === 0 && Array.isArray(props.selectProps.value) && ( + - - {filterProps?.variant === "inline" - ? "Filtered" - : `${props.selectProps.placeholder} (${ - (props.selectProps.value as []).length - })`} - - + {filterProps?.variant === "inline" + ? "Filtered" + : `${props.selectProps.placeholder} (${props.selectProps.value.length})`} + )} {props.index === 0 && !filterProps?.selectedOptions && ( { {props.selectProps.placeholder} )} - + ); }; diff --git a/compliance-web/src/components/Shared/FilterSelect/components/Option.tsx b/compliance-web/src/components/Shared/FilterSelect/components/Option.tsx index a7d91420..60874aaa 100644 --- a/compliance-web/src/components/Shared/FilterSelect/components/Option.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/components/Option.tsx @@ -3,6 +3,7 @@ import { components, OptionProps } from "react-select"; import Checkbox from "@mui/material/Checkbox"; import { OptionType } from "../type"; import { Box, Radio } from "@mui/material"; +import { BCDesignTokens } from "epic.theme"; const Option = ({ getStyles, @@ -32,13 +33,25 @@ const Option = ({ isDisabled={isDisabled} isFocused={isFocused} isSelected={isSelected} - getStyles={getStyles} + getStyles={(base, state) => ({ + ...getStyles(base, state), + cursor: 'pointer', + padding: "0.5rem 1rem", + whiteSpace: "nowrap", + })} innerProps={innerProps} > {isMulti && ( )} {!isMulti && ( diff --git a/compliance-web/src/components/Shared/FilterSelect/components/SingleValueContainer.tsx b/compliance-web/src/components/Shared/FilterSelect/components/SingleValueContainer.tsx index 62ef2f6f..c4459e89 100644 --- a/compliance-web/src/components/Shared/FilterSelect/components/SingleValueContainer.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/components/SingleValueContainer.tsx @@ -1,40 +1,23 @@ import { SingleValueProps, components } from "react-select"; -import { Box, Typography } from "@mui/material"; -import { css as emotionCss } from "@emotion/react"; -import clsx from "clsx"; +import { Typography } from "@mui/material"; import { BCDesignTokens } from "epic.theme"; const SingleValue = (props: SingleValueProps) => { return ( {props.selectProps.value ? ( - - - {props.selectProps.filterProps?.variant === "inline" - ? "Filtered" - : props.selectProps.placeholder} - - + {props.selectProps.filterProps?.variant === "inline" + ? "Filtered" + : props.selectProps.placeholder} + ) : ( { + const { table } = props; + return ( + + + + + + No results found + + {table.options.data.length > 0 && ( + + Adjust your parameters and try again + + )} + + + + ); +}; + +export default DataTableNoData; diff --git a/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx b/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx index b03be099..caaec248 100644 --- a/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx +++ b/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx @@ -7,64 +7,12 @@ import { MRT_TableOptions, useMaterialReactTable, } from "material-react-table"; -import { - Box, - Button, - Container, - IconButton, - Tooltip, - Typography, -} from "@mui/material"; +import { Box, Button, IconButton, Tooltip, Typography } from "@mui/material"; import { FiltersCache } from "./FiltersCache"; import { exportToCsv } from "./utils"; import { BCDesignTokens } from "epic.theme"; -import { - AddRounded, - DownloadRounded, - SearchRounded, -} from "@mui/icons-material"; - -const NoDataComponent = ({ ...props }) => { - const { table } = props; - return ( - - - - - - No results found - - {table.options.data.length > 0 && ( - - Adjust your parameters and try again - - )} - - - - ); -}; +import { AddRounded, DownloadRounded } from "@mui/icons-material"; +import DataTableNoData from "./DataTableNoData"; interface MRT_EAO_TitleToolbarProps { tableTitle: string; @@ -243,7 +191,7 @@ const MasterDataTable = ({ }); }, }, - renderEmptyRowsFallback: ({ table }) => , + renderEmptyRowsFallback: ({ table }) => , renderTopToolbarCustomActions: ({ table }) => { return ( <> From 4471afedb5eb2c383d20285d55eb00ab01a892c6 Mon Sep 17 00:00:00 2001 From: Nitheesh T Ganesh Date: Wed, 29 Jan 2025 05:30:13 -0700 Subject: [PATCH 2/7] Removing confirmation buttons - filters are applied dynamically --- .../Shared/FilterSelect/FilterSelect.tsx | 86 +++++++++++-------- .../Shared/FilterSelect/components/Menu.tsx | 26 ------ 2 files changed, 48 insertions(+), 64 deletions(-) diff --git a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx index 27545172..ba9c9b71 100644 --- a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import Select from "react-select"; import Menu from "./components/Menu"; import Option from "./components/Option"; @@ -24,33 +24,14 @@ const FilterSelect = (props: SelectProps) => { const [menuStyle, setMenuStyle] = useState({}); const selectRef = useRef(null); - const isOptionSelected = (o: OptionType) => - isMulti ? selectedOptions.includes(o.value) : selectedOptions === o.value; - - const handleChange = (newValue: any, actionMeta: any) => { - if (!isMulti) { - if (isOptionSelected(newValue)) { - setSelectedOptions(""); - } else { - setSelectedOptions(newValue.value); - } - return; - } - const { option } = actionMeta; - if (option === undefined) return; - - if (isOptionSelected(option)) { - setSelectedOptions( - selectedOptions.filter((o: string) => o !== option.value) - ); - } else { - let value = [...selectedOptions, option.value]; - value = Array.from(new Set(value)); - setSelectedOptions(value || []); - } - }; + const isOptionSelected = useCallback( + (o: OptionType) => + isMulti ? selectedOptions.includes(o.value) : selectedOptions === o.value, + [isMulti, selectedOptions] + ); - const applyFilters = () => { + const applyFilters = useCallback(() => { + if (!selectedOptions) return; if (props.filterAppliedCallback) { const options = selectedOptions; props.filterAppliedCallback(options); @@ -69,9 +50,39 @@ const FilterSelect = (props: SelectProps) => { ); setSelectValue(value); } - setMenuIsOpen(false); - selectRef.current?.blur(); - }; + // setMenuIsOpen(false); + // selectRef.current?.blur(); + }, [props, selectedOptions, isMulti, options]); + + const handleChange = useCallback( + (newValue: any, actionMeta: any) => { + if (!isMulti) { + if (isOptionSelected(newValue)) { + setSelectedOptions(""); + } else { + setSelectedOptions(newValue.value); + } + return; + } + const { option } = actionMeta; + if (option === undefined) return; + + if (isOptionSelected(option)) { + setSelectedOptions( + selectedOptions.filter((o: string) => o !== option.value) + ); + } else { + let value = [...selectedOptions, option.value]; + value = Array.from(new Set(value)); + setSelectedOptions(value || []); + } + }, + [isMulti, isOptionSelected, selectedOptions] + ); + + useEffect(() => { + applyFilters(); + }, [selectedOptions, applyFilters]); const clearFilters = () => { setSelectedOptions([]); @@ -83,10 +94,10 @@ const FilterSelect = (props: SelectProps) => { }; const onCancel = () => { - const currentValues = isMulti - ? selectValue.map((v: OptionType) => v.value) - : selectValue.value; - setSelectedOptions(currentValues || isMulti ? [] : ""); + // const currentValues = isMulti + // ? selectValue.map((v: OptionType) => v.value) + // : selectValue.value; + // setSelectedOptions(currentValues ?? isMulti ? [] : ""); setMenuIsOpen(false); selectRef.current?.blur(); }; @@ -100,7 +111,7 @@ const FilterSelect = (props: SelectProps) => { if (rightEdgeOfMenu > windowWidth) { const overflow = rightEdgeOfMenu - windowWidth; const newPosition = { - transform: `translateX(${-(overflow + 80)}px)`, + transform: `translateX(${-(overflow + 50)}px)`, }; setMenuStyle(newPosition); } else { @@ -125,11 +136,9 @@ const FilterSelect = (props: SelectProps) => { const isSearchable = () => { if (props.isSearchable !== undefined) return props.isSearchable; - if (selectValue instanceof Array) { return selectValue.length === 0; } - return !selectValue; }; @@ -214,6 +223,7 @@ const FilterSelect = (props: SelectProps) => { ? BCDesignTokens.surfaceColorBorderActive : "transparent", }), + cursor: "pointer", }), menu: (base) => ({ ...base, @@ -221,7 +231,7 @@ const FilterSelect = (props: SelectProps) => { marginBlock: "0px", border: `1px solid ${BCDesignTokens.surfaceColorBorderDefault}`, borderRadius: "4px", - py: "0.25rem", + paddingBottom: "0.25rem", ...menuStyle, }), placeholder: (base, props) => ({ diff --git a/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx b/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx index 99caec72..00534e9a 100644 --- a/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/components/Menu.tsx @@ -44,32 +44,6 @@ const Menu = (props: MenuProps) => { {props.children} - - - - From 0f8df026dd1ead0eee5d5ee74ff4b6faa621c129 Mon Sep 17 00:00:00 2001 From: Nitheesh T Ganesh Date: Thu, 30 Jan 2025 13:09:00 -0700 Subject: [PATCH 3/7] moved TableFilter to master datatable --- .../Profile/CaseFileComplaintsTable.tsx | 53 +-------------- .../Profile/CaseFileInspectionsTable.tsx | 25 ------- .../MasterDataTable/MasterDataTable.tsx | 23 ++++++- .../src/routes/_authenticated/admin/staff.tsx | 61 ----------------- .../ce-database/case-files/index.tsx | 51 +-------------- .../ce-database/complaints/index.tsx | 65 +------------------ .../ce-database/inspections/index.tsx | 61 ----------------- 7 files changed, 27 insertions(+), 312 deletions(-) diff --git a/compliance-web/src/components/App/CaseFiles/Profile/CaseFileComplaintsTable.tsx b/compliance-web/src/components/App/CaseFiles/Profile/CaseFileComplaintsTable.tsx index 7e92e0d5..090580e8 100644 --- a/compliance-web/src/components/App/CaseFiles/Profile/CaseFileComplaintsTable.tsx +++ b/compliance-web/src/components/App/CaseFiles/Profile/CaseFileComplaintsTable.tsx @@ -1,4 +1,3 @@ -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import { useComplaintsByCaseFileId } from "@/hooks/useComplaints"; @@ -20,9 +19,7 @@ const CaseFileComplaintsTable = ({ caseFileId }: { caseFileId: number }) => { setStaffUserList( [ ...new Set( - complaints?.map( - (complaint) => complaint.primary_officer?.name ?? "" - ) + complaints?.map((complaint) => complaint.primary_officer?.name ?? "") ), ].filter(Boolean) ); @@ -90,18 +87,6 @@ const CaseFileComplaintsTable = ({ caseFileId }: { caseFileId: number }) => { }, filterVariant: "multi-select", filterSelectOptions: complaintStatusList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 100, }, { @@ -109,18 +94,6 @@ const CaseFileComplaintsTable = ({ caseFileId }: { caseFileId: number }) => { header: "Topic", filterVariant: "multi-select", filterSelectOptions: topicList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 120, }, { @@ -128,18 +101,6 @@ const CaseFileComplaintsTable = ({ caseFileId }: { caseFileId: number }) => { header: "Source", filterVariant: "multi-select", filterSelectOptions: complaintSourceList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 120, }, { @@ -148,18 +109,6 @@ const CaseFileComplaintsTable = ({ caseFileId }: { caseFileId: number }) => { header: "Primary", filterVariant: "multi-select", filterSelectOptions: staffUserList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 120, }, ], diff --git a/compliance-web/src/components/App/CaseFiles/Profile/CaseFileInspectionsTable.tsx b/compliance-web/src/components/App/CaseFiles/Profile/CaseFileInspectionsTable.tsx index fd1c9c90..ce858fdc 100644 --- a/compliance-web/src/components/App/CaseFiles/Profile/CaseFileInspectionsTable.tsx +++ b/compliance-web/src/components/App/CaseFiles/Profile/CaseFileInspectionsTable.tsx @@ -1,4 +1,3 @@ -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import { useInspectionsByCaseFileId } from "@/hooks/useInspections"; @@ -76,18 +75,6 @@ const CaseFileInspectionsTable = ({ caseFileId }: { caseFileId: number }) => { }, filterVariant: "multi-select", filterSelectOptions: inspectionStatusList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 100, }, // TODO: Add map correct values for the next 3 columns @@ -112,18 +99,6 @@ const CaseFileInspectionsTable = ({ caseFileId }: { caseFileId: number }) => { header: "Primary", filterVariant: "multi-select", filterSelectOptions: staffUserList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 120, }, ], diff --git a/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx b/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx index caaec248..0263aea0 100644 --- a/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx +++ b/compliance-web/src/components/Shared/MasterDataTable/MasterDataTable.tsx @@ -6,6 +6,8 @@ import { MRT_TableInstance, MRT_TableOptions, useMaterialReactTable, + MRT_Column, + MRT_Header, } from "material-react-table"; import { Box, Button, IconButton, Tooltip, Typography } from "@mui/material"; import { FiltersCache } from "./FiltersCache"; @@ -13,6 +15,7 @@ import { exportToCsv } from "./utils"; import { BCDesignTokens } from "epic.theme"; import { AddRounded, DownloadRounded } from "@mui/icons-material"; import DataTableNoData from "./DataTableNoData"; +import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; interface MRT_EAO_TitleToolbarProps { tableTitle: string; @@ -60,7 +63,25 @@ const MasterDataTable = ({ }; const table = useMaterialReactTable({ - columns: columns, + columns: columns.map((column) => ({ + ...column, + ...(column.filterSelectOptions && + column.filterVariant === "multi-select" && { + Filter: (props: { + column: MRT_Column; + header: MRT_Header; + }) => ( + + ), + }), + })), data: data, globalFilterFn: "contains", enableHiding: false, diff --git a/compliance-web/src/routes/_authenticated/admin/staff.tsx b/compliance-web/src/routes/_authenticated/admin/staff.tsx index c339df98..aea37028 100644 --- a/compliance-web/src/routes/_authenticated/admin/staff.tsx +++ b/compliance-web/src/routes/_authenticated/admin/staff.tsx @@ -1,5 +1,4 @@ import StaffModal from "@/components/App/Staff/StaffModal"; -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import ConfirmationModal from "@/components/Shared/Popups/ConfirmationModal"; @@ -135,18 +134,6 @@ export function Staff() { header: "Position", filterVariant: "multi-select", filterSelectOptions: positionList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => row.supervisor?.name, @@ -154,18 +141,6 @@ export function Staff() { header: "Supervisor", filterVariant: "multi-select", filterSelectOptions: supervisorList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => row.deputy_director?.name, @@ -173,36 +148,12 @@ export function Staff() { header: "Deputy Director", filterVariant: "multi-select", filterSelectOptions: deputyList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorKey: "permission", header: "Permission Level", filterVariant: "multi-select", filterSelectOptions: permissionList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => (row.is_active ? "Active" : "Inactive"), @@ -224,18 +175,6 @@ export function Staff() { filterVariant: "multi-select", filterSelectOptions: ["Active", "Inactive"], filterValue: ["Active"], - Filter: ({ header, column }) => { - return ( - - ); - }, }, ], [deputyList, permissionList, positionList, supervisorList] diff --git a/compliance-web/src/routes/_authenticated/ce-database/case-files/index.tsx b/compliance-web/src/routes/_authenticated/ce-database/case-files/index.tsx index 73b7b34c..7e5364c7 100644 --- a/compliance-web/src/routes/_authenticated/ce-database/case-files/index.tsx +++ b/compliance-web/src/routes/_authenticated/ce-database/case-files/index.tsx @@ -1,5 +1,4 @@ import CaseFileDrawer from "@/components/App/CaseFiles/CaseFileDrawer"; -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import PageLink from "@/components/Shared/PageLink"; @@ -111,36 +110,12 @@ export function CaseFiles() { header: "Project", filterVariant: "multi-select", filterSelectOptions: projectList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorKey: "initiation.name", header: "Initiation", filterVariant: "multi-select", filterSelectOptions: initiationList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => dateUtils.formatDate(row.date_created), @@ -168,18 +143,6 @@ export function CaseFiles() { }, filterVariant: "multi-select", filterSelectOptions: statusList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => row.primary_officer?.name, @@ -187,18 +150,6 @@ export function CaseFiles() { header: "Primary", filterVariant: "multi-select", filterSelectOptions: staffUserList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, ], [initiationList, projectList, staffUserList, statusList] @@ -225,7 +176,7 @@ export function CaseFiles() { tableTitle: "Case Files", tableAddRecordButtonText: "Case File", tableAddRecordFunction: handleOpenModal, - tableAddRecordButtonVisibility: showCreateCaseFileButton + tableAddRecordButtonVisibility: showCreateCaseFileButton, }} /> diff --git a/compliance-web/src/routes/_authenticated/ce-database/complaints/index.tsx b/compliance-web/src/routes/_authenticated/ce-database/complaints/index.tsx index 74d98c2f..8ef01d76 100644 --- a/compliance-web/src/routes/_authenticated/ce-database/complaints/index.tsx +++ b/compliance-web/src/routes/_authenticated/ce-database/complaints/index.tsx @@ -1,4 +1,3 @@ -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import PageLink from "@/components/Shared/PageLink"; @@ -28,7 +27,9 @@ export function Complaints() { useEffect(() => { setProjectList( [ - ...new Set(complaintsList?.map((comp) => comp.case_file?.project?.name ?? "")), + ...new Set( + complaintsList?.map((comp) => comp.case_file?.project?.name ?? "") + ), ].filter(Boolean) ); setTopicList( @@ -78,36 +79,12 @@ export function Complaints() { header: "Project", filterVariant: "multi-select", filterSelectOptions: projectList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorFn: (row) => row.requirement_detail?.topic?.name, header: "Topic", filterVariant: "multi-select", filterSelectOptions: topicList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 150, }, { @@ -121,18 +98,6 @@ export function Complaints() { header: "Complaint Source", filterVariant: "multi-select", filterSelectOptions: complaintSourceList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 150, }, { @@ -140,18 +105,6 @@ export function Complaints() { header: "Primary", filterVariant: "multi-select", filterSelectOptions: officerList, - Filter: ({ header, column }) => { - return ( - - ); - }, }, { accessorKey: "status", @@ -174,18 +127,6 @@ export function Complaints() { }, filterVariant: "multi-select", filterSelectOptions: statusList, - Filter: ({ header, column }) => { - return ( - - ); - }, size: 150, }, { diff --git a/compliance-web/src/routes/_authenticated/ce-database/inspections/index.tsx b/compliance-web/src/routes/_authenticated/ce-database/inspections/index.tsx index 72c634aa..549efac0 100644 --- a/compliance-web/src/routes/_authenticated/ce-database/inspections/index.tsx +++ b/compliance-web/src/routes/_authenticated/ce-database/inspections/index.tsx @@ -1,4 +1,3 @@ -import TableFilter from "@/components/Shared/FilterSelect/TableFilter"; import MasterDataTable from "@/components/Shared/MasterDataTable/MasterDataTable"; import { searchFilter } from "@/components/Shared/MasterDataTable/utils"; import PageLink from "@/components/Shared/PageLink"; @@ -87,32 +86,12 @@ export function Inspections() { header: "Project", filterVariant: "multi-select", filterSelectOptions: projectList, - Filter: ({ header, column }) => ( - - ), }, { accessorKey: "ir_status.name", header: "Stage", filterVariant: "multi-select", filterSelectOptions: irStatusList, - Filter: ({ header, column }) => ( - - ), size: 120, }, { @@ -120,16 +99,6 @@ export function Inspections() { header: "Type", filterVariant: "multi-select", filterSelectOptions: irTypeList, - Filter: ({ header, column }) => ( - - ), size: 120, }, { @@ -137,16 +106,6 @@ export function Inspections() { header: "Initiation", filterVariant: "multi-select", filterSelectOptions: initiationList, - Filter: ({ header, column }) => ( - - ), }, { accessorKey: "inspection_status", @@ -169,16 +128,6 @@ export function Inspections() { }, filterVariant: "multi-select", filterSelectOptions: inspectionStatusList, - Filter: ({ header, column }) => ( - - ), size: 120, }, { @@ -187,16 +136,6 @@ export function Inspections() { header: "Primary", filterVariant: "multi-select", filterSelectOptions: staffUserList, - Filter: ({ header, column }) => ( - - ), }, { accessorKey: "case_file.case_file_number", From d612ab0f2f056ce7a1a4720c390c653f4e730ed8 Mon Sep 17 00:00:00 2001 From: Nitheesh T Ganesh Date: Thu, 30 Jan 2025 15:50:57 -0700 Subject: [PATCH 4/7] COMP-250: All filters - alphabetical sort order --- .../src/components/Shared/FilterSelect/TableFilter.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compliance-web/src/components/Shared/FilterSelect/TableFilter.tsx b/compliance-web/src/components/Shared/FilterSelect/TableFilter.tsx index 6db626c9..4cba14c6 100644 --- a/compliance-web/src/components/Shared/FilterSelect/TableFilter.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/TableFilter.tsx @@ -42,6 +42,9 @@ const makeTableFilter = } ) => toOptionType(option) ); + filterOptions.sort((a: { label: string }, b: { label: string }) => + a.label.localeCompare(b.label) + ); return filterOptions; }, [column]); From 2a6706b406fbc870d2260215c70ec22537e058d7 Mon Sep 17 00:00:00 2001 From: Nitheesh T Ganesh Date: Fri, 31 Jan 2025 15:42:38 -0700 Subject: [PATCH 5/7] COMP-120: Staff Table - Changes to Filters --- compliance-web/package-lock.json | 57 ++--- compliance-web/package.json | 2 +- .../Shared/FilterSelect/FilterSelect.tsx | 209 ++++++++++-------- .../Shared/FilterSelect/TableFilter.tsx | 5 +- .../components/MultiValueContainer.tsx | 37 ++-- .../components/Shared/FilterSelect/type.ts | 2 - .../MasterDataTable/MasterDataTable.tsx | 40 ++-- .../routes/_authenticated/admin/agencies.tsx | 7 +- .../src/routes/_authenticated/admin/staff.tsx | 5 +- .../routes/_authenticated/admin/topics.tsx | 4 +- .../ce-database/case-files/index.tsx | 5 +- .../ce-database/complaints/index.tsx | 7 +- .../ce-database/inspections/index.tsx | 6 +- 13 files changed, 195 insertions(+), 191 deletions(-) diff --git a/compliance-web/package-lock.json b/compliance-web/package-lock.json index 5990d429..fdae3390 100644 --- a/compliance-web/package-lock.json +++ b/compliance-web/package-lock.json @@ -27,7 +27,7 @@ "json-2-csv": "^5.5.5", "jwt-decode": "^4.0.0", "keycloak-js": "^25.0.1", - "material-react-table": "^2.13.1", + "material-react-table": "^2.13.3", "oidc-client-ts": "^3.0.1", "quill": "^2.0.2", "react": "^18.2.0", @@ -3938,9 +3938,10 @@ } }, "node_modules/@tanstack/match-sorter-utils": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz", - "integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==", + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", "dependencies": { "remove-accents": "0.5.0" }, @@ -4041,11 +4042,12 @@ } }, "node_modules/@tanstack/react-table": { - "version": "8.19.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz", - "integrity": "sha512-MtgPZc4y+cCRtU16y1vh1myuyZ2OdkWgMEBzyjYsoMWMicKZGZvcDnub3Zwb6XF2pj9iRMvm1SO1n57lS0vXLw==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "license": "MIT", "dependencies": { - "@tanstack/table-core": "8.19.3" + "@tanstack/table-core": "8.20.5" }, "engines": { "node": ">=12" @@ -4060,11 +4062,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.3.tgz", - "integrity": "sha512-9ICwbDUUzN99CJIGc373i8NLoj6zFTKI2Hlcmo0+lCSAhPQ5mxq4dGOMKmLYoEFyHcGQ64Bd6ZVbnPpM6lNK5w==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.6.tgz", + "integrity": "sha512-xaSy6uUxB92O8mngHZ6CvbhGuqxQ5lIZWCBy+FjhrbHmOwc6BnOnKkYm2FsB1/BpKw/+FVctlMbEtI+F6I1aJg==", + "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.8.3" + "@tanstack/virtual-core": "3.10.6" }, "funding": { "type": "github", @@ -4169,9 +4172,10 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.19.3", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.3.tgz", - "integrity": "sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -4181,9 +4185,10 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.3.tgz", - "integrity": "sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.6.tgz", + "integrity": "sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -8283,13 +8288,14 @@ } }, "node_modules/material-react-table": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.13.1.tgz", - "integrity": "sha512-3iWwCa24ogxwllP4+W11euR/GV6f5wQE5FEilJ72/H3hDYHgsN+XehANytaG0G7/qy/OWYE7oXkcsRUU35I/iA==", + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.13.3.tgz", + "integrity": "sha512-xeyAEG6UYG3qgBIo17epAP5zsWT1pH0uCEkaUxvhki9sGcP35OqfOMSZJNhISvmqEqXKYHdqKbZI6iOwsg1sYA==", + "license": "MIT", "dependencies": { - "@tanstack/match-sorter-utils": "8.15.1", - "@tanstack/react-table": "8.19.3", - "@tanstack/react-virtual": "3.8.3", + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.5", + "@tanstack/react-virtual": "3.10.6", "highlight-words": "1.2.2" }, "engines": { @@ -9450,7 +9456,8 @@ "node_modules/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" }, "node_modules/request-progress": { "version": "3.0.0", diff --git a/compliance-web/package.json b/compliance-web/package.json index dfa06cc9..ebd6bc44 100644 --- a/compliance-web/package.json +++ b/compliance-web/package.json @@ -31,7 +31,7 @@ "json-2-csv": "^5.5.5", "jwt-decode": "^4.0.0", "keycloak-js": "^25.0.1", - "material-react-table": "^2.13.1", + "material-react-table": "^2.13.3", "oidc-client-ts": "^3.0.1", "quill": "^2.0.2", "react": "^18.2.0", diff --git a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx index ba9c9b71..74257be1 100644 --- a/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx +++ b/compliance-web/src/components/Shared/FilterSelect/FilterSelect.tsx @@ -1,6 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useCallback, useEffect, useRef, useState } from "react"; -import Select from "react-select"; +import { useCallback, useEffect, useRef, useState, useMemo } from "react"; +import Select, { + ControlProps, + CSSObjectWithLabel, + GroupBase, + OptionProps, + PlaceholderProps, +} from "react-select"; import Menu from "./components/Menu"; import Option from "./components/Option"; import MultiValue from "./components/MultiValueContainer"; @@ -9,8 +15,9 @@ import SingleValue from "./components/SingleValueContainer"; import DropdownIndicator from "./components/DropDownIndicator"; import { useTheme } from "@mui/material"; import { BCDesignTokens } from "epic.theme"; +import React from "react"; -const FilterSelect = (props: SelectProps) => { +const FilterSelect = React.memo((props: SelectProps) => { const theme = useTheme(); const { name, isMulti, defaultValue } = props; const standardDefault = isMulti ? [] : ""; @@ -50,8 +57,6 @@ const FilterSelect = (props: SelectProps) => { ); setSelectValue(value); } - // setMenuIsOpen(false); - // selectRef.current?.blur(); }, [props, selectedOptions, isMulti, options]); const handleChange = useCallback( @@ -94,15 +99,11 @@ const FilterSelect = (props: SelectProps) => { }; const onCancel = () => { - // const currentValues = isMulti - // ? selectValue.map((v: OptionType) => v.value) - // : selectValue.value; - // setSelectedOptions(currentValues ?? isMulti ? [] : ""); setMenuIsOpen(false); selectRef.current?.blur(); }; - const adjustDropdownPosition = () => { + const adjustDropdownPosition = useCallback(() => { if (menuRef?.current) { const menuRect = menuRef.current.getBoundingClientRect(); const windowWidth = window.innerWidth - 50; @@ -118,21 +119,27 @@ const FilterSelect = (props: SelectProps) => { setMenuStyle({}); } } - }; + }, []); + + const updateSelectedOptions = useCallback(() => { + const currentValues = isMulti + ? selectValue.map((v: OptionType) => v.value) + : selectValue.value; + setSelectedOptions(currentValues); + }, [isMulti, selectValue]); useEffect(() => { if (menuIsOpen) { adjustDropdownPosition(); - const currentValues = isMulti - ? selectValue.map((v: OptionType) => v.value) - : selectValue.value; - setSelectedOptions(currentValues); + updateSelectedOptions(); } - }, [isMulti, menuIsOpen, selectValue]); + }, [adjustDropdownPosition, menuIsOpen, updateSelectedOptions]); useEffect(() => { - setOptions(props.options as OptionType[]); - }, [props.options]); + if (JSON.stringify(options) !== JSON.stringify(props.options)) { + setOptions(props.options as OptionType[]); + } + }, [props.options, options]); const isSearchable = () => { if (props.isSearchable !== undefined) return props.isSearchable; @@ -152,6 +159,94 @@ const FilterSelect = (props: SelectProps) => { } }, [props.value, selectValue]); + const styles = useMemo( + () => ({ + option: ( + base: CSSObjectWithLabel, + provided: OptionProps> + ) => ({ + ...base, + whiteSpace: "normal", + overflow: "hidden", + textOverflow: "ellipsis", + display: "flex", + alignItems: "center", + padding: ".5rem .75rem .5rem 0px", + fontSize: BCDesignTokens.typographyFontSizeBody, + maxWidth: props.maxWidth ?? "100%", + background: provided.isFocused + ? BCDesignTokens.themeGray20 + : "transparent", + color: provided.isSelected + ? BCDesignTokens.themePrimaryBlue + : BCDesignTokens.themeGray90, + cursor: provided.isFocused ? "pointer" : "default", + }), + control: ( + base: CSSObjectWithLabel, + props: ControlProps> + ) => ({ + ...base, + background: props.hasValue + ? BCDesignTokens.surfaceColorBackgroundLightBlue + : BCDesignTokens.themeGrayWhite, + height: "2.25rem", + minHeight: "2.25rem", + borderWidth: "1px", + // borderStyle: props.hasValue ? "none" : "solid", + borderColor: + props.isFocused || props.menuIsOpen + ? BCDesignTokens.surfaceColorBorderActive + : props.hasValue + ? BCDesignTokens.themeBlue20 + : BCDesignTokens.surfaceColorBorderDefault, + boxShadow: "none", + ...(props.selectProps.filterProps?.variant === "bar" && { + borderColor: props.isFocused + ? BCDesignTokens.surfaceColorBorderActive + : "transparent", + }), + cursor: "pointer", + }), + menu: (base: CSSObjectWithLabel) => ({ + ...base, + position: "relative", + marginBlock: "0px", + border: `1px solid ${BCDesignTokens.surfaceColorBorderDefault}`, + borderRadius: "4px", + paddingBottom: "0.25rem", + ...menuStyle, + }), + placeholder: ( + base: CSSObjectWithLabel, + props: PlaceholderProps> + ) => ({ + ...base, + fontWeight: BCDesignTokens.typographyFontWeightsRegular, + color: BCDesignTokens.typographyColorPlaceholder, + fontSize: BCDesignTokens.typographyFontSizeSmallBody, + lineHeight: "1rem", + paddingLeft: "0.25rem", + ...(props.selectProps.filterProps?.variant == "bar" && { + color: BCDesignTokens.themePrimaryBlue, + fontWeight: BCDesignTokens.typographyFontWeightsBold, + }), + }), + menuPortal: (base: CSSObjectWithLabel) => ({ + ...base, + zIndex: theme.zIndex.modal, + marginTop: "4px", + }), + input: (base: CSSObjectWithLabel) => ({ + ...base, + fontWeight: "400", + fontSize: BCDesignTokens.typographyFontSizeSmallBody, + paddingLeft: "0.25rem", + }), + }), + [menuStyle, props.maxWidth, theme.zIndex.modal] + ); + return (