Skip to content

Commit

Permalink
Finish organisation stats page
Browse files Browse the repository at this point in the history
  • Loading branch information
willemarcel committed Feb 2, 2021
1 parent c24ca60 commit e16552a
Show file tree
Hide file tree
Showing 26 changed files with 777 additions and 83 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@webscopeio/react-textarea-autocomplete": "^4.7.3",
"axios": "^0.21.1",
"chart.js": "^2.9.4",
"date-fns": "^2.16.1",
"dompurify": "^2.2.6",
"downshift-hooks": "^0.8.1",
"final-form": "^4.20.1",
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ import { Login } from './views/login';
import { Welcome } from './views/welcome';
import { Settings } from './views/settings';
import { ManagementPageIndex, ManagementSection } from './views/management';
import {
ListOrganisations,
CreateOrganisation,
EditOrganisation,
OrganisationStats,
} from './views/organisations';
import { ListOrganisations, CreateOrganisation, EditOrganisation } from './views/organisations';
import { OrganisationStats } from './views/organisationStats';
import { MyTeams, ManageTeams, CreateTeam, EditTeam, TeamDetail } from './views/teams';
import { ListCampaigns, CreateCampaign, EditCampaign } from './views/campaigns';
import { ListInterests, CreateInterest, EditInterest } from './views/interests';
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/projectDetail/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export default function ProjectTimeline({ tasksByDay }: Object) {
data={formatTimelineData(tasksByDay, CHART_COLOURS.orange, CHART_COLOURS.red)}
options={{
legend: { position: 'top', align: 'end', labels: { boxWidth: 12 } },
tooltips: { callbacks: { label: (tooltip, data) => formatTimelineTooltip(tooltip, data) } },
tooltips: {
callbacks: { label: (tooltip, data) => formatTimelineTooltip(tooltip, data, true) },
},
scales: { xAxes: [{ type: 'time', time: { unit: unit } }] },
}}
/>
Expand Down
73 changes: 61 additions & 12 deletions frontend/src/components/projects/filterSelectFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ import React from 'react';
import ReactPlaceholder from 'react-placeholder';
import 'react-placeholder/lib/reactPlaceholder.css';
import Select from 'react-select';
import { format, parse } from 'date-fns';
import DatePicker from 'react-datepicker';
import { FormattedMessage, useIntl } from 'react-intl';

import { FormattedMessage } from 'react-intl';
import messages from './messages';
import { CalendarIcon } from '../svgIcons';

export const ProjectFilterSelect = (props) => {
const state = props.options;
const fieldsetTitle = <FormattedMessage {...messages[props.fieldsetName]} />;
const fieldsetTitlePlural = <FormattedMessage {...messages[`${props.fieldsetName}s`]} />;
export const ProjectFilterSelect = ({
fieldsetName,
fieldsetStyle,
titleStyle,
selectedTag,
setQueryForChild,
allQueryParamsForChild,
options,
}) => {
const state = options;
const fieldsetTitle = <FormattedMessage {...messages[fieldsetName]} />;
const fieldsetTitlePlural = <FormattedMessage {...messages[`${fieldsetName}s`]} />;

return (
<fieldset id={props.fieldsetName} className={props.fieldsetStyle}>
<legend className={props.titleStyle}>{fieldsetTitle}</legend>
<fieldset id={fieldsetName} className={fieldsetStyle}>
<legend className={titleStyle}>{fieldsetTitle}</legend>
{state.isError ? (
<div className="bg-tan pa4">
<FormattedMessage
Expand All @@ -29,17 +40,55 @@ export const ProjectFilterSelect = (props) => {
<TagFilterPickerAutocomplete
fieldsetTitle={fieldsetTitle}
defaultSelectedItem={fieldsetTitlePlural}
fieldsetName={props.fieldsetName}
queryParamSelectedItem={props.selectedTag || fieldsetTitle}
tagOptionsFromAPI={props.options}
setQuery={props.setQueryForChild}
allQueryParams={props.allQueryParamsForChild}
fieldsetName={fieldsetName}
queryParamSelectedItem={selectedTag || fieldsetTitle}
tagOptionsFromAPI={options}
setQuery={setQueryForChild}
allQueryParams={allQueryParamsForChild}
/>
</ReactPlaceholder>
</fieldset>
);
};

export const DateFilterPicker = ({
fieldsetName,
fieldsetStyle,
titleStyle,
selectedValue,
setQueryForChild,
allQueryParamsForChild,
}) => {
const intl = useIntl();
const dateFormat = 'yyyy-MM-dd';
return (
<fieldset id={fieldsetName} className={fieldsetStyle}>
<legend className={titleStyle}>
<FormattedMessage {...messages[fieldsetName]} />
</legend>
<CalendarIcon className="blue-grey dib w1 pr2 v-mid" />
<DatePicker
selected={selectedValue ? parse(selectedValue, dateFormat, new Date()) : null}
onChange={(date) =>
setQueryForChild(
{
...allQueryParamsForChild,
page: undefined,
[fieldsetName]: date ? format(date, dateFormat) : null,
},
'pushIn',
)
}
dateFormat={dateFormat}
className="w-auto pv2 ph1"
placeholderText={intl.formatMessage(messages[`${fieldsetName}Placeholder`])}
showYearDropdown
scrollableYearDropdown
/>
</fieldset>
);
};

/*
defaultSelectedItem gets appended to top of list as an option for reset
*/
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/projects/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ export default defineMessages({
id: 'project.nav.campaign',
defaultMessage: 'Campaign',
},
startDate: {
id: 'navFilters.startDate',
defaultMessage: 'From',
},
startDatePlaceholder: {
id: 'navFilters.startDate.placeholder',
defaultMessage: 'Click to select a start date',
},
endDate: {
id: 'navFilters.endDate',
defaultMessage: 'To',
},
endDatePlaceholder: {
id: 'navFilters.endDatePlace.placeholder',
defaultMessage: 'Click to select an end date',
},
showMapToggle: {
id: 'project.nav.showMapToggle',
defaultMessage: 'Show map',
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/svgIcons/calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

// Icon produced by FontAwesome project: https://github.com/FortAwesome/Font-Awesome/
// License: CC-By 4.0
export class CalendarIcon extends React.PureComponent {
render() {
return (
<svg viewBox="0 0 448 512" {...this.props}>
<path
fill="currentColor"
d="M148 288h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm108-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 96v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96-260v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"
></path>
</svg>
);
}
}
1 change: 1 addition & 0 deletions frontend/src/components/svgIcons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ export { CircleIcon } from './circle';
export { FourCellsGridIcon, NineCellsGridIcon } from './grid';
export { CutIcon } from './cut';
export { FileImportIcon } from './fileImport';
export { CalendarIcon } from './calendar';
12 changes: 12 additions & 0 deletions frontend/src/components/teamsAndOrgs/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,26 @@ export default defineMessages({
id: 'management.organisations.stats.to_be_mapped',
defaultMessage: 'Tasks to be mapped',
},
tasksMapped: {
id: 'management.organisations.stats.tasks_mapped',
defaultMessage: 'Tasks mapped',
},
readyForValidation: {
id: 'management.organisations.stats.ready_for_validation',
defaultMessage: 'Ready for validation',
},
tasksValidated: {
id: 'management.organisations.stats.tasks_validated',
defaultMessage: 'Tasks validated',
},
actionsNeeded: {
id: 'management.organisations.stats.actions_needed',
defaultMessage: 'Actions needed',
},
completedActions: {
id: 'management.organisations.stats.completed_actions',
defaultMessage: 'Completed actions',
},
actionsNeededHelp: {
id: 'management.organisations.stats.actions_needed.help',
defaultMessage:
Expand Down
104 changes: 104 additions & 0 deletions frontend/src/components/teamsAndOrgs/tasksStats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { Bar } from 'react-chartjs-2';

import { CHART_COLOURS } from '../../config';
import { useTagAPI } from '../../hooks/UseTagAPI';
import { formatFilterCountriesData } from '../../utils/countries';
import { formatTasksStatsData, formatTimelineTooltip } from '../../utils/formatChartJSData';
import { ProjectFilterSelect, DateFilterPicker } from '../projects/filterSelectFields';
import { TasksStatsSummary } from './tasksStatsSummary';

const TasksStats = ({ query, setQuery, stats }) => {
const [campaignAPIState] = useTagAPI([], 'campaigns');
const [countriesAPIState] = useTagAPI([], 'countries', formatFilterCountriesData);
const {
startDate: startDateInQuery,
endDate: endDateInQuery,
campaign: campaignInQuery,
location: countryInQuery,
} = query;

const fieldsetStyle = 'bn dib pv0-ns pv2 ph2-ns ph1 mh0 mb1';
const titleStyle = 'dib ttu fw5 blue-grey mb1';

return (
<>
<div className="w-100 cf flex flex-wrap">
<DateFilterPicker
fieldsetName="startDate"
fieldsetStyle={`${fieldsetStyle} fl`}
titleStyle={titleStyle}
selectedValue={startDateInQuery}
setQueryForChild={setQuery}
allQueryParamsForChild={query}
/>
<DateFilterPicker
fieldsetName="endDate"
fieldsetStyle={`${fieldsetStyle} fl`}
titleStyle={titleStyle}
selectedValue={endDateInQuery}
setQueryForChild={setQuery}
allQueryParamsForChild={query}
/>
<div className="w-40-l w-100 fl">
<ProjectFilterSelect
fieldsetName="campaign"
fieldsetStyle={`${fieldsetStyle} w-50-ns w-100`}
titleStyle={titleStyle}
selectedTag={campaignInQuery}
options={campaignAPIState}
setQueryForChild={setQuery}
allQueryParamsForChild={query}
/>
<ProjectFilterSelect
fieldsetName="location"
fieldsetStyle={`${fieldsetStyle} w-50-ns w-100`}
titleStyle={titleStyle}
selectedTag={countryInQuery}
options={countriesAPIState}
setQueryForChild={setQuery}
allQueryParamsForChild={query}
/>
</div>
</div>
<div className="pt3 pb3 ph2 cf w-100 w-two-thirds-l">
<TasksStatsChart stats={stats} />
</div>
<div className="cf w-100">
<TasksStatsSummary stats={stats} />
</div>
</>
);
};

const TasksStatsChart = ({ stats }) => {
const options = {
legend: { position: 'top', align: 'end', labels: { boxWidth: 12 } },
tooltips: {
callbacks: { label: (tooltip, data) => formatTimelineTooltip(tooltip, data, false) },
},
scales: {
yAxes: [
{
stacked: true,
ticks: {
beginAtZero: true,
},
},
],
xAxes: [
{
stacked: true,
},
],
},
};
return (
<Bar
data={formatTasksStatsData(stats, CHART_COLOURS.orange, CHART_COLOURS.red)}
options={options}
/>
);
};

export default TasksStats;
41 changes: 41 additions & 0 deletions frontend/src/components/teamsAndOrgs/tasksStatsSummary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { FormattedMessage, FormattedNumber } from 'react-intl';

import messages from './messages';
import { StatsCardContent } from '../statsCardContent';
import { useTotalTasksStats } from '../../hooks/UseTotalTasksStats';

export function TasksStatsSummary({ stats }) {
const totalStats = useTotalTasksStats(stats);
return (
<>
<div className="pa2 w-25-l w-50-m w-100 fl">
<div className="cf pa3 bg-white shadow-4">
<StatsCardContent
label={<FormattedMessage {...messages.tasksMapped} />}
className="tc"
value={<FormattedNumber value={totalStats.mapped} />}
/>
</div>
</div>
<div className="pa2 w-25-l w-50-m w-100 fl">
<div className="cf pa3 bg-white shadow-4">
<StatsCardContent
label={<FormattedMessage {...messages.tasksValidated} />}
className="tc"
value={<FormattedNumber value={totalStats.validated} />}
/>
</div>
</div>
<div className="pa2 w-25-l w-100 fl">
<div className="cf pa3 bg-white shadow-4">
<StatsCardContent
label={<FormattedMessage {...messages.completedActions} />}
className="tc"
value={<FormattedNumber value={totalStats.mapped + totalStats.validated} />}
/>
</div>
</div>
</>
);
}
Loading

0 comments on commit e16552a

Please sign in to comment.