Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[N/A] splitted features on map #1613

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with
the frontend app
- `strat`: make _stratification_ functionality available for conservation
features in the frontend app
- `upcomingChanges`: displays a banner warning about the upcoming changes to the app

- `NEXT_PUBLIC_CONTACT_EMAIL`: Email address to be used for general contact inquiries.

Expand Down
1 change: 0 additions & 1 deletion app/components/features/selected-item/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ export const Item: React.FC<ItemProps> = ({
) : (
<HiEyeOff className="h-4 w-4" />
)}
{/* <Icon className="h-4 w-4" icon={isShown ? SHOW_SVG : HIDE_SVG} /> */}
</button>
</Tooltip>

Expand Down
2 changes: 1 addition & 1 deletion app/components/forms/checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const THEME = {
};

export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
theme?: 'dark' | 'light';
theme?: 'dark' | 'light' | 'light-secondary';
status?: 'none' | 'valid' | 'error' | 'disabled';
}

Expand Down
24 changes: 24 additions & 0 deletions app/components/forms/select/constants/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const SELECT_THEME = {
base: 'text-sm text-gray-400',
highlighted: 'text-sm bg-gray-800 text-white',
disabled: 'text-sm opacity-50 pointer-events-none',
clearSelectionLabel: '',
},
},
light: {
Expand All @@ -33,6 +34,7 @@ export const SELECT_THEME = {
base: 'text-sm text-gray-900',
highlighted: 'text-sm bg-gray-100',
disabled: 'text-sm opacity-50 pointer-events-none',
clearSelectionLabel: '',
},
},
'light-square': {
Expand All @@ -51,6 +53,26 @@ export const SELECT_THEME = {
base: 'text-sm text-gray-100',
highlighted: 'text-sm bg-gray-200 text-gray-900',
disabled: 'text-sm opacity-50 pointer-events-none',
clearSelectionLabel: '',
},
},
modal: {
container: 'text-gray-700 bg-white ring-1 ring-gray-900 rounded-lg',
open: 'ring-1 ring-primary-400 bg-white text-gray-700 rounded',
closed: 'text-gray-500',
prefix: {
base: 'text-gray-900',
},
icon: {
closed: 'text-gray-700',
open: 'text-primary-500 transform rotate-180',
disabled: 'text-gray-100',
},
item: {
base: 'text-sm text-gray-900',
highlighted: 'text-sm bg-gray-200 text-gray-900',
disabled: 'text-sm opacity-50 pointer-events-none',
clearSelectionLabel: 'font-semibold',
},
},
states: {
Expand All @@ -61,9 +83,11 @@ export const SELECT_THEME = {
sizes: {
base: 'pl-4 pr-10 text-sm',
s: 'pl-4 pr-10 text-sm',
xs: 'pl-4 pr-10 text-base',
label: {
base: 'py-3',
s: 'py-1',
xs: 'py-2 text-base',
},
},
};
Expand Down
2 changes: 1 addition & 1 deletion app/components/forms/select/multi/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export const MultiSelect: React.FC<SelectProps> = ({
<div
className={cn({
'c-multi-select-dropdown': true,
'z-50': true,
'z-[60]': true,
// The content of `<Menu />` must always be in the DOM so that Downshift can get the ref
// to the `<ul />` element through `getMenuProps`
invisible: !isOpen,
Expand Down
1 change: 1 addition & 0 deletions app/components/forms/select/single/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export const SingleSelect: React.FC<SelectProps> = ({
<span
className={cn({
'ml-6': !!option.checkbox,
[THEME[theme].item.clearSelectionLabel]: !option.value,
})}
>
{option.label}
Expand Down
4 changes: 2 additions & 2 deletions app/components/forms/select/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ReactNode, FocusEventHandler } from 'react';

interface SelectThemeProps {
theme: 'dark' | 'light' | 'light-square';
size: 'base' | 's';
theme: 'dark' | 'light' | 'light-square' | 'modal';
size: 'base' | 's' | 'xs';
status?: 'none' | 'error' | 'valid';
maxHeight?: number | string;
}
Expand Down
2 changes: 1 addition & 1 deletion app/components/uploader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const Uploader = ({
onClose,
}: PropsWithChildren<UploaderProps>): JSX.Element => {
return (
<div className="mb-5 mt-3">
<div>
<Button
className="dropzone w-full cursor-pointer py-1 hover:bg-gray-600"
theme="secondary"
Expand Down
119 changes: 85 additions & 34 deletions app/hooks/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { useMemo } from 'react';

import { useQuery, useMutation, useQueryClient, QueryObserverOptions } from 'react-query';

import { useRouter } from 'next/router';

import { AxiosRequestConfig } from 'axios';
import chroma from 'chroma-js';
import Fuse from 'fuse.js';
import flatten from 'lodash/flatten';
import orderBy from 'lodash/orderBy';
import partition from 'lodash/partition';
import { useSession } from 'next-auth/react';

import { COLORS } from 'hooks/map/constants';

import { ItemProps as IntersectItemProps } from 'components/features/intersect-item/component';
import { ItemProps as RawItemProps } from 'components/features/raw-item/component';
import { Feature } from 'types/api/feature';
Expand Down Expand Up @@ -171,13 +176,17 @@ export function useSelectedFeatures(
filters: UseFeaturesFiltersProps = {},
queryOptions = {}
) {
const { query } = useRouter();
const { pid } = query as { pid: string };
const { data: session } = useSession();
const { search } = filters;

const queryClient = useQueryClient();

const featureColorQueryState =
queryClient.getQueryState<{ id: Feature['id']; color: string }[]>('feature-colors');
// const featureColorQueryState =
// queryClient.getQueryState<{ id: Feature['id']; color: string }[]>('feature-colors');

const featureColors = useColorFeatures(pid, sid);

const fetchFeatures = () =>
SCENARIOS.request({
Expand All @@ -193,8 +202,7 @@ export function useSelectedFeatures(

return useQuery(['selected-features', sid], fetchFeatures, {
...queryOptions,
enabled:
!!sid && ((featureColorQueryState && featureColorQueryState.status === 'success') || true),
enabled: (!!sid && featureColors?.length > 0) || true,
select: ({ data }) => {
const { features = [] } = data;

Expand Down Expand Up @@ -276,10 +284,7 @@ export function useSelectedFeatures(
min: amountMin,
max: amountMax,
},
color: featureColorQueryState
? featureColorQueryState?.data?.find(({ id }) => featureId === id)?.color
: null,

color: featureColors.find(({ id }) => featureId === id)?.color,
// SPLIT
splitOptions,
splitSelected,
Expand All @@ -291,12 +296,12 @@ export function useSelectedFeatures(
};
});

// Filter
if (search) {
const fuse = new Fuse(parsedData, {
keys: ['name'],
threshold: 0.25,
});

parsedData = fuse.search(search).map((f) => {
return f.item;
});
Expand All @@ -314,7 +319,7 @@ export function useTargetedFeatures(
queryOptions = {}
) {
const { data: session } = useSession();
const { search } = filters;
const { search, sort } = filters;

const fetchFeatures = () =>
SCENARIOS.request({
Expand All @@ -325,10 +330,16 @@ export function useTargetedFeatures(
},
params: {
disablePagination: true,
...(search && {
q: search,
}),
...(sort && {
sort,
}),
},
}).then(({ data }) => data);

return useQuery(['targeted-features', sid], fetchFeatures, {
return useQuery(['targeted-features', sid, filters], fetchFeatures, {
...queryOptions,
retry: false,
enabled: !!sid,
Expand Down Expand Up @@ -405,6 +416,11 @@ export function useTargetedFeatures(
name: alias || featureClassName,
type: tag,
description,
// todo: missing scenarioUsageCount from API
scenarios: d.scenarioUsageCount,
// todo: missing tag from API
tag: d.tag,
isCustom: d.metadata?.isCustom,

// SPLIT
splitOptions,
Expand All @@ -429,7 +445,16 @@ export function useTargetedFeatures(
}

// Sort
parsedData = orderBy(parsedData, ['name'], ['desc']);
if (sort) {
parsedData.sort((a, b) => {
if (sort.startsWith('-')) {
const _sort = sort.substring(1);
return b[_sort].localeCompare(a[_sort]);
}

return a[sort].localeCompare(b[sort]);
});
}

parsedData = flatten(
parsedData.map((s) => {
Expand All @@ -439,25 +464,27 @@ export function useTargetedFeatures(

// Generate splitted features to target
if (isSplitted) {
return splitFeaturesSelected
.sort((a, b) => a.name.localeCompare(b.name))
.map((sf) => {
const { id: sfId, name: sfName, marxanSettings: sfMarxanSettings } = sf;

return {
...sf,
id: `${id}-${sfId}`,
parentId: id,
name: `${name} / ${sfName}`,
splitted: true,
splitSelected,
splitFeaturesSelected,
...(!!sfMarxanSettings && {
target: sfMarxanSettings.prop * 100,
fpf: sfMarxanSettings.fpf,
}),
};
});
return (
splitFeaturesSelected
// .sort((a, b) => a.name.localeCompare(b.name))
.map((sf) => {
const { id: sfId, name: sfName, marxanSettings: sfMarxanSettings } = sf;

return {
...sf,
id: `${id}-${sfId}`,
parentId: id,
name: `${name} / ${sfName}`,
splitted: true,
splitSelected,
splitFeaturesSelected,
...(!!sfMarxanSettings && {
target: sfMarxanSettings.prop * 100,
fpf: sfMarxanSettings.fpf,
}),
};
})
);
}

// if (isIntersected) {
Expand Down Expand Up @@ -518,11 +545,9 @@ export function useSaveSelectedFeatures({
};

return useMutation(saveFeature, {
onSuccess: (data, variables, context) => {
onSuccess: (data, variables) => {
const { id } = variables;
queryClient.setQueryData(['selected-features', id], { data: data?.data });

console.info('Succces', data, variables, context);
},
onError: (error, variables, context) => {
// An error happened!
Expand Down Expand Up @@ -585,6 +610,7 @@ export function useUploadFeaturesShapefile({
onSuccess: async (data, variables) => {
const { id: projectId } = variables;
await queryClient.invalidateQueries(['all-features', projectId]);
await queryClient.invalidateQueries(['all-paginated-features', projectId]);
},
});
}
Expand Down Expand Up @@ -615,6 +641,7 @@ export function useUploadFeaturesCSV({
onSuccess: async (data, variables) => {
const { id: projectId } = variables;
await queryClient.invalidateQueries(['all-features', projectId]);
await queryClient.invalidateQueries(['all-paginated-features', projectId]);
},
});
}
Expand Down Expand Up @@ -676,6 +703,7 @@ export function useEditFeatureTag() {
await queryClient.invalidateQueries(['feature', featureId]);
await queryClient.invalidateQueries(['project-tags', projectId]);
await queryClient.invalidateQueries(['all-features', projectId]);
await queryClient.invalidateQueries(['all-paginated-features', projectId]);
},
onError: (error, variables, context) => {
console.info('Error', error, variables, context);
Expand Down Expand Up @@ -709,6 +737,7 @@ export function useDeleteFeatureTag() {
const { featureId, projectId } = variables;
await queryClient.invalidateQueries(['feature', featureId]);
await queryClient.invalidateQueries(['all-features', projectId]);
await queryClient.invalidateQueries(['all-paginated-features', projectId]);
},
onError: (error, variables, context) => {
console.info('Error', error, variables, context);
Expand All @@ -728,3 +757,25 @@ export function useProjectFeatures(
}
);
}

export function useColorFeatures(projectId: Project['id'], sid: Scenario['id']) {
const useAllFeaturesQuery = useAllFeatures(projectId, {});
const targetedFeaturesQuery = useTargetedFeatures(sid);

if (targetedFeaturesQuery.isSuccess && useAllFeaturesQuery.isSuccess) {
const data = [...(useAllFeaturesQuery.data?.data || []), ...targetedFeaturesQuery.data];
return data.map(({ id }, index) => {
const color =
data.length > COLORS['features-preview'].ramp.length
? chroma.scale(COLORS['features-preview'].ramp).colors(data.length)[index]
: COLORS['features-preview'].ramp[index];

return {
id,
color,
};
});
}

return [];
}
2 changes: 2 additions & 0 deletions app/hooks/features/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { AxiosRequestConfig } from 'axios';

export interface UseFeaturesFiltersProps {
search?: string;
sort?: string;
tag?: string;
}

export interface UseSaveSelectedFeaturesProps {
Expand Down
Loading
Loading