Skip to content

Commit

Permalink
Merge pull request #838 from Vizzuality/nw/MARXAN-828-report
Browse files Browse the repository at this point in the history
feat(front): PDF reports[MARXAN-828]
  • Loading branch information
anamontiaga authored Feb 22, 2022
2 parents ae4bc0a + 3b6375f commit 2210254
Show file tree
Hide file tree
Showing 22 changed files with 2,530 additions and 3 deletions.
2 changes: 1 addition & 1 deletion app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ yarn-error.log*
# storybook
storybook-static

report*
report.
2 changes: 1 addition & 1 deletion app/layout/guide-request/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface GuideRequestProps {
onDismiss?: () => void,
}

export const GuideRequest: React.FC<GuideRequestProps> = ({ onDismiss }:GuideRequestProps) => {
export const GuideRequest: React.FC<GuideRequestProps> = ({ onDismiss }: GuideRequestProps) => {
const { user } = useMe();

const { onActive } = useHelp();
Expand Down
2 changes: 1 addition & 1 deletion app/layout/scenarios/map/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const ScenariosEditMap: React.FC<ScenariosEditMapProps> = () => {
const accessToken = useAccessToken();

const { query } = useRouter();

const { pid, sid } = query;

const scenarioSlice = getScenarioEditSlice(sid);
Expand Down Expand Up @@ -368,7 +369,6 @@ export const ScenariosEditMap: React.FC<ScenariosEditMapProps> = () => {
},
};
}

return null;
};

Expand Down
79 changes: 79 additions & 0 deletions app/layout/scenarios/reports/header/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';

import { useRouter } from 'next/router';

import { format } from 'date-fns';

import { useProjectUsers } from 'hooks/project-users';
import { useProject } from 'hooks/projects';
import { useScenario } from 'hooks/scenarios';

export interface ScenariosReportHeaderProps {
page: number,
totalPages: number,
}

export const ScenariosReportHeader: React.FC<ScenariosReportHeaderProps> = ({
page,
totalPages,
}: ScenariosReportHeaderProps) => {
const { query } = useRouter();
const { pid, sid } = query;

const {
data: projectData,
isFetched: projectDataIsFetched,
} = useProject(pid);

const {
data: scenarioData,
isFetched: scenarioDataIsFetched,
} = useScenario(sid);

const {
data: projectUsers,
isFetched: projectUsersAreFetched,
} = useProjectUsers(pid);

const projectOwner = projectUsers?.find((u) => u.roleName === 'project_owner').user.displayName;

const reportDataIsFetched = projectDataIsFetched
&& scenarioDataIsFetched && projectUsersAreFetched;

return (
reportDataIsFetched && (
<header className="w-full pb-6">

<div className="flex justify-between">
<div className="flex space-x-1 text-xs">
<p className="font-semibold uppercase">
Created by:
</p>
<p className="capitalize">{projectOwner}</p>
</div>

<p className="text-xs font-semibold ">
{`Page ${page}/${totalPages}`}
</p>
</div>

<h1 className="pb-4 text-2xl text-gray-500 font-heading">
{`${projectData.name}-${scenarioData.name}`}
</h1>

<div className="flex space-x-12 text-xxs">
<div className="flex space-x-1">
<p className="font-semibold">Marxan platform version:</p>
<p> V.0.0.1</p>
</div>
<div className="flex space-x-1">
<p className="font-semibold">Date:</p>
<p>{format(new Date().getTime(), 'MM/dd/yyyy')}</p>
</div>
</div>
</header>
)
);
};

export default ScenariosReportHeader;
1 change: 1 addition & 0 deletions app/layout/scenarios/reports/header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
130 changes: 130 additions & 0 deletions app/layout/scenarios/reports/map/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useCallback, useEffect, useState } from 'react';

import { useRouter } from 'next/router';

import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl';
import { LayerManager, Layer } from '@vizzuality/layer-manager-react';

import { useAccessToken } from 'hooks/auth';
import {
usePUGridLayer,
} from 'hooks/map';
import { useProject } from 'hooks/projects';
import { useScenario } from 'hooks/scenarios';
import { useBestSolution } from 'hooks/solutions';

import Loading from 'components/loading';
import Map from 'components/map';

export interface ScenariosReportMapProps {
}

export const ScenariosReportMap: React.FC<ScenariosReportMapProps> = () => {
const [mapInteractive, setMapInteractive] = useState(false);

const accessToken = useAccessToken();

const { query } = useRouter();

const { pid, sid } = query;

const {
data = {},
} = useProject(pid);
const { bbox } = data;

const {
data: scenarioData,
} = useScenario(sid);

const {
data: bestSolutionData,
} = useBestSolution(sid, {
enabled: scenarioData?.ranAtLeastOnce,
});
const bestSolution = bestSolutionData;

const minZoom = 2;
const maxZoom = 20;
const [viewport, setViewport] = useState({});
const [bounds, setBounds] = useState(null);

const PUGridLayer = usePUGridLayer({
cache: Date.now(),
active: true,
sid: sid ? `${sid}` : null,
include: 'results',
sublayers: ['solutions'],
options: {
runId: bestSolution?.runId,
},
});

useEffect(() => {
setBounds({
bbox,
options: { padding: 50 },
viewportOptions: { transitionDuration: 0 },
});
}, [bbox]);

const handleViewportChange = useCallback((vw) => {
setViewport(vw);
}, []);

const handleTransformRequest = (url) => {
if (url.startsWith(process.env.NEXT_PUBLIC_API_URL)) {
return {
url,
headers: {
Authorization: `Bearer ${accessToken}`,
},
};
}
return null;
};

return (
<>
<div
className="relative w-2/3 h-full overflow-hidden"
style={{ height: '146.05mm' }}
>
<Map
className="map-report"
scrollZoom={false}
touchZoom={false}
dragPan={false}
dragRotate={false}
touchRotate={false}
bounds={bounds}
width="100%"
height="100%"
minZoom={minZoom}
maxZoom={maxZoom}
viewport={viewport}
mapboxApiAccessToken={process.env.NEXT_PUBLIC_MAPBOX_API_TOKEN}
mapStyle="mapbox://styles/marxan/ckn4fr7d71qg817kgd9vuom4s"
onMapViewportChange={handleViewportChange}
onMapLoad={() => setMapInteractive(true)}
transformRequest={handleTransformRequest}
>
{(map) => {
return (
<LayerManager map={map} plugin={PluginMapboxGl}>
<Layer key={PUGridLayer.id} {...PUGridLayer} />
</LayerManager>
);
}}
</Map>
</div>
<Loading
visible={!mapInteractive}
className="absolute top-0 bottom-0 left-0 right-0 z-40 flex items-center justify-center w-full h-full bg-black bg-opacity-90"
iconClassName="w-10 h-10 text-primary-500"
/>
</>
);
};

export default ScenariosReportMap;
1 change: 1 addition & 0 deletions app/layout/scenarios/reports/map/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
102 changes: 102 additions & 0 deletions app/layout/scenarios/reports/solutions/page-1/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';

import { useRouter } from 'next/router';

import { useProjectUsers } from 'hooks/project-users';
import { useProject } from 'hooks/projects';
import { useScenario, useScenarioPU } from 'hooks/scenarios';
import { useWDPACategories } from 'hooks/wdpa';

import ScenarioReportsMap from 'layout/scenarios/reports/map';

export interface ScenariosReportPage1Props {

}

export const ScenariosReportPage1: React.FC<ScenariosReportPage1Props> = () => {
const { query } = useRouter();
const { pid, sid } = query;

const {
data: projectData,
isFetched: projectDataIsFetched,
} = useProject(pid);

const {
data: projectUsers,
isFetched: projectUsersDataIsFetched,
} = useProjectUsers(pid);

const contributors = projectUsers?.map((u) => u.user.displayName);

const {
data: scenarioData,
isFetched: scenarioDataIsFetched,
} = useScenario(sid);

const {
data: protectedAreasData,
isFetched: protectedAreasDataIsFetched,
} = useWDPACategories({
adminAreaId: projectData?.adminAreaLevel2Id
|| projectData?.adminAreaLevel1I
|| projectData?.countryId,
customAreaId: !projectData?.adminAreaLevel2Id
&& !projectData?.adminAreaLevel1I
&& !projectData?.countryId ? projectData?.planningAreaId : null,
scenarioId: sid,
});

const protectedAreas = protectedAreasData?.filter((a) => a.selected).map((a) => a.name);

const {
data: PUData,
isFetched: PUDataIsFetched,
} = useScenarioPU(sid);

const reportDataIsFetched = projectDataIsFetched && projectUsersDataIsFetched
&& scenarioDataIsFetched && protectedAreasDataIsFetched && PUDataIsFetched;

return (
reportDataIsFetched && (
<div className="flex space-x-4">

<section className="w-1/3 pt-6 space-y-8 text-xs">
<div>
<p className="font-semibold">Contributors</p>
<p>{contributors.join(', ')}</p>
</div>

<div>
<p className="font-semibold"> Features meeting targets:</p>
</div>

<div>
<p className="font-semibold">Cost surface:</p>
</div>

<div>
<p className="font-semibold">BLM</p>
<p>{scenarioData.metadata.marxanInputParameterFile.BLM || null}</p>
</div>

<div>
<p className="font-semibold">Protected Areas:</p>
<p>{protectedAreas.join(', ')}</p>
</div>

<div>
<p className="font-semibold">No. of planning units</p>
<p>{`In: ${PUData.included.length || 0}`}</p>
<p>{`Out: ${PUData.excluded.length || 0}`}</p>
</div>
</section>

<ScenarioReportsMap />

</div>
)
);
};

export default ScenariosReportPage1;
1 change: 1 addition & 0 deletions app/layout/scenarios/reports/solutions/page-1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './component';
Loading

0 comments on commit 2210254

Please sign in to comment.