Skip to content

Commit

Permalink
Merge pull request #276 from Vizzuality/feature/features-filters
Browse files Browse the repository at this point in the history
Features filters
  • Loading branch information
mbarrenechea authored Jun 17, 2021
2 parents f891aad + a46aeca commit 97134fb
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 19 deletions.
2 changes: 1 addition & 1 deletion app/hooks/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function useAllFeatures(projectId, options: UseFeaturesOptionsProps = {})
.reduce((acc, k) => {
return {
...acc,
[`filter[${k}]`]: filters[k],
[`filter[${k}]`]: filters[k].toString(),
};
}, {});

Expand Down
23 changes: 22 additions & 1 deletion app/layout/scenarios/sidebar/features/add/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const ScenariosFeaturesAdd: React.FC<ScenariosFeaturesAddProps> = ({
onDismiss,
}: ScenariosFeaturesAddProps) => {
const [search, setSearch] = useState(null);
const [filters, setFilters] = useState({});
const [sort, setSort] = useState(null);
const { query } = useRouter();
const { pid } = query;

Expand All @@ -30,6 +32,8 @@ export const ScenariosFeaturesAdd: React.FC<ScenariosFeaturesAddProps> = ({
isFetched: allFeaturesIsFetched,
} = useAllFeatures(pid, {
search,
filters,
sort,
});

const INITIAL_VALUES = useMemo(() => {
Expand Down Expand Up @@ -61,6 +65,14 @@ export const ScenariosFeaturesAdd: React.FC<ScenariosFeaturesAddProps> = ({
setSearch(s);
}, []);

const onFilters = useCallback((f) => {
setFilters(f);
}, []);

const onSort = useCallback((s) => {
setSort(s);
}, []);

const onSubmit = useCallback((values) => {
// Save current features then dismiss the modal
console.info(values);
Expand All @@ -80,14 +92,23 @@ export const ScenariosFeaturesAdd: React.FC<ScenariosFeaturesAddProps> = ({
{({ handleSubmit, values }) => (
<form onSubmit={handleSubmit} autoComplete="off" className="flex flex-col flex-grow overflow-hidden text-black">
<h2 className="flex-shrink-0 pl-8 mb-5 text-lg pr-28 font-heading">Add features to your planning area</h2>
<Toolbar search={search} onSearch={onSearch} />
<Toolbar
search={search}
filters={filters}
sort={sort}
onSearch={onSearch}
onFilters={onFilters}
onSort={onSort}
/>

<FieldRFF
name="selected"
>
{({ input }) => (
<List
search={search}
filters={filters}
sort={sort}
selected={values.selected}
onToggleSelected={(id) => {
onToggleSelected(id, input);
Expand Down
140 changes: 140 additions & 0 deletions app/layout/scenarios/sidebar/features/add/filters/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { useCallback, useMemo } from 'react';

import { Form as FormRFF, Field as FieldRFF } from 'react-final-form';
import Button from 'components/button';
import Checkbox from 'components/forms/checkbox';
import Label from 'components/forms/label';
import Radio from 'components/forms/radio';

export interface ScenarioFeaturesAddFiltersProps {
filters?: Record<string, any>;
onChangeFilters: (filters: Record<string, any>) => void;
sort?: string;
onChangeSort: (sort: string) => void;
onDismiss?: () => void;
}

const TAGS = [
{ id: 'species', label: 'species' },
{ id: 'bioregional', label: 'bioregional' },
];

const SORT = [
{ id: 'alias', label: 'Alphabetical' },
{ id: '-alias', label: '-Alphabetical' },
{ id: 'featureClassName', label: 'Classname' },
{ id: '-featureClassName', label: '-Classname' },
];

export const ScenarioFeaturesAddFilters: React.FC<ScenarioFeaturesAddFiltersProps> = ({
filters = {},
onChangeFilters,
sort,
onChangeSort,
onDismiss,
}: ScenarioFeaturesAddFiltersProps) => {
const INITIAL_VALUES = useMemo(() => {
return {
...filters,
sort: sort || 'alias',
};
}, [filters, sort]);

// Callbacks
const onSubmit = useCallback((values) => {
const { sort: valuesSort, ...valuesFilters } = values;
onChangeFilters(valuesFilters);
onChangeSort(valuesSort);
if (onDismiss) onDismiss();
}, [onChangeFilters, onChangeSort, onDismiss]);

const onClear = useCallback(() => {
onChangeFilters({});
onChangeSort(null);
if (onDismiss) onDismiss();
}, [onChangeFilters, onChangeSort, onDismiss]);

return (
<FormRFF
key="features-all-filters"
onSubmit={onSubmit}
initialValues={INITIAL_VALUES}
>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit} autoComplete="off" className="flex flex-col flex-grow overflow-hidden text-black">
<h2 className="pl-8 mb-5 text-lg pr-28 font-heading">Filters</h2>

<div className="flex flex-col px-8 space-y-5">
<div>
<h3 className="flex-shrink-0 mb-2 text-sm pr-28 font-heading">Filter by type</h3>
<div className="flex flex-col space-y-2">
{TAGS.map(({ id, label }) => {
return (
<FieldRFF
key={id}
name="tag"
type="checkbox"
value={id}
>
{(fprops) => (
<div className="flex space-x-2">
<Checkbox theme="light" id={`tag-${id}`} {...fprops.input} />
<Label theme="light" id={`tag-${id}`} className="ml-2">{label}</Label>
</div>
)}
</FieldRFF>
);
})}
</div>
</div>

<div>
<h3 className="flex-shrink-0 mb-2 text-sm pr-28 font-heading">Order by</h3>
<div className="flex flex-col space-y-2">
{SORT.map(({ id, label }) => {
return (
<FieldRFF
key={id}
name="sort"
type="radio"
value={id}
>
{(fprops) => (
<div className="flex space-x-2">
<Radio theme="light" id={`tag-${id}`} {...fprops.input} />
<Label theme="light" id={`tag-${id}`} className="ml-2">{label}</Label>
</div>
)}
</FieldRFF>
);
})}
</div>
</div>
</div>

<div className="flex justify-center flex-shrink-0 px-8 mt-10 space-x-3">
<Button
className="w-full"
theme="secondary"
size="lg"
onClick={onClear}
>
Clear all
</Button>

<Button
type="submit"
className="w-full"
theme="primary"
size="lg"
>
Apply
</Button>
</div>
</form>
)}
</FormRFF>
);
};

export default ScenarioFeaturesAddFilters;
1 change: 1 addition & 0 deletions app/layout/scenarios/sidebar/features/add/filters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
6 changes: 6 additions & 0 deletions app/layout/scenarios/sidebar/features/add/list/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import useBottomScrollListener from 'hooks/scroll';

export interface ScenariosFeaturesAddListProps {
search?: string;
filters?: Record<string, any>;
sort?: string;
selected: number[] | string[];
onToggleSelected: (selected: string | number) => void;
}

export const ScenariosFeaturesAddList: React.FC<ScenariosFeaturesAddListProps> = ({
search,
filters,
sort,
selected = [],
onToggleSelected,
}: ScenariosFeaturesAddListProps) => {
Expand All @@ -31,6 +35,8 @@ export const ScenariosFeaturesAddList: React.FC<ScenariosFeaturesAddListProps> =
isFetched: allFeaturesIsFetched,
} = useAllFeatures(pid, {
search,
filters,
sort,
});

const scrollRef = useBottomScrollListener(
Expand Down
102 changes: 87 additions & 15 deletions app/layout/scenarios/sidebar/features/add/toolbar/component.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
import React, { useEffect } from 'react';
import React, {
useCallback, useEffect, useMemo, useState,
} from 'react';

import Search from 'components/search';
import { useDebouncedCallback } from 'use-debounce';

import Search from 'components/search';
import Icon from 'components/icon';
import Modal from 'components/modal';

import Filters from 'layout/scenarios/sidebar/features/add/filters';

import FILTER_SVG from 'svgs/ui/filter.svg?sprite';

export interface ScenarioFeaturesAddToolbarProps {
search?: string;
onSearch: (selected: string) => void;
onSearch?: (selected: string) => void;

filters?: Record<string, any>;
onFilters?: (filters: Record<string, unknown>) => void;

sort?: string;
onSort?: (sort: string) => void;
}

export const ScenarioFeaturesAdd: React.FC<ScenarioFeaturesAddToolbarProps> = ({
search, onSearch,
export const ScenarioFeaturesAddToolbar: React.FC<ScenarioFeaturesAddToolbarProps> = ({
search,
onSearch,
filters = {},
onFilters,
sort,
onSort,
}: ScenarioFeaturesAddToolbarProps) => {
const onChangeDebounced = useDebouncedCallback((value) => {
const [open, setOpen] = useState(false);
const FILTERS_LENGTH = useMemo(() => {
return Object.keys(filters)
.reduce((acc, k) => {
if (typeof filters[k] === 'undefined') return acc;

if (filters[k] && Array.isArray(filters[k])) {
return acc + filters[k].length;
}

return acc + 1;
}, 0);
}, [filters]);

const onChangeOpen = useCallback(() => {
setOpen(true);
}, []);

const onChangeSearchDebounced = useDebouncedCallback((value) => {
onSearch(value);
}, 500);

Expand All @@ -24,16 +62,50 @@ export const ScenarioFeaturesAdd: React.FC<ScenarioFeaturesAddToolbarProps> = ({

return (
<div className="px-8">
<Search
theme="light"
size="sm"
defaultValue={search}
placeholder="Search by feature name..."
aria-label="Search"
onChange={onChangeDebounced}
/>
<div className="flex items-center">
<Search
theme="light"
size="sm"
defaultValue={search}
placeholder="Search by feature name..."
aria-label="Search"
onChange={onChangeSearchDebounced}
/>

<button
type="button"
className="relative flex items-center px-1 py-2 space-x-2"
onClick={onChangeOpen}
>
<Icon icon={FILTER_SVG} />
<span className="text-xs tracking-wider uppercase font-heading">
Filters
{!!FILTERS_LENGTH && (
<span className="absolute top-0 left-0 py-0.5 px-1 rounded-full bg-red-500 text-white text-xxs leading-none" style={{ fontFamily: 'Arial' }}>
{FILTERS_LENGTH}
</span>
)}
</span>
</button>

<Modal
title="All features filters"
open={open}
size="narrow"
onDismiss={() => {
setOpen(false);
}}
>
<Filters
filters={filters}
onChangeFilters={onFilters}
sort={sort}
onChangeSort={onSort}
/>
</Modal>
</div>
</div>
);
};

export default ScenarioFeaturesAdd;
export default ScenarioFeaturesAddToolbar;
4 changes: 2 additions & 2 deletions app/layout/scenarios/sidebar/features/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const ScenariosSidebarWDPA: React.FC<ScenariosSidebarWDPAProps> = () => {
animate={{ opacity: 1, y: 0 }}
>
<Pill selected>
<header className="flex justify-between flex-shrink-0">
<header className="flex items-start justify-between flex-shrink-0">
<div>
<div className="flex items-baseline space-x-4">
<h2 className="text-lg font-medium font-heading">Features</h2>
Expand Down Expand Up @@ -85,7 +85,7 @@ export const ScenariosSidebarWDPA: React.FC<ScenariosSidebarWDPAProps> = () => {
{step === 0 && (
<Button
theme="primary"
size="lg"
size="base"
onClick={() => setModal(true)}
>
<span className="mr-3">Add features</span>
Expand Down
3 changes: 3 additions & 0 deletions app/svgs/ui/filter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 97134fb

Please sign in to comment.