Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert cloud catalog filter section to sistent #752

Merged
merged 6 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions src/custom/CatalogFilterSection/CatalogFilterSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import { useCallback, useState } from 'react';
import { Box, Drawer, Typography } from '../../base';
import { CloseIcon } from '../../icons';
import { CULTURED, DARK_SLATE_GRAY, WHITE } from '../../theme';
import { CloseBtn } from '../Modal';
import CatalogFilterSidebarState from './CatalogFilterSidebarState';
import {
FilterButton,
FilterDrawerDiv,
FilterText,
FiltersCardDiv,
FiltersDrawerHeader
} from './style';

export interface FilterOption {
value: string;
label: string;
totalCount?: number;
description?: string;
Icon?: React.ComponentType<{
width: string;
height: string;
}>;
}

export interface FilterList {
filterKey: string;
sectionDisplayName?: string;
options: FilterOption[];
defaultOpen?: boolean;
isMultiSelect?: boolean;
}

export interface CatalogFilterSidebarProps {
setData: (callback: (prevFilters: FilterValues) => FilterValues) => void;
lists: FilterList[];
value?: FilterValues;
}

export type FilterValues = Record<string, string | string[]>;

export interface StyleProps {
backgroundColor?: string;
sectionTitleBackgroundColor?: string;
}

/**
* @function CatalogFilterSidebar
* @description A functional component that renders the filter sidebar.
* @param {Array} value - The data to be filtered.
* @param {Function} setData - A function to set the filtered data.
* @param {Array} lists - An array of filter sections and it's options lists.
*/
const CatalogFilterSidebar: React.FC<CatalogFilterSidebarProps> = ({
lists,
setData,
value = {}
}) => {
const [openDrawer, setOpenDrawer] = useState<boolean>(false);

const handleDrawerOpen = useCallback(() => {
setOpenDrawer(true);
}, []);

const handleDrawerClose = useCallback(() => {
setOpenDrawer(false);
}, []);

const styleProps: StyleProps = {
amitamrutiya marked this conversation as resolved.
Show resolved Hide resolved
backgroundColor: WHITE,
sectionTitleBackgroundColor: CULTURED
};

return (
<>
<FiltersCardDiv styleProps={styleProps}>
<CatalogFilterSidebarState
lists={lists}
onApplyFilters={setData}
value={value}
styleProps={styleProps}
/>
</FiltersCardDiv>
<FilterDrawerDiv>
<FilterButton variant="contained" onClick={handleDrawerOpen}>
<FilterAltIcon height="20" width="20" fill={WHITE} />
<FilterText>Filters</FilterText>
</FilterButton>

<Drawer anchor="bottom" open={openDrawer} variant="temporary" onClose={handleDrawerClose}>
<Box sx={{ overflowY: 'hidden', height: '90vh' }}>
<FiltersDrawerHeader>
<Typography variant="h6" sx={{ color: WHITE }} component="div">
Filters
</Typography>
<CloseBtn onClick={handleDrawerClose}>
<CloseIcon height={'32px'} width={'32px'} />
</CloseBtn>
</FiltersDrawerHeader>
<Box
style={{
height: '75vh',
overflowY: 'auto',
background: WHITE
}}
>
<CatalogFilterSidebarState
lists={lists}
onApplyFilters={setData}
value={value}
styleProps={styleProps}
/>
</Box>
<Box sx={{ backgroundColor: DARK_SLATE_GRAY, height: '5vh' }} />
</Box>
</Drawer>
</FilterDrawerDiv>
</>
);
};

export default CatalogFilterSidebar;
97 changes: 97 additions & 0 deletions src/custom/CatalogFilterSection/CatalogFilterSidebarState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useCallback, useState } from 'react';
import {
CatalogFilterSidebarProps,
FilterList,
FilterValues,
StyleProps
} from './CatalogFilterSidebar';
import FilterSection from './FilterSection';
/**
* @component CatalogFilterSidebarState
* @description A functional component that manages the filter state.
* @param {Array} lists - An array of filter sections and its options lists.
* @param {Function} onApplyFilters - A function to apply the filters.
* @param {Object} value - The selected filters.
* @param {Object} styleProps - The style properties for the component.
*/
const CatalogFilterSidebarState: React.FC<{
lists: FilterList[];
onApplyFilters: CatalogFilterSidebarProps['setData'];
value: FilterValues;
styleProps: StyleProps;
}> = ({ lists, onApplyFilters, value, styleProps }) => {
// Generate initial state with all sections open by default
const [openSections, setOpenSections] = useState<Record<string, boolean>>(() => {
const initialOpenSections: Record<string, boolean> = {};
lists.forEach((list) => {
initialOpenSections[list.filterKey] = !!list.defaultOpen;
});
return initialOpenSections;
});

/**
* @function handleSectionToggle
* @description Handles the section toggle event.
* @param {string} filterKey - The name of the filter section.
*/
const handleSectionToggle = useCallback((filterKey: string) => {
setOpenSections((prevOpenSections) => ({
...prevOpenSections,
[filterKey]: !prevOpenSections[filterKey]
}));
}, []);

/**
* @function handleCheckboxChange
* @description Handles the checkbox change event.
* @param {string} filterKey - The name of the filter section.
* @param {string} value - The value of the checkbox.
* @param {boolean} checked - The checked state of the checkbox.
*/
const handleCheckboxChange = useCallback(
(filterKey: string, value: string, checked: boolean) => {
onApplyFilters((prevFilters) => {
const updatedFilters = { ...prevFilters };
const filterList = lists.find((list) => list.filterKey === filterKey);

// default is multi select
if (filterList?.isMultiSelect !== false) {
let currentValues = updatedFilters[filterKey] as string[] | undefined;

if (!Array.isArray(currentValues)) {
currentValues = currentValues ? [currentValues as string] : []; // convert to array;
}

updatedFilters[filterKey] = checked
? [...currentValues, value]
: currentValues.filter((item) => item !== value);
} else {
updatedFilters[filterKey] = checked ? value : '';
}

return updatedFilters;
});
},
[lists, onApplyFilters]
);

return (
<>
{lists.map((list) => (
<FilterSection
key={list.filterKey}
filterKey={list.filterKey}
sectionDisplayName={list.sectionDisplayName}
options={list.options}
filters={value}
openSections={openSections}
onCheckboxChange={handleCheckboxChange}
onSectionToggle={handleSectionToggle}
styleProps={styleProps}
/>
))}
</>
);
};

export default CatalogFilterSidebarState;
142 changes: 142 additions & 0 deletions src/custom/CatalogFilterSection/FilterSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useCallback, useState } from 'react';
import {
Box,
Checkbox,
Collapse,
InputAdornment,
List,
OutlinedInput,
Stack,
Typography
} from '../../base';
import { SearchIcon } from '../../icons';
import { InfoTooltip } from '../CustomTooltip';
import { FilterOption, FilterValues, StyleProps } from './CatalogFilterSidebar';
import { FilterTitleButton, InputAdornmentEnd } from './style';

interface FilterSectionProps {
filterKey: string;
sectionDisplayName?: string;
options: FilterOption[];
filters: FilterValues;
openSections: Record<string, boolean>;
onCheckboxChange: (filterKey: string, value: string, checked: boolean) => void;
onSectionToggle: (filterKey: string) => void;
styleProps: StyleProps;
}

/**
* @component FilterSection
* @description A functional component that renders a filter section.
* @param {string} filterKey - The key of the filter section.
* @param {string} sectionDisplayName - The title of the filter section.
* @param {Array} options - The available options for the filter section.
* @param {Object} filters - The selected filters.
* @param {Object} openSections - The open/closed state of the filter sections.
* @param {Function} onCheckboxChange - A function to handle checkbox change event.
* @param {Function} onSectionToggle - A function to handle section toggle event.
* @param {Object} styleProps - The style properties for the component.
*/
const FilterSection: React.FC<FilterSectionProps> = ({
filterKey,
sectionDisplayName,
options,
filters,
openSections,
onCheckboxChange,
onSectionToggle,
styleProps
}) => {
const [searchQuery, setSearchQuery] = useState<string>('');

const handleTextFieldChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value);
}, []);

const showSearch = options.length > 10;
const searchedOptions = searchQuery
? options.filter((option) => option.label.toLowerCase().includes(searchQuery.toLowerCase()))
: options;

return (
<>
<FilterTitleButton
onClick={() => onSectionToggle(filterKey)}
style={{ backgroundColor: styleProps.sectionTitleBackgroundColor }}
>
<Typography variant="h6" fontWeight="bold">
{(sectionDisplayName || filterKey).toUpperCase()}
</Typography>
{openSections[filterKey] ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</FilterTitleButton>
<Collapse in={openSections[filterKey]} timeout="auto" unmountOnExit>
<List
component="div"
sx={{
overflowY: 'auto',
maxHeight: '25rem',
backgroundColor: styleProps.backgroundColor
}}
>
{showSearch && (
<Box px={'0.5rem'}>
<OutlinedInput
type="search"
fullWidth
placeholder="Search "
value={searchQuery}
onChange={handleTextFieldChange}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
endAdornment={
<InputAdornmentEnd position="end">
Total: {searchedOptions.length}
</InputAdornmentEnd>
}
/>
</Box>
)}
{searchedOptions.map((option, index) => (
<Stack
key={`${option.value}-${index}`}
direction="row"
alignItems="center"
px={'0.5rem'}
justifyContent="space-between"
>
<Stack direction="row" alignItems="center" gap="0.35rem">
<Checkbox
id={`checkbox-${option.label}`}
checked={
Array.isArray(filters[filterKey])
? (filters[filterKey] as string[]).includes(option.value)
: filters[filterKey] === option.value
}
onChange={(e) => onCheckboxChange(filterKey, option.value, e.target.checked)}
value={option.value}
/>

{option.Icon && <option.Icon width="20px" height="20px" />}

<Typography>{option.label}</Typography>
</Stack>
<Stack direction="row" alignItems="center" gap="0.35rem">
{option.totalCount !== undefined && `(${option.totalCount || 0})`}
{option.description && (
<InfoTooltip variant="standard" helpText={option.description} />
)}
</Stack>
</Stack>
))}
</List>
</Collapse>
</>
);
};

export default FilterSection;
3 changes: 3 additions & 0 deletions src/custom/CatalogFilterSection/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CatalogFilterSidebar from './CatalogFilterSidebar';

export { CatalogFilterSidebar };
Loading
Loading