Skip to content

Commit

Permalink
analysis: grouped categories selector
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgnlez committed May 10, 2024
1 parent 260169f commit 94ddb34
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 104 deletions.
33 changes: 18 additions & 15 deletions client/src/components/ui/select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid';

import { cn } from '@/lib/utils';

Expand All @@ -17,14 +17,23 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-navy-600 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
'group flex h-10 w-full items-center justify-between rounded-md border border-gray-200 bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:border-navy-400 focus:outline-none focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
<>
<ChevronDownIcon
className="hidden h-4 w-4 text-gray-900 group-data-[state=closed]:block"
aria-hidden="true"
/>
<ChevronUpIcon
className="hidden h-4 w-4 text-gray-900 group-data-[state=open]:block"
aria-hidden="true"
/>
</>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Expand All @@ -39,7 +48,7 @@ const SelectScrollUpButton = React.forwardRef<
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
<ChevronUpIcon className="h-4 w-4 text-gray-900" aria-hidden="true" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
Expand All @@ -53,7 +62,7 @@ const SelectScrollDownButton = React.forwardRef<
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
<ChevronDownIcon className="h-4 w-4 text-gray-900" aria-hidden="true" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
Expand All @@ -66,7 +75,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
Expand All @@ -77,7 +86,7 @@ const SelectContent = React.forwardRef<
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
'py-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
)}
Expand All @@ -96,7 +105,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
className={cn('py-1.5 pl-3 pr-2 text-sm font-semibold', className)}
{...props}
/>
));
Expand All @@ -109,17 +118,11 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-pointer select-none items-center py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-navy-50 data-[disabled]:pointer-events-none data-[state=checked]:text-navy-400 data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>

<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1,152 @@
export { default } from './component';
import { useCallback, useMemo, useEffect, ComponentProps, Fragment, useState } from 'react';
import { useRouter } from 'next/router';

import { useAppDispatch, useAppSelector } from 'store/hooks';
import { analysisUI } from 'store/features/analysis/ui';
import { setFilter } from 'store/features/analysis/filters';
import { useIndicators } from 'hooks/indicators';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
SelectLabel,
SelectSeparator,
} from '@/components/ui/select';

const IndicatorsFilter = () => {
const { query = {}, replace } = useRouter();
const { indicator } = query;
const { visualizationMode } = useAppSelector(analysisUI);
const dispatch = useAppDispatch();
const [value, setValue] = useState<string | undefined>(undefined);

const { data, isFetching, isFetched } = useIndicators(
{ sort: 'name' },
{
select: (data) => data?.data,
},
);

const options = useMemo(() => {
const categories = Array.from(
new Set(data?.map(({ category }) => category).filter(Boolean)),
).sort((a, b) => a.localeCompare(b));

const categoryGroups = categories.map((category) => {
const indicators = data?.filter((indicator) => indicator.category === category);
const categoryOptions = indicators.map((indicator) => ({
label: indicator.name,
value: indicator.id,
disabled: indicator.status === 'inactive',
}));
return { label: category, value: category, options: categoryOptions };
});

return [
...(visualizationMode !== 'map'
? [
{
label: 'All indicators',
value: 'all',
options: [],
},
]
: []),
...categoryGroups,
];
}, [data, visualizationMode]);

const indicatorName = useMemo(() => {
const indicator = data?.find((indicator) => indicator.id === value);
return indicator?.name;
}, [data, value]);

const handleChange = useCallback(
(value: Parameters<ComponentProps<typeof Select>['onValueChange']>[0]) => {
replace({ query: { ...query, indicator: value } }, undefined, {
shallow: true,
});

dispatch(
setFilter({
id: 'indicator',
value: {
label: indicatorName,
value,
},
}),
);
},
[query, replace, indicatorName, dispatch],
);

useEffect(() => {
if (indicator) {
return setValue(indicator as string);
}

if (visualizationMode === 'map') {
if (options?.at(0)?.options?.length) {
return setValue(options.at(0).options.at(0).value);
}
}

if (options?.length && !options?.at(0)?.options?.length) {
return setValue(options.at(0).value);
}
}, [visualizationMode, options, indicator, indicatorName, dispatch]);

useEffect(() => {
if (indicator && indicatorName) {
dispatch(
setFilter({
id: 'indicator',
value: {
label: indicatorName,
value: indicator,
},
}),
);
}
}, [dispatch, indicator, indicatorName]);

return (
<Select value={value} onValueChange={handleChange} disabled={!isFetched && isFetching}>
<SelectTrigger
className="h-full w-[325px] overflow-ellipsis text-left"
data-testid="indicators-filter"
>
<SelectValue placeholder="Select an indicator" />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<Fragment key={option.value}>
{!option?.options?.length && (
<>
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
<SelectSeparator />
</>
)}
{option?.options?.length > 0 && (
<SelectGroup>
<SelectLabel>{option.label}</SelectLabel>
{option.options.map((indicator) => (
<SelectItem key={indicator.value} value={indicator.value}>
{indicator.label}
</SelectItem>
))}
</SelectGroup>
)}
</Fragment>
))}
</SelectContent>
</Select>
);
};

export default IndicatorsFilter;
1 change: 1 addition & 0 deletions client/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface Indicator {
type: 'indicators';
unit?: Unit;
metadata: IndicatorMetadata;
category: string;
}

export type Group = {
Expand Down

0 comments on commit 94ddb34

Please sign in to comment.