Skip to content

Commit

Permalink
list cost surfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
anamontiaga committed Aug 29, 2023
1 parent 20a4cba commit e734cda
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 5 deletions.
49 changes: 49 additions & 0 deletions app/hooks/cost-surface/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useQuery, QueryObserverOptions } from 'react-query';

import { useSession } from 'next-auth/react';

import { CostSurface } from 'types/api/cost-surface';
import { Project } from 'types/api/project';

import { API } from 'services/api';

export function useProjectCostSurfaces<T = CostSurface[]>(
pid: Project['id'],
params: { search?: string; sort?: string; filters?: Record<string, unknown> } = {},
queryOptions: QueryObserverOptions<CostSurface[], Error, T> = {}
) {
const { data: session } = useSession();

const mockData: CostSurface[] = [
{
id: 'Cost Surface Rwanda A',
name: 'Cost Surface Rwanda A',
scenarioUsageCount: 3,
},
{
id: 'Cost Surface Rwanda B',
name: 'Cost Surface Rwanda B',
scenarioUsageCount: 0,
},
{
id: 'Cost Surface Rwanda C',
name: 'Cost Surface Rwanda C',
scenarioUsageCount: 0,
},
];

return useQuery({
queryKey: ['cost-surfaces', pid],
queryFn: async () =>
API.request<CostSurface[]>({
method: 'GET',
url: `/projects/${pid}/cost-surfaces`,
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
params,
}).then(({ data }) => mockData),
enabled: Boolean(pid),
...queryOptions,
});
}
16 changes: 16 additions & 0 deletions app/hooks/cost-surface/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AxiosRequestConfig } from 'axios';

export interface UseWDPACategoriesProps {
adminAreaId?: string;
customAreaId?: string;
scenarioId: string[] | string;
}

export interface UseSaveScenarioProtectedAreasProps {
requestConfig?: AxiosRequestConfig;
}

export interface SaveScenarioProtectedAreasProps {
data: unknown;
id: string[] | string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type DataItem = {
id: string;
name: string;
scenarios: number;
tag: string;
tag?: string;
isVisibleOnMap: boolean;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,135 @@
import { useState, useCallback, useEffect, ChangeEvent } from 'react';

import { useRouter } from 'next/router';

import { useAppDispatch, useAppSelector } from 'store/hooks';
import { setSelectedCostSurfaces as setVisibleCostSurfaces } from 'store/slices/projects/[id]';

import { useProjectCostSurfaces } from 'hooks/cost-surface';

import ActionsMenu from 'layout/project/sidebar/project/inventory-panel/features/actions-menu';
import FeaturesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu';
import { CostSurface } from 'types/api/cost-surface';

import InventoryTable, { type DataItem } from '../components/inventory-table';

const COST_SURFACE_TABLE_COLUMNS = {
name: 'Name',
};

const InventoryPanelCostSurface = ({ noData: noDataMessage }: { noData: string }): JSX.Element => {
return <div className="flex h-[200px] items-center justify-center">{noDataMessage}</div>;
const dispatch = useAppDispatch();
const { selectedCostSurfaces: visibleCostSurfaces, search } = useAppSelector(
(state) => state['/projects/[id]']
);

const { query } = useRouter();
const { pid } = query as { pid: string };

const [selectedCostSurfaceIds, setSelectedCostSurfaceIds] = useState<CostSurface['id'][]>([]);
const [filters, setFilters] = useState<Parameters<typeof useProjectCostSurfaces>[1]>({
sort: COST_SURFACE_TABLE_COLUMNS.name,
});

const allProjectCostSurfacesQuery = useProjectCostSurfaces(
pid,
{
...filters,
search,
},
{
select: (data) =>
data?.map((cs) => ({
id: cs.id,
name: cs.name,
scenarioUsageCount: cs.scenarioUsageCount,
})),
keepPreviousData: true,
placeholderData: [],
}
);

const costSurfaceIds = allProjectCostSurfacesQuery.data?.map((cs) => cs.id);

const handleSelectAll = useCallback(
(evt: ChangeEvent<HTMLInputElement>) => {
setSelectedCostSurfaceIds(evt.target.checked ? costSurfaceIds : []);
},
[costSurfaceIds]
);

const handleSelectCostSurface = useCallback((evt: ChangeEvent<HTMLInputElement>) => {
if (evt.target.checked) {
setSelectedCostSurfaceIds((prevSelectedCostSurface) => [
...prevSelectedCostSurface,
evt.target.value,
]);
} else {
setSelectedCostSurfaceIds((prevSelectedCostSurface) =>
prevSelectedCostSurface.filter((costSurfaceId) => costSurfaceId !== evt.target.value)
);
}
}, []);

useEffect(() => {
setSelectedCostSurfaceIds([]);
}, [search]);

const toggleSeeOnMap = useCallback(
(costSurfaceId: CostSurface['id']) => {
const newSelectedCostSurfaces = [...visibleCostSurfaces];
if (!newSelectedCostSurfaces.includes(costSurfaceId)) {
newSelectedCostSurfaces.push(costSurfaceId);
} else {
const i = newSelectedCostSurfaces.indexOf(costSurfaceId);
newSelectedCostSurfaces.splice(i, 1);
}
dispatch(setVisibleCostSurfaces(newSelectedCostSurfaces));
},
[dispatch, visibleCostSurfaces]
);

const handleSort = useCallback(
(_sortType: (typeof filters)['sort']) => {
const sort = filters.sort === _sortType ? `-${_sortType}` : _sortType;

setFilters((prevFilters) => ({
...prevFilters,
sort,
}));
},
[filters.sort]
);

const displayBulkActions = selectedCostSurfaceIds.length > 0;

const data: DataItem[] = allProjectCostSurfacesQuery.data?.map((wdpa) => ({
...wdpa,
name: wdpa.name,
scenarios: wdpa.scenarioUsageCount,
isVisibleOnMap: visibleCostSurfaces?.includes(wdpa.id),
}));

return (
<div className="space-y-6">
<InventoryTable
loading={allProjectCostSurfacesQuery.isFetching}
data={data}
noDataMessage={noDataMessage}
columns={COST_SURFACE_TABLE_COLUMNS}
sorting={filters.sort}
selectedIds={selectedCostSurfaceIds}
onSortChange={handleSort}
onSelectAll={handleSelectAll}
onSelectRow={handleSelectCostSurface}
onToggleSeeOnMap={toggleSeeOnMap}
ActionsComponent={ActionsMenu}
/>
{displayBulkActions && (
<FeaturesBulkActionMenu selectedFeaturesIds={selectedCostSurfaceIds} />
)}
</div>
);
};

export default InventoryPanelCostSurface;
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { setSelectedFeatures as setVisibleFeatures } from 'store/slices/projects

import { useAllFeatures } from 'hooks/features';

import ActionsMenu from 'layout/project/sidebar/project/inventory-panel/features/actions-menu';
import FeaturesBulkActionMenu from 'layout/project/sidebar/project/inventory-panel/features/bulk-action-menu';
import { Feature } from 'types/api/feature';

import InventoryTable, { type DataItem } from '../components/inventory-table';

import ActionsMenu from './actions-menu';
import FeaturesBulkActionMenu from './bulk-action-menu';

const FEATURES_TABLE_COLUMNS = {
name: 'featureClassName',
tag: 'tag',
Expand Down
56 changes: 56 additions & 0 deletions app/services/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import axios, { AxiosResponse, CreateAxiosDefaults, isAxiosError } from 'axios';
import Jsona from 'jsona';
import { signOut } from 'next-auth/react';

const dataFormatter = new Jsona();

const APIConfig: CreateAxiosDefaults<unknown> = {
baseURL: `${process.env.NEXT_PUBLIC_API_URL}/api/v1`,
headers: { 'Content-Type': 'application/json' },
} satisfies CreateAxiosDefaults;

export const JSONAPI = axios.create({
...APIConfig,
transformResponse: (data) => {
try {
const parsedData = JSON.parse(data);
return {
data: dataFormatter.deserialize(parsedData),
meta: parsedData.meta,
};
} catch (error: unknown) {
if (isAxiosError(error)) {
throw new Error(error.response.statusText);
}
throw error;
}
},
});

const onResponseSuccess = (response: AxiosResponse<unknown>) => response;

const onResponseError = async (error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
if (isAxiosError(error)) {
if (error.response.status === 401) {
await signOut();
}
}
// Do something with response error
return Promise.reject(error as Error);
};

JSONAPI.interceptors.response.use(onResponseSuccess, onResponseError);

export const API = axios.create({
...APIConfig,
});

API.interceptors.response.use(onResponseSuccess, onResponseError);

const APIInstances = {
JSONAPI,
API,
};

export default APIInstances;
10 changes: 10 additions & 0 deletions app/store/slices/projects/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ProjectShowStateProps {
sort: string;
layerSettings: Record<string, any>;
selectedFeatures: string[];
selectedCostSurfaces: string[];
isSidebarOpen: boolean;
}

Expand All @@ -15,6 +16,7 @@ const initialState: ProjectShowStateProps = {
sort: '-lastModifiedAt',
layerSettings: {},
selectedFeatures: [],
selectedCostSurfaces: [],
isSidebarOpen: true,
} satisfies ProjectShowStateProps;

Expand Down Expand Up @@ -62,6 +64,13 @@ const projectsDetailSlice = createSlice({
) => {
state.selectedFeatures = action.payload;
},
// COST SURFACE
setSelectedCostSurfaces: (
state,
action: PayloadAction<ProjectShowStateProps['selectedCostSurfaces']>
) => {
state.selectedCostSurfaces = action.payload;
},
},
});

Expand All @@ -71,6 +80,7 @@ export const {
setSort,
setLayerSettings,
setSelectedFeatures,
setSelectedCostSurfaces,
setSidebarVisibility,
} = projectsDetailSlice.actions;

Expand Down
5 changes: 5 additions & 0 deletions app/types/api/cost-surface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CostSurface {
id: string;
name: string;
scenarioUsageCount: number;
}

0 comments on commit e734cda

Please sign in to comment.