Skip to content

Commit

Permalink
Merge pull request #263 from Vizzuality/SKY30-372-fe-add-geography-fi…
Browse files Browse the repository at this point in the history
…lter-to-the-conservation-builder

[SKY30-372] Add the location selector to the modelling tool + functionality enhancements
  • Loading branch information
SARodrigues authored Jun 3, 2024
2 parents 08820e3 + 96d3300 commit 047773b
Show file tree
Hide file tree
Showing 27 changed files with 168 additions and 89 deletions.
4 changes: 4 additions & 0 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const nextConfig = {
source: '/progress-tracker',
destination: '/progress-tracker/GLOB',
},
{
source: '/conservation-builder',
destination: '/conservation-builder/GLOB',
},
];
},
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/constants/pages.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const PAGES = {
homepage: '/',
progressTracker: '/progress-tracker',
conservationBuilder: '/conservation-builder',
homepage: '/',
knowledgeHub: '/knowledge-hub',
contact: '/contact',
about: '/about',
};
} as const;
4 changes: 2 additions & 2 deletions frontend/src/containers/map/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import LayersIcon from '@/styles/icons/layers.svg?sprite';
import { useSyncMapContentSettings } from '../sync-settings';

import LayersPanel from './layers-panel';
import MainPanel, { SIDEBAR_TYPES } from './main-panel';
import MainPanel, { PANEL_TYPES } from './main-panel/panels';

type MapSidebarProps = {
type: keyof typeof SIDEBAR_TYPES;
type: keyof typeof PANEL_TYPES;
};

const MapSidebar: React.FC<MapSidebarProps> = ({ type }) => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ import { useSetAtom } from 'jotai';
import { Button } from '@/components/ui/button';
import Icon from '@/components/ui/icon';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { PAGES } from '@/constants/pages';
import { popupAtom } from '@/containers/map/store';
import { cn } from '@/lib/classnames';
import GlobeIcon from '@/styles/icons/globe.svg';
import MagnifyingGlassIcon from '@/styles/icons/magnifying-glass.svg';
import { useGetLocations } from '@/types/generated/location';
import { LocationGroupsDataItemAttributes } from '@/types/generated/strapi.schemas';

import { useMapSearchParams } from '../../../../content/map/sync-settings';

import LocationDropdown from './location-dropdown';
import LocationTypeToggle from './type-toggle';

Expand All @@ -37,18 +34,17 @@ const BUTTON_CLASSES =

type LocationSelectorProps = {
className?: HTMLDivElement['className'];
theme: 'orange' | 'blue';
onChange: (locationCode: string) => void;
};

const LocationSelector: React.FC<LocationSelectorProps> = ({ className }) => {
const LocationSelector: React.FC<LocationSelectorProps> = ({ className, theme, onChange }) => {
const {
push,
query: { locationCode = 'GLOB' },
} = useRouter();

const setPopup = useSetAtom(popupAtom);

const searchParams = useMapSearchParams();

const [locationsFilter, setLocationsFilter] = useState<keyof typeof FILTERS>('all');
const [locationPopoverOpen, setLocationPopoverOpen] = useState(false);

Expand All @@ -73,9 +69,9 @@ const LocationSelector: React.FC<LocationSelectorProps> = ({ className }) => {
async (locationCode: LocationGroupsDataItemAttributes['code']) => {
setLocationPopoverOpen(false);
setPopup({});
push(`${PAGES.progressTracker}/${locationCode.toUpperCase()}?${searchParams.toString()}`);
onChange(locationCode.toUpperCase());
},
[setPopup, push, searchParams]
[setPopup, onChange]
);

const reorderedLocations = useMemo(() => {
Expand Down Expand Up @@ -104,6 +100,7 @@ const LocationSelector: React.FC<LocationSelectorProps> = ({ className }) => {
</PopoverTrigger>
<PopoverContent className="w-96 max-w-screen" align="start">
<LocationTypeToggle
theme={theme}
defaultValue={locationsFilter}
value={locationsFilter}
className="mb-4"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as ToggleGroup from '@radix-ui/react-toggle-group';
import { VariantProps, cva } from 'class-variance-authority';

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

import { FILTERS } from '../index';

const toggleVariants = cva(
'focus-visible:ring-slate-950 data-[state=on]:text-slate-950 dark:ring-offset-slate-950 dark:data-[state=on]:bg-slate-950 justify-center whitespace-nowrap ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:shadow-sm dark:focus-visible:ring-slate-300 dark:data-[state=on]:text-slate-50 group flex flex-1 items-center space-x-1 rounded-none border-r last:border-r-0 border-black py-2 whitespace-pre-line font-mono text-xs font-bold uppercase leading-none text-black last:border-l-0 h-full',
{
variants: {
theme: {
orange: 'data-[state=on]:bg-orange',
blue: 'data-[state=on]:bg-blue',
},
},
defaultVariants: {
theme: 'orange',
},
}
);

type LocationTypeToggleProps = VariantProps<typeof toggleVariants> & {
className?: HTMLDivElement['className'];
defaultValue: keyof typeof FILTERS;
value: keyof typeof FILTERS;
onChange: (value: keyof typeof FILTERS) => void;
};

const LocationTypeToggle: React.FC<LocationTypeToggleProps> = ({
className,
theme,
defaultValue,
value,
onChange,
}) => {
return (
<ToggleGroup.Root
className={cn(
className,
'grid w-full grid-cols-3 items-center justify-center border border-black'
)}
type="single"
defaultValue={defaultValue}
aria-label="Locations filter"
value={value}
onValueChange={(value: keyof typeof FILTERS) => {
if (!value) return;
onChange(value);
}}
>
<ToggleGroup.Item className={toggleVariants({ theme })} value="all" aria-label="All">
All
</ToggleGroup.Item>
<ToggleGroup.Item
className={toggleVariants({ theme })}
value="countryHighseas"
aria-label="Countries & High Seas"
>
Countries & High Seas
</ToggleGroup.Item>
<ToggleGroup.Item className={toggleVariants({ theme })} value="regions" aria-label="Regions">
Regions
</ToggleGroup.Item>
</ToggleGroup.Root>
);
};

export default LocationTypeToggle;
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import { useMemo } from 'react';

import { useRouter } from 'next/router';

import { PAGES } from '@/constants/pages';
import { useMapSearchParams } from '@/containers/map/content/map/sync-settings';
import { useSyncMapContentSettings } from '@/containers/map/sync-settings';
import { cn } from '@/lib/classnames';
import { useGetLocations } from '@/types/generated/location';

import LocationSelector from '../../location-selector';

import CountriesList from './countries-list';
import DetailsButton from './details-button';
import LocationSelector from './location-selector';
import DetailsWidgets from './widgets';

const SidebarDetails: React.FC = () => {
const {
push,
query: { locationCode = 'GLOB' },
} = useRouter();
const [{ showDetails }] = useSyncMapContentSettings();
const searchParams = useMapSearchParams();

const { data: locationsData } = useGetLocations({
filters: {
Expand All @@ -31,12 +36,16 @@ const SidebarDetails: React.FC = () => {
}));
}, [locationsData?.data]);

const handleLocationSelected = (locationCode) => {
push(`${PAGES.progressTracker}/${locationCode}?${searchParams.toString()}`);
};

return (
<>
<div className="h-full w-full">
<div className="sticky border-b border-black bg-orange px-4 py-4 md:py-6 md:px-8">
<h1 className="text-5xl font-black">{locationsData?.data[0]?.attributes?.name}</h1>
<LocationSelector className="mt-2" />
<LocationSelector className="mt-2" theme="orange" onChange={handleLocationSelected} />
<CountriesList
className="mt-2"
bgColorClassName="bg-orange"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import Details from './details';
import Modelling from './modelling';

export const SIDEBAR_TYPES = {
export const PANEL_TYPES = {
progress_tracker: 'progress-tracker',
conservation_builder: 'conservation-builder',
};

const SIDEBAR_COMPONENTS = {
[SIDEBAR_TYPES.progress_tracker]: Details,
[SIDEBAR_TYPES.conservation_builder]: Modelling,
[PANEL_TYPES.progress_tracker]: Details,
[PANEL_TYPES.conservation_builder]: Modelling,
};

type MainPanelProps = {
type: keyof typeof SIDEBAR_TYPES;
type PanelsProps = {
type: keyof typeof PANEL_TYPES;
};

const MainPanel: React.FC<MainPanelProps> = ({ type }) => {
const MainPanel: React.FC<PanelsProps> = ({ type }) => {
const Component = SIDEBAR_COMPONENTS[type] || Details;

return <Component />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { useRouter } from 'next/router';

import { useAtomValue } from 'jotai';

import { modellingAtom } from '../../../store';
import { PAGES } from '@/constants/pages';
import { useMapSearchParams } from '@/containers/map/content/map/sync-settings';
import { modellingAtom } from '@/containers/map/store';

import LocationSelector from '../../location-selector';

import ModellingButtons from './modelling-buttons';
import ModellingIntro from './modelling-intro';
import ModellingWidget from './widget';

const SidebarModelling: React.FC = () => {
const { push } = useRouter();
const searchParams = useMapSearchParams();
const { status: modellingStatus } = useAtomValue(modellingAtom);

const showIntro = modellingStatus === 'idle';

const handleLocationSelected = (locationCode) => {
push(`${PAGES.conservationBuilder}/${locationCode}?${searchParams.toString()}`);
};

return (
<>
<div className="h-full w-full overflow-y-auto pb-12">
Expand All @@ -28,6 +40,7 @@ const SidebarModelling: React.FC = () => {
</div>
)}

<LocationSelector className="mt-2" theme="blue" onChange={handleLocationSelected} />
<ModellingButtons className="mt-4" />
</div>
{showIntro && <ModellingIntro />}
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/pages/conservation-builder/[locationCode].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { QueryClient, dehydrate } from '@tanstack/react-query';
import type { GetServerSideProps } from 'next';

import MapLayout from '@/layouts/map';
import { getGetLocationsQueryKey, getGetLocationsQueryOptions } from '@/types/generated/location';
import { LocationListResponse } from '@/types/generated/strapi.schemas';

export const getServerSideProps: GetServerSideProps = async (context) => {
const { query } = context;
const { locationCode = 'GLOB' } = query;

const queryClient = new QueryClient();

await queryClient.prefetchQuery({
...getGetLocationsQueryOptions({
filters: {
code: locationCode,
},
}),
});

const locationsData = queryClient.getQueryData<LocationListResponse>(
getGetLocationsQueryKey({
filters: {
code: locationCode,
},
})
);

if (!locationsData || !locationsData.data) return { notFound: true };

return {
props: {
location: locationsData.data[0].attributes || {
code: 'GLOB',
name: 'Global',
},
dehydratedState: dehydrate(queryClient),
},
};
};

export default function ConservationBuilderPage() {
return null;
}

ConservationBuilderPage.layout = {
Component: MapLayout,
props: {
title: 'Conservation builder',
type: 'conservation-builder',
},
};
13 changes: 0 additions & 13 deletions frontend/src/pages/conservation-builder/index.tsx

This file was deleted.

Loading

0 comments on commit 047773b

Please sign in to comment.