Skip to content

Commit

Permalink
filters badges
Browse files Browse the repository at this point in the history
  • Loading branch information
mluena committed Feb 20, 2024
1 parent 4d16c09 commit aa4db80
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 96 deletions.
6 changes: 2 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"lucide-react": "^0.252.0",
"mapbox-gl": "2.15.0",
"next": "14.1.0",
"nuqs": "^1.16.1",
"postcss": "8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand All @@ -72,9 +71,8 @@
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"nuqs": "1.17.0",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"nuqs": "1.17.0"
"prettier-plugin-tailwindcss": "^0.3.0"
}
}

45 changes: 22 additions & 23 deletions client/src/components/ui/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,32 @@ import {
} from '@/components/ui/command';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';

const projects = [
{
value: '1',
label:
'Conservation and development of forest ecosystems biodiversity resources at Cat Tien National Park',
},
{
value: '2',
label:
'Innovative Solutions for Climate Change and Biodiversity Landscape Strategy to Support SDGs in Indonesia',
},
];

export function ComboboxDemo({ placeholder = 'Search' }: { placeholder: string }) {
export function Combobox({
icon,
placeholder = 'Search',
options,
onClick,
}: {
icon?: boolean;
placeholder: string;
options: Record<string, string>[];
onClick?: (e: string) => void;
}) {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('');

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
role="combobox"
aria-expanded={open}
className="h-10 w-full justify-between rounded-md border-none bg-gray-100"
className="h-10 w-full flex-1 justify-between rounded-md border-none bg-gray-100"
>
<div className="flex w-full items-center space-x-2">
<RiSearchLine className="h-6 w-6" />
{icon && <RiSearchLine className="h-6 w-6" />}
<span className="max-w-[200px] overflow-hidden truncate text-ellipsis text-xs text-gray-500">
{value ? projects.find((project) => project.value === value)?.label : placeholder}
{value ? options.find((option) => option.value === value)?.label : placeholder}
</span>
</div>
</Button>
Expand All @@ -56,22 +52,25 @@ export function ComboboxDemo({ placeholder = 'Search' }: { placeholder: string }
<CommandInput placeholder={placeholder} />
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{projects.map((project) => (
{options.map((option) => (
<CommandItem
key={project.value}
value={project.value}
key={option.value}
value={option.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? '' : currentValue);
setOpen(false);
if (onClick) {
onClick(currentValue);
}
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === project.value ? 'opacity-100' : 'opacity-0'
value === option.value ? 'opacity-100' : 'opacity-0'
)}
/>
{project.label}
{option.label}
</CommandItem>
))}
</CommandGroup>
Expand Down
14 changes: 8 additions & 6 deletions client/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'bg-background 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 rounded-3xl p-6 shadow-xl duration-200 md:w-full',
'bg-background 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 rounded-3xl px-6 py-4 shadow-xl duration-200 md:w-full',
className
)}
{...props}
Expand All @@ -61,11 +61,13 @@ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivEleme
);
DialogHeader.displayName = 'DialogHeader';

const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
const DialogFooter = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className="space-y-4">
<div className="absolute left-0 right-0 h-0.5 bg-gray-100" />
<div className={cn('flex w-full items-center justify-between', className)} {...props}>
{children}
</div>
</div>
);
DialogFooter.displayName = 'DialogFooter';

Expand Down
4 changes: 1 addition & 3 deletions client/src/components/ui/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ const Popover = PopoverPrimitive.Root;

const PopoverTrigger = React.forwardRef<
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>
>(({ ...props }) => (
<PopoverPrimitive.Trigger className="popover-menu-trigger flex-1" {...props} />
));
>(({ ...props }) => <PopoverPrimitive.Trigger className=" flex-1" {...props} />);

PopoverTrigger.displayName = PopoverPrimitive.Trigger.displayName;

Expand Down
13 changes: 13 additions & 0 deletions client/src/containers/filters/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ export const AREAS = [
label: '>500',
},
];

export const PROJECTS = [
{
value: '1',
label:
'Conservation and development of forest ecosystems biodiversity resources at Cat Tien National Park',
},
{
value: '2',
label:
'Innovative Solutions for Climate Change and Biodiversity Landscape Strategy to Support SDGs in Indonesia',
},
];
78 changes: 47 additions & 31 deletions client/src/containers/filters/content.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
'use client';

import { useCallback, MouseEvent } from 'react';
import { useCallback } from 'react';

import { useAtom } from 'jotai';

import { filtersAtom } from '@/store/projects';

import { useSyncFilters } from '@/hooks/datasets/sync-query';

import type { FilterValues, FiltersType } from '@/containers/filters/types';

import { Checkbox } from '@/components/ui/checkbox';
import { Combobox } from '@/components/ui/combobox';
import { Label } from '@/components/ui/label';

import { AREAS, INTERVENTION_TYPES } from './constants';

type FiltersType =
| 'intervention'
| 'country'
| 'area_restored'
| 'area_protected'
| 'area_plantation';
const FiltersCheckbox = ({
type,
title,
Expand All @@ -28,41 +25,32 @@ const FiltersCheckbox = ({
title: string;
options: { id: string; label: string }[];
}) => {
const [filters, setFilters] = useAtom(filtersAtom);
const [filtersSettings, setFilters] = useAtom(filtersAtom);
const [, setFiltersToURL] = useSyncFilters();

const handleChange = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); // avoid to close the dropdown interacting with the checkbox
e.stopPropagation();

const filtersToUpdate: FilterValues | undefined = filtersSettings[type];

if (type === 'country') {
if (filtersToUpdate === undefined) {
const update = {
...filters,
[type]: e.currentTarget.id,
...filtersSettings,
[type]: [e.currentTarget.id],
};
setFilters(update);
setFiltersToURL(update);
} else {
const filtersToUpdate = filters[type] as string[];

if (filtersToUpdate === undefined) {
const update = {
...filters,
[type]: [e.currentTarget.id],
};
setFilters(update);
setFiltersToURL(update);
} else {
const updatedFilters = filtersToUpdate?.includes(e.currentTarget.id)
? filtersToUpdate?.filter((f) => f !== e.currentTarget.id)
: [e.currentTarget.id, ...filtersToUpdate];
const update = { ...filters, [type]: updatedFilters };
setFilters(update);
setFiltersToURL(update);
}
const updatedFilters = filtersToUpdate?.includes(e.currentTarget.id)
? filtersToUpdate.length && filtersToUpdate?.filter((f) => f !== e.currentTarget.id)
: [e.currentTarget.id, ...filtersToUpdate];
const update = { ...filtersSettings, [type]: updatedFilters };
setFilters(update);
setFiltersToURL(update);
}
},
[filters, setFilters, type, setFiltersToURL]
[filtersSettings, setFilters, type, setFiltersToURL]
);

return (
Expand All @@ -71,18 +59,46 @@ const FiltersCheckbox = ({
<div className="flex flex-wrap items-center gap-2">
{options.map(({ id, label }) => (
<div key={id} className="flex items-center space-x-2 pr-2 last:pr-0">
<Checkbox id={id} onClick={handleChange} />
<Checkbox
id={id}
onClick={handleChange}
checked={filtersSettings[type]?.includes(id)}
/>
<Label htmlFor={id}>{label}</Label>
</div>
))}
</div>
</div>
);
};

export default function FiltersContent() {
const [filtersSettings, setFilters] = useAtom(filtersAtom);
const [, setFiltersToURL] = useSyncFilters();

const handleSingleValueChange = useCallback(
(e: string) => {
const update = {
...filtersSettings,
country: e,
};
setFilters(update);
setFiltersToURL(update);
},
[setFilters, setFiltersToURL, filtersSettings]
);

return (
<div className="flex flex-col space-y-6 text-sm">
<FiltersCheckbox type="intervention" title="Intervention type" options={INTERVENTION_TYPES} />
<div>
<span className="font-extrabold leading-5">Country</span>
<Combobox
placeholder="Search country"
options={[{ label: 'Spain', value: 'Spain' }]}
onClick={handleSingleValueChange}
/>
</div>
<FiltersCheckbox
type="area_restored"
title="Area of total restored or reforested area (ha)"
Expand Down
Loading

0 comments on commit aa4db80

Please sign in to comment.