Skip to content

Commit

Permalink
Merge branch 'main' into feat/operations_api
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgamez committed Dec 2, 2024
2 parents c016098 + 868662b commit 4ad0194
Show file tree
Hide file tree
Showing 6 changed files with 489 additions and 88 deletions.
216 changes: 216 additions & 0 deletions web-app/src/app/components/NestedCheckboxList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import {
Checkbox,
Collapse,
IconButton,
List,
ListItem,
ListItemButton,
ListItemText,
} from '@mui/material';
import * as React from 'react';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { theme } from '../Theme';

interface NestedCheckboxListProps {
checkboxData: CheckboxStructure[];
onCheckboxChange: (checkboxData: CheckboxStructure[]) => void;
onExpandGroupChange?: (checkboxData: CheckboxStructure[]) => void;
}

// NOTE: Although the data structure allows for multiple levels of nesting, the current implementation only supports two levels.
// TODO: Implement support for multiple levels of nesting
export interface CheckboxStructure {
title: string;
type: 'label' | 'checkbox';
checked: boolean;
seeChildren?: boolean;
children?: CheckboxStructure[];
}

export default function NestedCheckboxList({
checkboxData,
onCheckboxChange,
onExpandGroupChange,
}: NestedCheckboxListProps): JSX.Element {
const [checkboxStructure, setCheckboxStructure] =
React.useState<CheckboxStructure[]>(checkboxData);
const [hasChange, setHasChange] = React.useState<boolean>(false);

React.useEffect(() => {
if (hasChange) {
setHasChange(false);
onCheckboxChange(checkboxStructure);
}
}, [checkboxStructure]);

React.useEffect(() => {
setCheckboxStructure(checkboxData);
}, [checkboxData]);

return (
<List sx={{ width: '100%' }} dense>
{checkboxStructure.map((checkboxData, index) => {
const labelId = `checkbox-list-label-${checkboxData.title}`;
return (
<ListItem
key={checkboxData.title}
disablePadding
sx={{
display: 'block',
borderBottom:
checkboxData.children !== undefined
? `1px solid ${theme.palette.text.primary}`
: 'none',
'.MuiListItemSecondaryAction-root': {
top: checkboxData.type === 'checkbox' ? '22px' : '11px',
},
}}
secondaryAction={
<>
{checkboxData.children !== undefined &&
checkboxData.children?.length > 0 && (
<IconButton
edge={'end'}
aria-label='expand'
onClick={() => {
checkboxData.seeChildren =
checkboxData.seeChildren === undefined
? true
: !checkboxData.seeChildren;
checkboxStructure[index] = checkboxData;
setCheckboxStructure([...checkboxStructure]);
if (onExpandGroupChange !== undefined) {
onExpandGroupChange([...checkboxStructure]);
}
}}
>
{checkboxData.seeChildren !== undefined &&
checkboxData.seeChildren ? (
<ExpandLess />
) : (
<ExpandMore />
)}
</IconButton>
)}
</>
}
>
{checkboxData.type === 'checkbox' && (
<ListItemButton
role={undefined}
dense={true}
sx={{ p: 0 }}
onClick={() => {
setCheckboxStructure((prev) => {
checkboxData.checked = !checkboxData.checked;
checkboxData.children?.forEach((child) => {
child.checked = checkboxData.checked;
});
prev[index] = checkboxData;
return [...prev];
});
setHasChange(true);
}}
>
<Checkbox
edge='start'
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
checked={
checkboxData.checked ||
(checkboxData.children !== undefined &&
checkboxData.children.length > 0 &&
checkboxData.children.every((child) => child.checked))
}
indeterminate={
checkboxData.children !== undefined
? checkboxData.children.some((child) => child.checked) &&
!checkboxData.children.every((child) => child.checked)
: false
}
/>
<ListItemText
id={labelId}
primary={<b>{checkboxData.title}</b>}
primaryTypographyProps={{
variant: 'body1',
}}
/>
</ListItemButton>
)}
{checkboxData.type === 'label' && (
<ListItemText
id={labelId}
primary={<b>{checkboxData.title}</b>}
primaryTypographyProps={{
variant: 'body1',
}}
/>
)}
{checkboxData.children !== undefined && (
<Collapse
in={checkboxData.seeChildren}
timeout='auto'
unmountOnExit
>
<List
sx={{
ml: 1,
display: { xs: 'flex', md: 'block' },
flexWrap: 'wrap',
}}
dense
>
{checkboxData.children.map((value) => {
const labelId = `checkbox-list-label-${value.title}`;

return (
<ListItem
key={value.title}
disablePadding
sx={{ width: { xs: '50%', sm: '33%', md: '100%' } }}
onClick={() => {
setCheckboxStructure((prev) => {
value.checked = !value.checked;
if (!value.checked) {
checkboxData.checked = false;
}
return [...prev];
});
setHasChange(true);
}}
>
<ListItemButton
role={undefined}
dense={true}
sx={{ p: 0, pl: 1 }}
>
<Checkbox
edge='start'
tabIndex={-1}
disableRipple
checked={value.checked || checkboxData.checked}
inputProps={{ 'aria-labelledby': labelId }}
/>

<ListItemText
id={labelId}
primary={`${value.title}`}
primaryTypographyProps={{
variant: 'body1',
}}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
</Collapse>
)}
</ListItem>
);
})}
</List>
);
}
2 changes: 2 additions & 0 deletions web-app/src/app/interface/RemoteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface RemoteConfigValues extends FirebaseDefaultConfig {
/** GBFS metrics' bucket endpoint */
gbfsMetricsBucketEndpoint: string;
featureFlagBypass: string;
enableFeatureFilterSearch: boolean;
}

const featureByPassDefault: BypassConfig = {
Expand All @@ -46,6 +47,7 @@ export const defaultRemoteConfigValues: RemoteConfigValues = {
gbfsMetricsBucketEndpoint:
'https://storage.googleapis.com/mobilitydata-gbfs-analytics-dev',
featureFlagBypass: JSON.stringify(featureByPassDefault),
enableFeatureFilterSearch: false,
};

remoteConfig.defaultConfig = defaultRemoteConfigValues;
Loading

0 comments on commit 4ad0194

Please sign in to comment.