diff --git a/app/assets/scripts/actions/index.js b/app/assets/scripts/actions/index.js
index cee3fd126..9bbeb590f 100644
--- a/app/assets/scripts/actions/index.js
+++ b/app/assets/scripts/actions/index.js
@@ -59,6 +59,40 @@ export const getMe = () => (
fetchJSON('api/v2/user/me/', GET_ME, withToken())
);
+export const GET_REGIONAL_PROJECTS = 'GET_REGIONAL_PROJECTS';
+export const getRegionalProjects = (regionId, filterValues) => {
+ const filters = {
+ region: regionId,
+ limit: 9999,
+ ...filterValues
+ };
+ const query = buildAPIQS(filters, { arrayFormat: 'comma' });
+ return fetchJSON(`api/v2/project/?${query}`, GET_REGIONAL_PROJECTS, withToken());
+};
+
+export const GET_REGIONAL_PROJECTS_OVERVIEW = 'GET_REGIONAL_PROJECTS_OVERVIEW';
+export function getRegionalProjectsOverview (regionId) {
+ return fetchJSON(`api/v2/region-project/${regionId}/overview/`, GET_REGIONAL_PROJECTS_OVERVIEW, withToken());
+}
+
+export const GET_REGIONAL_MOVEMENT_ACTIVITIES = 'GET_REGIONAL_MOVEMENT_ACTIVITIES';
+export function getRegionalMovementActivities (regionId, filters) {
+ const query = buildAPIQS(filters, { arrayFormat: 'comma' });
+ return fetchJSON(`api/v2/region-project/${regionId}/movement-activities/?${query}`, GET_REGIONAL_MOVEMENT_ACTIVITIES, withToken());
+}
+
+export const GET_NATIONAL_SOCIETY_ACTIVITIES = 'GET_NATIONAL_SOCIETY_ACTIVITIES';
+export function getNationalSocietyActivities (regionId, filters) {
+ const query = buildAPIQS(filters, { arrayFormat: 'comma' });
+
+ return fetchJSON(`api/v2/region-project/${regionId}/national-society-activities/?${query}`, GET_NATIONAL_SOCIETY_ACTIVITIES, withToken());
+}
+
+export const GET_NATIONAL_SOCIETY_ACTIVITIES_WO_FILTERS = 'GET_NATIONAL_SOCIETY_ACTIVITIES_WO_FILTERS';
+export function getNationalSocietyActivitiesWoFilters (regionId) {
+ return fetchJSON(`api/v2/region-project/${regionId}/national-society-activities/`, GET_NATIONAL_SOCIETY_ACTIVITIES_WO_FILTERS, withToken());
+}
+
export const GET_PROJECTS = 'GET_PROJECTS';
export function getProjects (countryId, filterValues) {
const filters = {
@@ -66,7 +100,7 @@ export function getProjects (countryId, filterValues) {
...filterValues
};
const f = buildAPIQS(filters);
- return fetchJSON(`api/v2/project/?${f}`, GET_PROJECTS, withToken());
+ return fetchJSON(`api/v2/project/?${f}`, GET_PROJECTS, withToken(), { countryId });
}
export const POST_PROJECT = 'POST_PROJECT';
diff --git a/app/assets/scripts/components/formatted-number.js b/app/assets/scripts/components/formatted-number.js
index c9f563d05..11c71bdbd 100644
--- a/app/assets/scripts/components/formatted-number.js
+++ b/app/assets/scripts/components/formatted-number.js
@@ -29,7 +29,8 @@ const FormattedNumber = ({
if (addSeparator) {
displayNumber = addCommaSeparator(displayNumber);
} else if (fixedTo) {
- displayNumber = Number.parseFloat(displayNumber).toFixed(fixedTo);
+ const shouldFix = (displayNumber - Math.floor(displayNumber)) !== 0;
+ displayNumber = Number.parseFloat(displayNumber).toFixed(shouldFix ? fixedTo : 0);
}
}
diff --git a/app/assets/scripts/components/text-output.js b/app/assets/scripts/components/text-output.js
index f3215b250..a5ad8598d 100644
--- a/app/assets/scripts/components/text-output.js
+++ b/app/assets/scripts/components/text-output.js
@@ -4,36 +4,54 @@ import _cs from 'classnames';
import FormattedNumber from './formatted-number';
import FormattedDate from './formatted-date';
-const TextOutput = ({
- className,
- label,
- value,
- addSeparatorToValue,
- normalizeValue,
- fixedTo,
- type = 'string',
-}) => (
-
-
- {label}
-
-
- { type === 'string' && value }
- { type === 'number' && (
-
- )}
- { type === 'date' && (
-
- )}
-
-
+const isValueEmpty = (value) => (
+ value === null || value === '' || value === undefined
);
+function TextOutput (p) {
+ const {
+ className,
+ label,
+ value,
+ addSeparatorToValue,
+ normalizeValue,
+ fixedTo,
+ type = 'string',
+ reverseOrder,
+ hideEmptyValue,
+ } = p;
+
+ return (
+ (hideEmptyValue && isValueEmpty(value)) ? (
+ null
+ ) : (
+
+
+ {label}
+
+
+ { type === 'string' && value }
+ { type === 'number' && (
+
+ )}
+ { type === 'date' && (
+
+ )}
+
+
+ )
+ );
+}
+
export default TextOutput;
diff --git a/app/assets/scripts/reducers/index.js b/app/assets/scripts/reducers/index.js
index 27586cafe..62f43b0b5 100644
--- a/app/assets/scripts/reducers/index.js
+++ b/app/assets/scripts/reducers/index.js
@@ -2,6 +2,8 @@
import { combineReducers } from 'redux';
import { systemAlertsReducer } from '../components/system-alerts';
+import { createReducer } from '../utils/reducer-utils';
+
import user from './user';
import profile from './profile';
import countries from './countries';
@@ -66,6 +68,11 @@ export const reducers = {
projectForm,
projectDelete,
countryOverview,
+ regionalProjectsOverview: createReducer('GET_REGIONAL_PROJECTS_OVERVIEW'),
+ regionalMovementActivities: createReducer('GET_REGIONAL_MOVEMENT_ACTIVITIES'),
+ nationalSocietyActivities: createReducer('GET_NATIONAL_SOCIETY_ACTIVITIES'),
+ nationalSocietyActivitiesWoFilters: createReducer('GET_NATIONAL_SOCIETY_ACTIVITIES_WO_FILTERS'),
+ regionalProjects: createReducer('GET_REGIONAL_PROJECTS'),
me,
};
diff --git a/app/assets/scripts/reducers/projects.js b/app/assets/scripts/reducers/projects.js
index 8901019b4..062482ecd 100644
--- a/app/assets/scripts/reducers/projects.js
+++ b/app/assets/scripts/reducers/projects.js
@@ -5,33 +5,19 @@ import {
stateSuccess,
} from '../utils/reducer-utils';
-const initialState = {
- fetching: false,
- fetched: false,
- receivedAt: null,
- data: {}
-};
+const initialState = {};
export default function reducer (state = initialState, action) {
let newState = { ...state };
switch (action.type) {
case 'GET_PROJECTS_INFLIGHT':
- newState = {
- ...newState,
- ...stateInflight(state, action),
- };
+ newState[action.countryId] = stateInflight(state, action);
break;
case 'GET_PROJECTS_FAILED':
- newState = {
- ...newState,
- ...stateError(state, action),
- };
+ newState[action.countryId] = stateError(state, action);
break;
case 'GET_PROJECTS_SUCCESS':
- newState = {
- ...newState,
- ...stateSuccess(state, action),
- };
+ newState[action.countryId] = stateSuccess(state, action);
break;
}
diff --git a/app/assets/scripts/selectors/index.js b/app/assets/scripts/selectors/index.js
index 3d238d6bb..4466309b5 100644
--- a/app/assets/scripts/selectors/index.js
+++ b/app/assets/scripts/selectors/index.js
@@ -1,7 +1,38 @@
+const initialState = {
+ fetching: false,
+ fetched: false,
+ receivedAt: null,
+ data: {}
+};
+
export const countryOverviewSelector = (state) => (
state.countryOverview
);
+export const countryProjectSelector = (state, id) => (
+ state.projects[id] || initialState
+);
+
export const meSelector = (state) => (
state.me
);
+
+export const regionalMovementActivitiesSelector = (state) => (
+ state.regionalMovementActivities
+);
+
+export const regionalProjectsOverviewSelector = (state) => (
+ state.regionalProjectsOverview
+);
+
+export const nationalSocietyActivitiesSelector = (state) => (
+ state.nationalSocietyActivities
+);
+
+export const nationalSocietyActivitiesWoFiltersSelector = (state) => (
+ state.nationalSocietyActivitiesWoFilters
+);
+
+export const regionalProjectsSelector = (state) => (
+ state.regionalProjects
+);
diff --git a/app/assets/scripts/utils/constants.js b/app/assets/scripts/utils/constants.js
index 51865febf..ee9d44367 100644
--- a/app/assets/scripts/utils/constants.js
+++ b/app/assets/scripts/utils/constants.js
@@ -24,67 +24,67 @@ export const sectorList = [
key: '0',
title: 'WASH',
color: '#66c2a5',
- inputValue: 'Wash',
+ inputValue: '0',
},
{
key: '1',
title: 'PGI',
color: '#fc8d62',
- inputValue: 'Pgi',
+ inputValue: '1',
},
{
key: '2',
title: 'CEA',
color: '#8da0cb',
- inputValue: 'Cea',
+ inputValue: '2',
},
{
key: '3',
title: 'Migration',
color: '#e78ac3',
- inputValue: 'Migration',
+ inputValue: '3',
},
{
key: '4',
title: 'Health (public)',
color: '#a6d854',
- inputValue: 'Health Public',
+ inputValue: '4',
},
{
key: '5',
title: 'DRR',
color: '#ffd92f',
- inputValue: 'Drr',
+ inputValue: '5',
},
{
key: '6',
title: 'Shelter',
color: '#e5c494',
- inputValue: 'Shelter',
+ inputValue: '6',
},
{
key: '7',
title: 'NS Strengthening',
color: '#b3b3b3',
- inputValue: 'NS Strengthening',
+ inputValue: '7',
},
{
key: '8',
title: 'Education',
color: '#b3b3b3',
- inputValue: 'Education',
+ inputValue: '8',
},
{
key: '9',
title: 'Livelihoods and basic needs',
color: '#b3b3b3',
- inputValue: 'Livelihoods And Basic Needs',
+ inputValue: '9',
},
{
key: '10',
title: 'Health (clinical)',
color: '#a6d854',
- inputValue: 'Health Clinical',
+ inputValue: '10',
},
];
@@ -93,79 +93,85 @@ export const secondarySectorList = [
key: '0',
title: 'WASH',
color: '#66c2a5',
- inputValue: 'Wash',
+ inputValue: '0',
},
{
key: '1',
title: 'PGI',
color: '#fc8d62',
- inputValue: 'Pgi',
+ inputValue: '1',
},
{
key: '2',
title: 'CEA',
color: '#8da0cb',
- inputValue: 'Cea',
+ inputValue: '2',
},
{
key: '3',
title: 'Migration',
color: '#e78ac3',
- inputValue: 'Migration',
+ inputValue: '3',
},
{
key: '4',
title: 'Health (public)',
color: '#a6d854',
- inputValue: 'Health Public',
+ inputValue: '4',
},
{
key: '5',
title: 'DRR',
color: '#ffd92f',
- inputValue: 'Drr',
+ inputValue: '5',
},
{
key: '6',
title: 'Shelter',
color: '#e5c494',
- inputValue: 'Shelter',
+ inputValue: '6',
},
{
key: '7',
title: 'NS Strengthening',
color: '#b3b3b3',
- inputValue: 'NS Strengthening',
+ inputValue: '7',
},
{
key: '8',
title: 'Education',
color: '#b3b3b3',
- inputValue: 'Education',
+ inputValue: '8',
},
{
key: '9',
title: 'Livelihoods and basic needs',
color: '#b3b3b3',
- inputValue: 'Livelihoods And Basic Needs',
+ inputValue: '9',
},
{
key: '10',
title: 'Recovery',
color: '#b3b3b3',
- inputValue: 'Recovery',
+ inputValue: '10',
},
{
key: '11',
title: 'Internal displacement',
color: '#b3b3b3',
- inputValue: 'Internal Displacement',
+ inputValue: '11',
},
{
key: '12',
title: 'Health (clinical)',
color: '#a6d854',
- inputValue: 'Health Clinical',
+ inputValue: '12',
+ },
+ {
+ key: '13',
+ title: 'COVID',
+ color: '#a6d854',
+ inputValue: '13',
},
];
@@ -193,8 +199,8 @@ export const statusList = [
export const statuses = listToMap(statusList, d => d.key, d => d.title);
export const operationTypeList = [
- { value: 'Programme', label: 'Programme' },
- { value: 'Emergency Operation', label: 'Emergency operation' },
+ { value: '0', label: 'Programme' },
+ { value: '1', label: 'Emergency operation' },
];
export const operationTypes = {
diff --git a/app/assets/scripts/utils/country-centroids.js b/app/assets/scripts/utils/country-centroids.js
index 585ecb5dc..e137e9a89 100644
--- a/app/assets/scripts/utils/country-centroids.js
+++ b/app/assets/scripts/utils/country-centroids.js
@@ -1,4 +1,6 @@
'use strict';
+import { countryIsoMapById } from './field-report-constants';
+
const centroids = {
AD: [1.6261, 42.54203],
AE: [54.33582, 23.89438],
@@ -203,4 +205,9 @@ export function getCentroid (iso) {
return centroids[iso.toUpperCase()] || [0, 0];
}
+export function getCentroidByCountryId (id) {
+ const iso2 = countryIsoMapById[id] || '';
+ return getCentroid(iso2);
+}
+
export default centroids;
diff --git a/app/assets/scripts/utils/reducer-utils.js b/app/assets/scripts/utils/reducer-utils.js
index ae57697d3..de0e5902b 100644
--- a/app/assets/scripts/utils/reducer-utils.js
+++ b/app/assets/scripts/utils/reducer-utils.js
@@ -23,3 +23,34 @@ export const stateSuccess = (state, action) => {
data: action.data
});
};
+
+const initialState = {
+ fetching: false,
+ fetched: false,
+ receivedAt: null,
+ data: {}
+};
+
+export function createReducer (actionName) {
+ const inflight = `${actionName}_INFLIGHT`;
+ const failed = `${actionName}_FAILED`;
+ const success = `${actionName}_SUCCESS`;
+
+ return (state = initialState, action) => {
+ let newState = state;
+
+ switch (action.type) {
+ case inflight:
+ newState = stateInflight(state, action);
+ break;
+ case failed:
+ newState = stateError(state, action);
+ break;
+ case success:
+ newState = stateSuccess(state, action);
+ break;
+ }
+
+ return newState;
+ };
+}
diff --git a/app/assets/scripts/utils/request.js b/app/assets/scripts/utils/request.js
index e13fc472c..4e7380070 100644
--- a/app/assets/scripts/utils/request.js
+++ b/app/assets/scripts/utils/request.js
@@ -1,6 +1,7 @@
const emptyObject = {};
+const emptyList = [];
-export const getDataFromResponse = (response, defaultValue = emptyObject) => {
+export const getDataFromResponse = (response = emptyObject, defaultValue = emptyObject) => {
if (!response) {
return defaultValue;
}
@@ -11,3 +12,10 @@ export const getDataFromResponse = (response, defaultValue = emptyObject) => {
return response.data;
};
+
+export const getResultsFromResponse = (response, defaultValue = emptyList) => {
+ const data = getDataFromResponse(response);
+ const { results = defaultValue } = data || emptyObject;
+
+ return results;
+};
diff --git a/app/assets/scripts/utils/utils.js b/app/assets/scripts/utils/utils.js
index 7d745c91b..4d64e922f 100644
--- a/app/assets/scripts/utils/utils.js
+++ b/app/assets/scripts/utils/utils.js
@@ -5,6 +5,7 @@ import _toNumber from 'lodash.tonumber';
import _find from 'lodash.find';
import _filter from 'lodash.filter';
import { DateTime } from 'luxon';
+import { isNotDefined } from '@togglecorp/fujs';
import { getCentroid } from './country-centroids';
import { disasterType } from './field-report-constants';
@@ -294,3 +295,27 @@ export function getRecordsByType (types, records) {
return sortedRecordsByType;
}
+
+export const convertJsonToCsv = (data, columnDelimiter = ',', lineDelimiter = '\n', emptyValue = '') => {
+ if (!data || data.length <= 0) {
+ return undefined;
+ }
+
+ let result = '';
+
+ data.forEach((items) => {
+ result += items.map((str) => {
+ if (isNotDefined(str)) {
+ return emptyValue;
+ }
+ const val = String(str);
+ if (val.includes(columnDelimiter)) {
+ return `"${val}"`;
+ }
+ return val;
+ }).join(columnDelimiter);
+ result += lineDelimiter;
+ });
+
+ return result;
+};
diff --git a/app/assets/scripts/views/RegionalThreeW/activity-details.js b/app/assets/scripts/views/RegionalThreeW/activity-details.js
new file mode 100644
index 000000000..ede500c96
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/activity-details.js
@@ -0,0 +1,146 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
+import {
+ listToGroupList as listToGroupMap,
+ mapToMap,
+} from '@togglecorp/fujs';
+import { MdChevronRight } from 'react-icons/md';
+
+import TextOutput from '../../components/text-output';
+import BlockLoading from '../../components/block-loading';
+
+import { getResultsFromResponse } from '../../utils/request';
+import { getProjects as getProjectsAction } from '../../actions';
+import { countryProjectSelector } from '../../selectors';
+import { sectors } from '../../utils/constants';
+
+const emptyObject = {};
+
+function NSDetails (props) {
+ const {
+ sectorList,
+ nsDetails,
+ } = props.data;
+
+ return (
+
+
+ { nsDetails.society_name }
+
+
+ { Object.keys(sectorList).map(sectorId => (
+
+ { sectors[sectorId] }[{ sectorList[sectorId] }]
+
+ ))}
+
+
+ );
+}
+
+function ActivityDetails (props) {
+ const {
+ data,
+ projectsResponse,
+ getProjects,
+ } = props;
+
+ const {
+ completed_projects_count: completed,
+ id: countryId,
+ name,
+ ongoing_projects_count: ongoing,
+ planned_projects_count: planned,
+ } = data || emptyObject;
+
+ React.useEffect(() => {
+ if (countryId) {
+ getProjects(countryId);
+ }
+ }, [countryId]);
+
+ const [projectList, pending] = React.useMemo(() => ([
+ getResultsFromResponse(projectsResponse),
+ projectsResponse.fetching,
+ ]), [projectsResponse]);
+
+ const nsSectorList = React.useMemo(() => {
+ const projectsByNS = listToGroupMap(
+ projectList,
+ d => d.reporting_ns,
+ d => ({
+ sector: d.primary_sector,
+ nsDetails: d.reporting_ns_detail,
+ }));
+
+ const sectorGroupedProjectsByNS = Object.keys(projectsByNS).map(nsId => ({
+ sectorList: mapToMap(
+ listToGroupMap(projectsByNS[nsId], d => d.sector, d => true),
+ undefined,
+ d => d.length,
+ ),
+ nsDetails: projectsByNS[nsId][0].nsDetails,
+ }));
+
+ return sectorGroupedProjectsByNS;
+ }, [projectList]);
+
+ return (
+
+
+
+ { name }
+
+
+
+
+
+
+
+
+
+
+
+
+ { pending ? (
+
+ ) : (
+ nsSectorList.map(d => (
+
+ ))
+ )}
+
+
+
+ );
+}
+
+const mapStateToProps = (state, props) => ({
+ projectsResponse: countryProjectSelector(state, props.data.id),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ getProjects: (...args) => dispatch(getProjectsAction(...args)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(React.memo(ActivityDetails));
diff --git a/app/assets/scripts/views/RegionalThreeW/budget-overview.js b/app/assets/scripts/views/RegionalThreeW/budget-overview.js
new file mode 100644
index 000000000..82fc4b66a
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/budget-overview.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import _cs from 'classnames';
+import FormattedNumber from '../../components/formatted-number';
+
+function TextOutput (p) {
+ const {
+ label,
+ value = 0,
+ } = p;
+
+ return (
+
+ );
+}
+
+function BudgetOverview (p) {
+ const {
+ totalBudget,
+ nsCountWithOngoingActivity,
+ className,
+ } = p;
+
+ return (
+
+
+
+
+ );
+}
+
+export default BudgetOverview;
diff --git a/app/assets/scripts/views/RegionalThreeW/country-table.js b/app/assets/scripts/views/RegionalThreeW/country-table.js
new file mode 100644
index 000000000..7f5be505d
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/country-table.js
@@ -0,0 +1,156 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { addSeparator } from '@togglecorp/fujs';
+import _cs from 'classnames';
+import {
+ MdChevronRight,
+ MdExpandLess,
+} from 'react-icons/md';
+
+import BlockLoading from '../../components/block-loading';
+import {
+ programmeTypes,
+ sectors,
+ statuses,
+} from '../../utils/constants';
+import { getResultsFromResponse } from '../../utils/request';
+import { getProjects as getProjectsAction } from '../../actions';
+import { countryProjectSelector } from '../../selectors';
+
+const tableHeaders = [
+ {
+ key: 'reporting_ns',
+ label: 'Supporting NS',
+ modifier: d => d.reporting_ns_detail.society_name,
+ },
+ {
+ key: 'primary_sector',
+ label: 'Activity sector',
+ modifier: d => sectors[d.primary_sector],
+ },
+ {
+ key: 'status',
+ label: 'Status',
+ modifier: d => statuses[d.status],
+ },
+ {
+ key: 'programme_type',
+ label: 'Type',
+ modifier: d => programmeTypes[d.programme_type],
+ },
+ {
+ key: 'target_total',
+ label: 'Total people targeted',
+ modifier: d => addSeparator(d.target_total),
+ },
+ {
+ key: 'reached_total',
+ label: 'Total people reached',
+ modifier: d => addSeparator(d.reached_total),
+ },
+ {
+ key: 'budget_amount',
+ label: 'Total budget',
+ modifier: d => addSeparator(d.budget_amount),
+ },
+];
+
+const emptyList = [];
+
+function CountryTable (p) {
+ const {
+ data,
+ isActive,
+ onHeaderClick,
+ projectsResponse,
+ getProjects,
+ filters,
+ } = p;
+
+ React.useEffect(() => {
+ if (isActive && data.id) {
+ getProjects(data.id, filters);
+ }
+ }, [data.id, isActive, filters]);
+
+ const [projectList, pending] = React.useMemo(() => ([
+ getResultsFromResponse(projectsResponse),
+ projectsResponse.fetching,
+ ]), [projectsResponse]);
+
+ const handleHeaderClick = React.useCallback(() => {
+ if (onHeaderClick) {
+ onHeaderClick(isActive ? undefined : data.id);
+ }
+ }, [onHeaderClick, data.id, isActive]);
+
+ const count = (data.projects_count || emptyList).length;
+
+ return (
+
+
+ { isActive && (
+
+ { pending ? (
+
+ ) : (
+
+
+
+ { tableHeaders.map(h => (
+
+ {h.label}
+ |
+ ))}
+
+
+
+ { projectList.map(p => (
+
+ { tableHeaders.map(h => (
+
+ { h.modifier ? h.modifier(p) : (p[h.key] || '-') }
+ |
+ ))}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ );
+}
+
+const mapStateToProps = (state, props) => ({
+ projectsResponse: countryProjectSelector(state, props.data.id),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ getProjects: (...args) => dispatch(getProjectsAction(...args)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(CountryTable);
diff --git a/app/assets/scripts/views/RegionalThreeW/export-button.js b/app/assets/scripts/views/RegionalThreeW/export-button.js
new file mode 100644
index 000000000..727ee3f12
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/export-button.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { saveAs } from 'file-saver';
+import _cs from 'classnames';
+
+import { getResultsFromResponse } from '../../utils/request';
+import { convertJsonToCsv } from '../../utils/utils';
+import { getRegionalProjects as getRegionalProjectsAction } from '../../actions';
+import { regionalProjectsSelector } from '../../selectors';
+
+import exportHeaders from '../ThreeW/export-headers';
+
+function ExportButton (p) {
+ const {
+ regionId,
+ filters,
+ getRegionalProjects,
+ projectsResponse,
+ } = p;
+
+ const [shouldStartRequest, setShouldStartRequest] = React.useState(false);
+
+ React.useEffect(() => {
+ if (shouldStartRequest) {
+ getRegionalProjects(regionId, filters);
+ setShouldStartRequest(false);
+ }
+ }, [shouldStartRequest, regionId, filters]);
+
+ const [
+ pending,
+ requestComplete,
+ projectList,
+ ] = React.useMemo(() => [
+ projectsResponse.fetching,
+ projectsResponse.fetched,
+ getResultsFromResponse(projectsResponse),
+ ], [projectsResponse]);
+
+ React.useEffect(() => {
+ if (!pending && requestComplete && projectList.length > 0) {
+ const resolveToValues = (headers, data) => {
+ const resolvedValues = [];
+ headers.forEach(header => {
+ const el = header.modifier ? header.modifier(data) || '' : data[header.key] || '';
+ resolvedValues.push(el);
+ });
+ return resolvedValues;
+ };
+
+ const csvHeaders = exportHeaders.map(d => d.title);
+ const resolvedValueList = projectList.map(project => (
+ resolveToValues(exportHeaders, project)
+ ));
+
+ const csv = convertJsonToCsv([
+ csvHeaders,
+ ...resolvedValueList,
+ ]);
+
+ const blob = new Blob([csv], { type: 'text/csv' });
+ const timestamp = (new Date()).getTime();
+ const fileName = `projects-export-${timestamp}.csv`;
+
+ saveAs(blob, fileName);
+ }
+ }, [pending, requestComplete, projectList]);
+
+ const handleClick = React.useCallback(() => {
+ setShouldStartRequest(true);
+ }, [setShouldStartRequest]);
+
+ return (
+
+ );
+}
+
+const mapStateToProps = (state, props) => ({
+ projectsResponse: regionalProjectsSelector(state),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ getRegionalProjects: (...args) => dispatch(getRegionalProjectsAction(...args)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(ExportButton);
diff --git a/app/assets/scripts/views/RegionalThreeW/index.js b/app/assets/scripts/views/RegionalThreeW/index.js
new file mode 100644
index 000000000..1aead7698
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/index.js
@@ -0,0 +1,305 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import {
+ ResponsiveContainer,
+ Sankey,
+ Tooltip,
+ Layer,
+ Label,
+ Rectangle,
+} from 'recharts';
+import _cs from 'classnames';
+
+import BlockLoading from '../../components/block-loading';
+import {
+ getRegionalMovementActivities as getRegionalMovementActivitiesAction,
+ getRegionalProjectsOverview as getRegionalProjectsOverviewAction,
+ getNationalSocietyActivities as getNationalSocietyActivitiesAction,
+ getNationalSocietyActivitiesWoFilters as getNationalSocietyActivitiesWoFiltersAction,
+} from '../../actions';
+
+import {
+ regionalMovementActivitiesSelector,
+ regionalProjectsOverviewSelector,
+ nationalSocietyActivitiesSelector,
+ nationalSocietyActivitiesWoFiltersSelector,
+} from '../../selectors';
+
+import { statuses } from '../../utils/constants';
+import { getDataFromResponse } from '../../utils/request';
+
+import StatusOverview from './status-overview';
+import BudgetOverview from './budget-overview';
+import CountryTable from './country-table';
+import PeopleOverview from './people-overview';
+import MovementActivitiesFilters from './movement-activities-filters';
+import NSActivitiesFilters from './ns-activities-filters';
+import ExportButton from './export-button';
+import Map from './map';
+
+const emptyList = [];
+
+function SankeyNode (p) {
+ const { x, y, width, height, index, payload } = p;
+ if (payload.value === 0) {
+ return null;
+ }
+
+ const isOut = (x + width) > window.innerWidth / 2;
+
+ return (
+
+
+
+ {payload.name} ({payload.value})
+
+
+ );
+}
+
+function SankeyLink (p) {
+ const {
+ sourceX,
+ targetX,
+ sourceY,
+ targetY,
+ sourceControlX,
+ targetControlX,
+ linkWidth,
+ index,
+ } = p;
+
+ const isLeft = targetX < window.innerWidth / 2;
+
+ return (
+
+
+
+ );
+}
+
+function RegionalThreeW (p) {
+ const {
+ regionId,
+ regionalMovementActivitiesResponse,
+ regionalProjectsOverviewResponse,
+ nationalSocietyActivitiesResponse,
+ nationalSocietyActivitiesWoFiltersResponse,
+ getRegionalMovementActivities,
+ getRegionalProjectsOverview,
+ getNationalSocietyActivities,
+ getNationalSocietyActivitiesWoFilters,
+ } = p;
+
+ const [activeCountryId, setActiveCountryId] = React.useState(undefined);
+ const [movementActivityFilters, setMovementActivityFilters] = React.useState({
+ operation_type: undefined,
+ programme_type: undefined,
+ primary_sector: undefined,
+ status: undefined,
+ });
+ const [nsActivityFilters, setNSActivityFilters] = React.useState({
+ reporting_ns: [],
+ primary_sector: [],
+ project_country: [],
+ });
+
+ React.useEffect(() => {
+ getRegionalMovementActivities(regionId, movementActivityFilters);
+ }, [
+ regionId,
+ getRegionalMovementActivities,
+ movementActivityFilters,
+ ]);
+
+ React.useEffect(() => {
+ getRegionalProjectsOverview(regionId);
+ }, [regionId, getRegionalProjectsOverview]);
+
+ React.useEffect(() => {
+ getNationalSocietyActivitiesWoFilters(regionId);
+ }, [regionId, getNationalSocietyActivitiesWoFilters]);
+
+ React.useEffect(() => {
+ getNationalSocietyActivities(regionId, nsActivityFilters);
+ }, [regionId, nsActivityFilters, getNationalSocietyActivities]);
+
+ const [
+ movementActivityList,
+ movementActivityListPending,
+ ] = React.useMemo(() => ([
+ (getDataFromResponse(regionalMovementActivitiesResponse).countries_count || emptyList)
+ .filter(d => d.projects_count > 0),
+ regionalMovementActivitiesResponse.fetching,
+ ]), [regionalMovementActivitiesResponse]);
+
+ const [
+ projectsOverview,
+ projectsOverviewPending,
+ ] = React.useMemo(() => [
+ getDataFromResponse(regionalProjectsOverviewResponse),
+ regionalProjectsOverviewResponse.fetching,
+ ], [regionalProjectsOverviewResponse]);
+
+ const [
+ nationalSocietyActivities,
+ nationalSocietyActivitiesPending,
+ ] = React.useMemo(() => ([
+ getDataFromResponse(nationalSocietyActivitiesResponse),
+ nationalSocietyActivitiesResponse.fetching,
+ ]), [nationalSocietyActivitiesResponse]);
+
+ const [
+ nationalSocietyActivitiesWoFilters,
+ nationalSocietyActivitiesWoFiltersPending,
+ ] = React.useMemo(() => ([
+ getDataFromResponse(nationalSocietyActivitiesWoFiltersResponse),
+ nationalSocietyActivitiesWoFiltersResponse.fetching,
+ ]), [nationalSocietyActivitiesWoFiltersResponse]);
+
+ const sectorActivityData = React.useMemo(() => (
+ (projectsOverview.projects_by_status || emptyList).map(
+ d => ({ label: statuses[d.status], value: d.count }),
+ ).sort((a, b) => a.label.localeCompare(b.label))
+ ), [projectsOverview]);
+
+ return (
+
+ { projectsOverviewPending ? (
+
+ ) : (
+
+ )}
+
+ { movementActivityListPending &&
}
+
+
+
+ Movement activities
+
+
+
+
+
+
+
+
+ { movementActivityList.map(c => (
+
+ ))}
+
+
+
+
+
+
+ National society activities
+
+
+
+
+ { nationalSocietyActivitiesPending || nationalSocietyActivitiesWoFiltersPending ? (
+
+ ) : (
+ (nationalSocietyActivities.links || emptyList).length === 0 ? (
+
+ Not enough data to show the chart
+
+ ) : (
+
+
+
+
+
+
+ )
+ )}
+
+
+
+ );
+}
+
+const mapStateToProps = (state) => ({
+ regionalMovementActivitiesResponse: regionalMovementActivitiesSelector(state),
+ regionalProjectsOverviewResponse: regionalProjectsOverviewSelector(state),
+ nationalSocietyActivitiesResponse: nationalSocietyActivitiesSelector(state),
+ nationalSocietyActivitiesWoFiltersResponse: nationalSocietyActivitiesWoFiltersSelector(state),
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ getRegionalMovementActivities: (...args) => dispatch(getRegionalMovementActivitiesAction(...args)),
+ getRegionalProjectsOverview: (...args) => dispatch(getRegionalProjectsOverviewAction(...args)),
+ getNationalSocietyActivities: (...args) => dispatch(getNationalSocietyActivitiesAction(...args)),
+ getNationalSocietyActivitiesWoFilters: (...args) => dispatch(getNationalSocietyActivitiesWoFiltersAction(...args)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(React.memo(RegionalThreeW));
diff --git a/app/assets/scripts/views/RegionalThreeW/map.js b/app/assets/scripts/views/RegionalThreeW/map.js
new file mode 100644
index 000000000..f027396ca
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/map.js
@@ -0,0 +1,197 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import { render } from 'react-dom';
+import { BrowserRouter, Link } from 'react-router-dom';
+import mapboxgl from 'mapbox-gl';
+
+import store from '../../utils/store';
+import newMap from '../../utils/get-new-map';
+import { getRegionBoundingBox } from '../../utils/region-bounding-box';
+import { getCentroidByCountryId } from '../../utils/country-centroids';
+
+import ActivityDetails from './activity-details';
+
+const emptyList = [];
+
+function getGeojsonFromMovementActivities (movementActivities = emptyList) {
+ const geojson = {
+ type: 'geojson',
+ data: {
+ type: 'FeatureCollection',
+ features: movementActivities.map(d => ({
+ type: 'Feature',
+ geometry: {
+ type: 'Point',
+ coordinates: getCentroidByCountryId(d.id),
+ },
+ properties: {
+ ...d,
+ }
+ })),
+ }
+ };
+
+ return geojson;
+}
+
+function ProgressBar (p) {
+ const {
+ value,
+ max,
+ } = p;
+ const width = 100 * value / max;
+
+ return (
+
+ );
+}
+
+const MAX_SCALE_STOPS = 6;
+
+function Scale (p) {
+ const { max } = p;
+ const numbers = [];
+
+ const diff = max / MAX_SCALE_STOPS;
+
+ for (let i = 0; i <= max; i += diff) {
+ numbers.push(i);
+ }
+
+ return (
+
+ { numbers.map(n =>
{n}
) }
+
+ );
+}
+
+function Map (props) {
+ const {
+ regionId,
+ data,
+ } = props;
+
+ const ref = React.useRef();
+ const [map, setMap] = React.useState();
+ const [mapLoaded, setMapLoaded] = React.useState(false);
+
+ React.useEffect(() => {
+ const { current: mapContainer } = ref;
+ setMap(newMap(mapContainer));
+ }, [setMap]);
+
+ React.useEffect(() => {
+ if (!map) {
+ return;
+ }
+
+ map.on('load', () => {
+ const bbox = getRegionBoundingBox(regionId);
+ map.fitBounds(bbox);
+ setMapLoaded(true);
+ });
+ }, [map, setMapLoaded]);
+
+ React.useEffect(() => {
+ if (!map || !mapLoaded) {
+ return;
+ }
+
+ const geojson = getGeojsonFromMovementActivities(data);
+
+ try {
+ if (map.getLayer('movement-activity-circles')) {
+ map.removeLayer('movement-activity-circles');
+ }
+ map.removeSource('movement-activity-markers');
+ } catch (err) {
+ // pass
+ }
+
+ map.addSource('movement-activity-markers', geojson);
+ map.addLayer({
+ id: 'movement-activity-circles',
+ source: 'movement-activity-markers',
+ type: 'circle',
+ paint: {
+ 'circle-radius': 7,
+ 'circle-color': '#f5333f',
+ 'circle-opacity': 0.7,
+ },
+ });
+
+ map.on('click', 'movement-activity-circles', (e) => {
+ const properties = e.features[0].properties;
+ const popoverContent = document.createElement('div');
+
+ render(
+ (
+
+
+
+
+
+ ),
+ popoverContent,
+ );
+
+ new mapboxgl.Popup({ closeButton: false })
+ .setLngLat(e.lngLat)
+ .setDOMContent(popoverContent.children[0])
+ .addTo(map);
+ });
+ }, [map, regionId, data, mapLoaded]);
+
+ const [supportingNSList, maxProjects] = React.useMemo(() => {
+ const maxProjects = Math.max(...data.map(d => d.projects_count));
+ const numBuckets = Math.ceil(maxProjects / MAX_SCALE_STOPS);
+ const max = numBuckets * MAX_SCALE_STOPS;
+
+ return [
+ data.map(d => ({
+ id: d.id,
+ name: d.name,
+ value: d.projects_count,
+ })),
+ max,
+ ];
+ }, [data]);
+
+ return (
+
+
+
+
+
+ { supportingNSList.map(d => (
+
+
+
+
+ { d.name }
+
+
+
+ {d.value} projects
+
+
+
+
+ ))}
+
+
+
+ );
+}
+
+export default React.memo(Map);
diff --git a/app/assets/scripts/views/RegionalThreeW/movement-activities-filters.js b/app/assets/scripts/views/RegionalThreeW/movement-activities-filters.js
new file mode 100644
index 000000000..d44f98226
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/movement-activities-filters.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import _cs from 'classnames';
+import Faram from '@togglecorp/faram';
+
+import SelectInput from '../../components/form-elements/select-input';
+import {
+ statusList,
+ sectorList,
+ programmeTypeList,
+ operationTypeList,
+} from '../../utils/constants';
+
+const compareString = (a, b) => a.label.localeCompare(b.label);
+
+const programmeTypeOptions = programmeTypeList.map(p => ({
+ value: p.key,
+ label: p.title,
+})).sort(compareString);
+
+const sectorsOfActivityOptions = sectorList.map(p => ({
+ value: p.inputValue,
+ label: p.title,
+})).sort(compareString);
+
+const statusOptions = statusList.map(p => ({
+ value: p.key,
+ label: p.title,
+})).sort(compareString);
+
+const operationTypeOptions = operationTypeList.map(o => ({
+ value: o.value,
+ label: o.label,
+})).sort(compareString);
+
+const filterSchema = {
+ fields: {
+ operation_type: [],
+ programme_type: [],
+ primary_sector: [],
+ status: []
+ }
+};
+
+function MovementActivitiesFilters (p) {
+ const {
+ className,
+ value,
+ onChange,
+ } = p;
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default MovementActivitiesFilters;
diff --git a/app/assets/scripts/views/RegionalThreeW/ns-activities-filters.js b/app/assets/scripts/views/RegionalThreeW/ns-activities-filters.js
new file mode 100644
index 000000000..345146a3e
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/ns-activities-filters.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import Faram from '@togglecorp/faram';
+
+import SelectInput from '../../components/form-elements/select-input';
+
+const filterSchema = {
+ fields: {
+ reporting_ns: [],
+ project_country: [],
+ primary_sector: [],
+ }
+};
+
+const emptyData = {
+ nodes: [],
+};
+
+function toLabelValue (d) {
+ return {
+ label: d.name,
+ value: d.id,
+ };
+}
+
+const compareString = (a, b) => a.label.localeCompare(b.label);
+
+function NSActivitiesFilters (p) {
+ const {
+ value,
+ onChange,
+ data = emptyData,
+ } = p;
+
+ const [
+ supportingNSOptions,
+ activityOptions,
+ receivingNSOptions,
+ ] = React.useMemo(() => [
+ (data.nodes || []).filter(d => d.type === 'supporting_ns').map(toLabelValue).sort(compareString),
+ (data.nodes || []).filter(d => d.type === 'sector').map(toLabelValue).sort(compareString),
+ (data.nodes || []).filter(d => d.type === 'receiving_ns').map(toLabelValue).sort(compareString),
+ ], [data]);
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default NSActivitiesFilters;
diff --git a/app/assets/scripts/views/RegionalThreeW/people-overview.js b/app/assets/scripts/views/RegionalThreeW/people-overview.js
new file mode 100644
index 000000000..2360751c3
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/people-overview.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import _cs from 'classnames';
+
+import FormattedNumber from '../../components/formatted-number';
+
+function PeopleOverview (props) {
+ const {
+ targeted = 0,
+ reached = 0,
+ className,
+ } = props;
+
+ const barStyle = React.useMemo(() => {
+ let progress = 0;
+
+ if (targeted && targeted !== 0) {
+ progress = Math.min(100, 100 * reached / targeted);
+ }
+
+ return { width: `${progress}%` };
+ }, [targeted, reached]);
+
+ return (
+
+
+ Total number of people reached
+
+
+
+ );
+}
+
+export default PeopleOverview;
diff --git a/app/assets/scripts/views/RegionalThreeW/status-overview.js b/app/assets/scripts/views/RegionalThreeW/status-overview.js
new file mode 100644
index 000000000..92f700a82
--- /dev/null
+++ b/app/assets/scripts/views/RegionalThreeW/status-overview.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import _cs from 'classnames';
+
+import {
+ ResponsiveContainer,
+ PieChart,
+ Pie,
+ Legend,
+ Tooltip,
+ Cell,
+} from 'recharts';
+
+import FormattedNumber from '../../components/formatted-number';
+
+const colors = {
+ 'Completed': '#f5333f',
+ 'Ongoing': '#f7969c',
+ 'Planned': '#f9e5e6',
+};
+
+function StatusOverview (p) {
+ const {
+ data,
+ total,
+ className,
+ } = p;
+
+ return (
+
+
+ Total activities by status
+
+
+
+
+
+ Total activities
+
+
+
+
+
+
+ { data.map((entry, index) => {
+ return (
+ |
+ );
+ })}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default StatusOverview;
diff --git a/app/assets/scripts/views/ThreeW/export-headers.js b/app/assets/scripts/views/ThreeW/export-headers.js
index 51ac830ef..0715ffba3 100644
--- a/app/assets/scripts/views/ThreeW/export-headers.js
+++ b/app/assets/scripts/views/ThreeW/export-headers.js
@@ -28,12 +28,12 @@ const exportHeaders = [
modifier: r => operationTypes[r.operation_type],
},
{
- title: 'Programme Type',
+ title: 'Programme type',
key: 'programme_type',
modifier: r => programmeTypes[r.programme_type],
},
{
- title: 'Project Name',
+ title: 'Activity name',
key: 'name',
},
{
diff --git a/app/assets/scripts/views/ThreeW/filter.js b/app/assets/scripts/views/ThreeW/filter.js
index ca6d6be61..1ac714597 100644
--- a/app/assets/scripts/views/ThreeW/filter.js
+++ b/app/assets/scripts/views/ThreeW/filter.js
@@ -12,20 +12,22 @@ import {
programmeTypeList,
} from '../../utils/constants';
+const compareString = (a, b) => a.label.localeCompare(b.label);
+
const programmeTypeOptions = programmeTypeList.map(p => ({
value: p.title,
label: p.title,
-}));
+})).sort(compareString);
const sectorsOfActivityOptions = sectorList.map(p => ({
value: p.inputValue,
label: p.title,
-}));
+})).sort(compareString);
const statusOptions = statusList.map(p => ({
value: p.title,
label: p.title,
-}));
+})).sort(compareString);
export default class ThreeWFilter extends React.PureComponent {
constructor (props) {
diff --git a/app/assets/scripts/views/ThreeW/index.js b/app/assets/scripts/views/ThreeW/index.js
index 0b2b8dd15..ba9e725e6 100644
--- a/app/assets/scripts/views/ThreeW/index.js
+++ b/app/assets/scripts/views/ThreeW/index.js
@@ -3,9 +3,9 @@ import React from 'react';
import _cs from 'classnames';
import memoize from 'memoize-one';
import { saveAs } from 'file-saver';
-import { isNotDefined } from '@togglecorp/fujs';
import { getDataFromResponse } from '../../utils/request';
+import { convertJsonToCsv } from '../../utils/utils';
import Summary from './stats/summary';
import SectorActivity from './stats/sector-activity';
@@ -16,29 +16,6 @@ import Table from './table';
import Map from './map';
import exportHeaders from './export-headers';
-const convertJsonToCsv = (data, columnDelimiter = ',', lineDelimiter = '\n', emptyValue = '') => {
- if (!data || data.length <= 0) {
- return undefined;
- }
-
- let result = '';
-
- data.forEach((items) => {
- result += items.map((str) => {
- if (isNotDefined(str)) {
- return emptyValue;
- }
- const val = String(str);
- if (val.includes(columnDelimiter)) {
- return `"${val}"`;
- }
- return val;
- }).join(columnDelimiter);
- result += lineDelimiter;
- });
-
- return result;
-};
export default class ThreeW extends React.PureComponent {
getIsCountryAdmin = memoize((user, countryId) => {
diff --git a/app/assets/scripts/views/ThreeW/map.js b/app/assets/scripts/views/ThreeW/map.js
index 7d409ee9f..7e63f66d8 100644
--- a/app/assets/scripts/views/ThreeW/map.js
+++ b/app/assets/scripts/views/ThreeW/map.js
@@ -179,7 +179,7 @@ class ThreeWMap extends React.PureComponent {
this.resetBounds(countryId);
const groupedProjects = listToGroupList(
- projectList,
+ projectList.filter(d => d.project_district),
project => project.project_district,
project => project,
);
diff --git a/app/assets/scripts/views/ThreeW/project-details.js b/app/assets/scripts/views/ThreeW/project-details.js
index f5c4a8439..9e65ab632 100644
--- a/app/assets/scripts/views/ThreeW/project-details.js
+++ b/app/assets/scripts/views/ThreeW/project-details.js
@@ -93,7 +93,7 @@ class ProjectDetails extends React.PureComponent {
@@ -109,7 +109,7 @@ class ProjectDetails extends React.PureComponent {
@@ -137,10 +137,12 @@ class ProjectDetails extends React.PureComponent {
@@ -152,10 +154,13 @@ class ProjectDetails extends React.PureComponent {
label='Primary sector'
value={sectors[primary_sector]}
/>
- secondarySectors[d])).join(', ')}
- />
+ {secondary_sectors.length > 0 && (
+ secondarySectors[d])).join(', ')}
+ hideEmptyValue
+ />
+ )}
@@ -165,18 +170,26 @@ class ProjectDetails extends React.PureComponent {
@@ -188,18 +201,26 @@ class ProjectDetails extends React.PureComponent {
diff --git a/app/assets/scripts/views/ThreeW/project-form-modal.js b/app/assets/scripts/views/ThreeW/project-form-modal.js
index 4e58702d0..f9e0a86c8 100644
--- a/app/assets/scripts/views/ThreeW/project-form-modal.js
+++ b/app/assets/scripts/views/ThreeW/project-form-modal.js
@@ -4,13 +4,13 @@ import _cs from 'classnames';
import ProjectForm from './project-form';
-function ProjectFormModal (props) {
+function ProjectFormModal (p) {
const {
projectData,
countryId,
onCloseButtonClick,
pending,
- } = props;
+ } = p;
return (
diff --git a/app/assets/scripts/views/ThreeW/project-form.js b/app/assets/scripts/views/ThreeW/project-form.js
index 8b781cb95..94167bbe8 100644
--- a/app/assets/scripts/views/ThreeW/project-form.js
+++ b/app/assets/scripts/views/ThreeW/project-form.js
@@ -16,6 +16,7 @@ import TextInput from '../../components/form-elements/text-input';
import NumberInput from '../../components/form-elements/number-input';
import DateInput from '../../components/form-elements/date-input';
import Checkbox from '../../components/form-elements/faram-checkbox';
+import TextOutput from '../../components/text-output';
import {
getCountries,
@@ -29,16 +30,13 @@ import {
} from '../../utils/field-report-constants';
import {
- statusList,
+ // statusList,
statuses,
sectorList,
secondarySectorInputValues,
secondarySectorList,
- sectorInputValues,
programmeTypeList,
- programmeTypes,
operationTypeList,
- operationTypes,
projectVisibilityList,
} from '../../utils/constants';
@@ -52,10 +50,12 @@ const positiveIntegerCondition = (value) => {
const compareString = (a, b) => a.label.localeCompare(b.label);
+/*
const statusOptions = statusList.map(p => ({
- value: p.title,
+ value: p.key,
label: p.title,
})).sort(compareString);
+*/
const sectorOptions = sectorList.map(p => ({
value: p.inputValue,
@@ -68,7 +68,7 @@ const secondarySectorOptions = secondarySectorList.map(p => ({
})).sort(compareString);
const programmeTypeOptions = programmeTypeList.map(p => ({
- value: p.title,
+ value: p.key,
label: p.title,
})).sort(compareString);
@@ -170,9 +170,9 @@ class ProjectForm extends React.PureComponent {
dtype: projectData.dtype,
project_district: projectData.project_district ? projectData.project_district : 'all',
name: projectData.name,
- operation_type: operationTypes[projectData.operation_type],
- primary_sector: sectorInputValues[projectData.primary_sector],
- programme_type: programmeTypes[projectData.programme_type],
+ operation_type: projectData.operation_type,
+ primary_sector: projectData.primary_sector,
+ programme_type: projectData.programme_type,
end_date: projectData.end_date,
start_date: projectData.start_date,
reached_other: projectData.reached_other || undefined,
@@ -182,7 +182,7 @@ class ProjectForm extends React.PureComponent {
reporting_ns: projectData.reporting_ns,
secondary_sectors: projectData.secondary_sectors ? projectData.secondary_sectors.map(d => secondarySectorInputValues[d]) : [],
is_project_completed: projectData.status === 2,
- status: statuses[projectData.status],
+ status: projectData.status,
target_other: projectData.target_other || undefined,
target_female: projectData.target_female || undefined,
target_male: projectData.target_male || undefined,
@@ -280,7 +280,7 @@ class ProjectForm extends React.PureComponent {
}));
const operationToDisasterMap = {};
- currentOperationList.forEach(d => { operationToDisasterMap[d.id] = d.dtype.id; });
+ currentOperationList.forEach(d => { operationToDisasterMap[d.id] = (d.dtype || {}).id; });
const currentEmergencyOperationOptions = currentOperationList
.filter(d => d.auto_generated_source === 'New field report')
@@ -298,21 +298,21 @@ class ProjectForm extends React.PureComponent {
getProjectStatusFaramValue = memoize((start, isCompleted) => {
if (isCompleted) {
- return { status: 'Completed' };
+ return { status: '2' };
}
if (!start) {
- return { status: 'Planned' };
+ return { status: '0' };
}
const startDate = new Date(start);
const today = new Date();
if (startDate.getTime() <= today.getTime()) {
- return { status: 'Ongoing' };
+ return { status: '1' };
}
- return { status: 'Planned' };
+ return { status: '0' };
})
getTargetedTotalFaramValue = memoize((male, female, other) => {
@@ -452,6 +452,7 @@ class ProjectForm extends React.PureComponent {
const fetchingCountries = countries && countries.fetching;
const shouldDisableCountryInput = fetchingCountries;
+
const fetchingDistricts = districts && districts[faramValues.project_country] && districts[faramValues.project_country].fetching;
const shouldDisableDistrictInput = fetchingCountries || fetchingDistricts;
const fetchingEvents = eventList && eventList.fetching;
@@ -584,9 +585,23 @@ class ProjectForm extends React.PureComponent {
/>
)}
+ { shouldShowCurrentEmergencyOperation && (
+
+
+
+ )}
-
+
@@ -698,7 +710,7 @@ class ProjectForm extends React.PureComponent {
/>
{
+const ProgressBar = (p) => {
+ const {
+ value,
+ max,
+ } = p;
const width = 100 * value / max;
return (
@@ -21,9 +22,15 @@ const ProgressBar = ({
);
};
-const Scale = ({ max }) => {
+const MAX_SCALE_STOPS = 5;
+
+function Scale (p) {
+ const { max } = p;
const numbers = [];
- for (let i = 0; i <= max; i++) {
+
+ const diff = max / MAX_SCALE_STOPS;
+
+ for (let i = 0; i <= max; i += diff) {
numbers.push(i);
}
@@ -32,7 +39,7 @@ const Scale = ({ max }) => {
{ numbers.map(n => {n}
) }
);
-};
+}
export default class RegionOverview extends React.PureComponent {
render () {
@@ -49,6 +56,8 @@ export default class RegionOverview extends React.PureComponent {
const projectDistrictList = Object.keys(groupedProjectList);
const maxProjects = Math.max(...projectDistrictList.map(d => groupedProjectList[d].length));
+ const numBuckets = Math.ceil(maxProjects / MAX_SCALE_STOPS);
+ const max = numBuckets * MAX_SCALE_STOPS;
return (
@@ -56,7 +65,7 @@ export default class RegionOverview extends React.PureComponent {
Regions
{ projectDistrictList.map(d => {
@@ -69,11 +78,11 @@ export default class RegionOverview extends React.PureComponent {
className='three-w-region-district'
>
- {regionName} ({p.length} projects)
+ {regionName} ({p.length} activities)
);
diff --git a/app/assets/scripts/views/ThreeW/stats/sector-activity.js b/app/assets/scripts/views/ThreeW/stats/sector-activity.js
index 49815008f..dacee9cfd 100644
--- a/app/assets/scripts/views/ThreeW/stats/sector-activity.js
+++ b/app/assets/scripts/views/ThreeW/stats/sector-activity.js
@@ -11,6 +11,7 @@ import {
Bar,
XAxis,
YAxis,
+ Tooltip,
} from 'recharts';
import { sectorList } from '../../../utils/constants';
@@ -51,7 +52,7 @@ export default class SectorActivity extends React.PureComponent {
return (
- Projects by sector of activity
+ Activities by sector
@@ -72,6 +73,7 @@ export default class SectorActivity extends React.PureComponent {
fill='#c1cdd1'
dataKey='value'
/>
+
diff --git a/app/assets/scripts/views/ThreeW/stats/status-overview.js b/app/assets/scripts/views/ThreeW/stats/status-overview.js
index 8bed1a71e..98f85b52d 100644
--- a/app/assets/scripts/views/ThreeW/stats/status-overview.js
+++ b/app/assets/scripts/views/ThreeW/stats/status-overview.js
@@ -54,7 +54,7 @@ export default class StatusOverview extends React.PureComponent {
return (
- Project status overview
+ Activity status overview
diff --git a/app/assets/scripts/views/ThreeW/table.js b/app/assets/scripts/views/ThreeW/table.js
index 898e68f97..f2d91ba57 100644
--- a/app/assets/scripts/views/ThreeW/table.js
+++ b/app/assets/scripts/views/ThreeW/table.js
@@ -6,6 +6,14 @@ import {
} from '@togglecorp/fujs';
import memoize from 'memoize-one';
import url from 'url';
+import {
+ MdContentCopy,
+ MdSearch,
+ MdEdit,
+ MdDeleteForever,
+ MdHistory,
+ MdMoreHoriz,
+} from 'react-icons/md';
import {
programmeTypes,
@@ -34,7 +42,7 @@ export default class ProjectListTable extends React.PureComponent {
},
{
key: 'name',
- label: 'Project name',
+ label: 'Activity name',
},
{
key: 'reporting_ns',
@@ -83,13 +91,13 @@ export default class ProjectListTable extends React.PureComponent {
}
+ label={}
>