Skip to content

Commit

Permalink
fix: more debug to observations
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Jan 15, 2024
1 parent 1d0a662 commit b2d508e
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 8 deletions.
6 changes: 4 additions & 2 deletions dashboard/src/components/Card.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import HelpButtonAndModal from './HelpButtonAndModal';

const Card = ({ title, count, unit, children, countId, dataTestId, help }) => {
const Card = ({ title, count, unit, children, countId, dataTestId, help, onClick = () => null }) => {
return (
<>
<div className="tw-relative tw-mb-2.5 tw-flex tw-h-full tw-w-full tw-flex-col tw-items-center tw-justify-between tw-rounded-2xl tw-border tw-border-main25 tw-bg-white tw-px-3 tw-pt-6 tw-pb-10 tw-font-bold">
<div
onClick={onClick}
className="tw-relative tw-mb-2.5 tw-flex tw-h-full tw-w-full tw-flex-col tw-items-center tw-justify-between tw-rounded-2xl tw-border tw-border-main25 tw-bg-white tw-px-3 tw-pt-6 tw-pb-10 tw-font-bold">
{!!title && (
<div className="tw-relative">
<p className="tw-m-0 tw-inline-block tw-text-center tw-text-lg tw-font-medium tw-text-black">
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/scenes/stats/Blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export const BlockDateWithTime = ({ data, field, help }) => {

const twoDecimals = (number) => Math.round(number * 100) / 100;

export const BlockTotal = ({ title, unit, data, field, help }) => {
export const BlockTotal = ({ title, unit, data, field, help, onClick = () => null }) => {
try {
if (!data.length) {
return <Card title={title} unit={unit} count={0} help={help} />;
return <Card title={title} unit={unit} count={0} help={help} onClick={onClick} />;
}
const dataWithOnlyNumbers = data.filter((item) => Boolean(item[field])).filter((e) => !isNaN(Number(e[field])));
const total = dataWithOnlyNumbers.reduce((total, item) => total + Number(item[field]), 0);
Expand All @@ -37,6 +37,7 @@ export const BlockTotal = ({ title, unit, data, field, help }) => {
title={title}
unit={unit}
count={twoDecimals(total)}
onClick={onClick}
help={help}
children={
<span className="font-weight-normal">
Expand Down
11 changes: 9 additions & 2 deletions dashboard/src/scenes/stats/CustomFieldsStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { BlockDateWithTime, BlockTotal } from './Blocks';
import Card from '../../components/Card';
import { getMultichoiceBarData, getPieData } from './utils';

const CustomFieldsStats = ({ customFields, data, additionalCols = [], dataTestId = '', help, onSliceClick, totalTitleForMultiChoice }) => {
const CustomFieldsStats = ({ customFields, data, help, onSliceClick, additionalCols = [], dataTestId = '', totalTitleForMultiChoice }) => {
const team = useRecoilValue(currentTeamState);

const customFieldsInStats = customFields
Expand All @@ -19,7 +19,14 @@ const CustomFieldsStats = ({ customFields, data, additionalCols = [], dataTestId
{additionalCols.map((col) => (
<div className="tw-basis-1/4 tw-px-4 tw-py-2" key={col.title}>
{/* TODO: fix alignment. */}
<Card title={col.title} count={col.value} children={<div></div>} dataTestId={dataTestId} help={help?.(col.title.capitalize())} />
<Card
title={col.title}
count={col.value}
children={<div></div>}
dataTestId={dataTestId}
help={help?.(col.title.capitalize())}
onClick={col.onBlockClick ? col.onBlockClick : null}
/>
</div>
))}
{customFieldsInStats.map((field) => {
Expand Down
190 changes: 188 additions & 2 deletions dashboard/src/scenes/stats/ObservationsStats.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
import React from 'react';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { utils, writeFile } from 'xlsx';
import SelectCustom from '../../components/SelectCustom';
import CustomFieldsStats from './CustomFieldsStats';
import { ModalBody, ModalContainer, ModalFooter, ModalHeader } from '../../components/tailwind/Modal';
import { teamsState, usersState } from '../../recoil/auth';
import TagTeam from '../../components/TagTeam';
import Table from '../../components/table';
import { dayjsInstance } from '../../services/date';
import { customFieldsObsSelector } from '../../recoil/territoryObservations';
import CreateObservation from '../../components/CreateObservation';
import { filterData } from '../../components/Filters';
import DateBloc from '../../components/DateBloc';
import Observation from '../../scenes/territory-observations/view';

const ObservationsStats = ({ territories, setSelectedTerritories, observations, customFieldsObs }) => {
const ObservationsStats = ({ territories, setSelectedTerritories, observations, customFieldsObs, allFilters }) => {
const [obsModalOpened, setObsModalOpened] = useState(false);
const [sliceField, setSliceField] = useState(null);
const [sliceValue, setSliceValue] = useState(null);
const [slicedData, setSlicedData] = useState([]);

const onSliceClick = (newSlice, fieldName, observationsConcerned = observations) => {
const newSlicefield = customFieldsObs.find((f) => f.name === fieldName);
setSliceField(newSlicefield);
setSliceValue(newSlice);
const slicedData =
newSlicefield.type === 'boolean'
? observationsConcerned.filter((p) => (newSlice === 'Non' ? !p[newSlicefield.field] : !!p[newSlicefield.field]))
: filterData(
observationsConcerned,
[{ ...newSlicefield, value: newSlice, type: newSlicefield.field === 'outOfActiveList' ? 'boolean' : newSlicefield.field }],
true
);
setSlicedData(slicedData);
setObsModalOpened(true);
};
return (
<>
<h3 className="tw-my-5 tw-text-xl">Statistiques des observations de territoire</h3>
Expand All @@ -25,18 +57,172 @@ const ObservationsStats = ({ territories, setSelectedTerritories, observations,
<CustomFieldsStats
data={observations}
customFields={customFieldsObs}
onSliceClick={onSliceClick}
dataTestId="number-observations"
additionalCols={[
{
title: "Nombre d'observations de territoire",
value: observations.length,
onBlockClick: () => {
setSlicedData(observations);
setObsModalOpened(true);
},
},
]}
help={(label) =>
`${label.capitalize()} des observations des territoires sélectionnés, dans la période définie.\n\nLa moyenne de cette données est basée sur le nombre d'observations faites.`
}
totalTitleForMultiChoice={<span className="tw-font-bold">Nombre d'observations concernées</span>}
/>
<SelectedObsModal
open={obsModalOpened}
onClose={() => {
setObsModalOpened(false);
}}
observations={slicedData}
sliceField={sliceField}
onAfterLeave={() => {
setSliceField(null);
setSliceValue(null);
setSlicedData([]);
}}
title={`${sliceField?.label ?? 'Observations de territoire'}${sliceValue ? ` : ${sliceValue}` : ''} (${slicedData.length})`}
territories={territories}
allFilters={allFilters}
/>
</>
);
};

const SelectedObsModal = ({ open, onClose, observations, territories, title, onAfterLeave, allFilters }) => {
const [observationToEdit, setObservationToEdit] = useState({});
const [openObservationModaleKey, setOpenObservationModaleKey] = useState(0);
const teams = useRecoilValue(teamsState);
const customFieldsObs = useRecoilValue(customFieldsObsSelector);
const users = useRecoilValue(usersState);

const exportXlsx = () => {
const wb = utils.book_new();
const formattedData = utils.json_to_sheet(
observations.map((observation) => {
return {
id: observation._id,
'Territoire - Nom': territories.find((t) => t._id === observation.territory)?.name,
'Observé le': dayjsInstance(observation.observedAt).format('YYYY-MM-DD HH:mm'),
Équipe: observation.team ? teams.find((t) => t._id === observation.team)?.name : '',
...customFieldsObs.reduce((fields, field) => {
if (['date', 'date-with-time'].includes(field.type))
fields[field.label || field.name] = observation[field.name]
? dayjsInstance(observation[field.name]).format(field.type === 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm')
: '';
else if (['boolean'].includes(field.type)) fields[field.label || field.name] = observation[field.name] ? 'Oui' : 'Non';
else if (['yes-no'].includes(field.type)) fields[field.label || field.name] = observation[field.name];
else if (Array.isArray(observation[field.name])) fields[field.label || field.name] = observation[field.name].join(', ');
else fields[field.label || field.name] = observation[field.name];
return fields;
}, {}),
'Créée par': users.find((u) => u._id === observation.user)?.name,
'Créée le': dayjsInstance(observation.createdAt).format('YYYY-MM-DD HH:mm'),
'Mise à jour le': dayjsInstance(observation.updatedAt).format('YYYY-MM-DD HH:mm'),
};
})
);
utils.book_append_sheet(wb, formattedData, 'Observations de territoires');

utils.book_append_sheet(wb, utils.json_to_sheet(observations), 'Observations (données brutes)');
utils.book_append_sheet(wb, utils.json_to_sheet(territories), 'Territoires (données brutes)');

const allTerritoriesFilters = [];
for (const selectedTerritory of allFilters.selectedTerritories) {
allTerritoriesFilters.push({
'Territoire - Nom': selectedTerritory.name,
'Territoire - _id': selectedTerritory._id,
});
}
utils.book_append_sheet(wb, utils.json_to_sheet(allTerritoriesFilters), 'Filtres (territoires)');
const otherFilters = [
{
'Voir toute l organisation': allFilters.viewAllOrganisationData,
'Période - début': allFilters.period.startDate,
'Période - fin': allFilters.period.endDate,
Preset: allFilters.preset,
},
];
utils.book_append_sheet(wb, utils.json_to_sheet(otherFilters), 'Filtres (autres)');
const allManuallySelectedTeams = [];
for (const selectedCustomField of allFilters.manuallySelectedTeams) {
allManuallySelectedTeams.push({
'Équipe - Nom': selectedCustomField.name,
'Équipe - _id': selectedCustomField._id,
});
}
utils.book_append_sheet(wb, utils.json_to_sheet(allManuallySelectedTeams), 'Filtres (équipes)');
writeFile(wb, `${title}.xlsx`);
};

return (
<>
<ModalContainer open={open} size="full" onClose={onClose} onAfterLeave={onAfterLeave}>
<ModalHeader
title={
<div className="tw-flex tw-w-full tw-items-center tw-justify-between">
{title}{' '}
<button onClick={exportXlsx} className="button-submit tw-ml-auto tw-mr-20">
Télécharger un export
</button>
</div>
}></ModalHeader>
<ModalBody>
<div className="tw-p-4">
<Table
className="Table"
data={observations}
onRowClick={(obs) => {
setObservationToEdit(obs);
setOpenObservationModaleKey((k) => k + 1);
}}
rowKey={'_id'}
columns={[
{
title: 'Date',
dataKey: 'observedAt',
render: (obs) => {
// anonymous comment migrated from `report.observations`
// have no time
// have no user assigned either
const time = dayjsInstance(obs.observedAt).format('D MMM HH:mm');
return (
<>
<DateBloc date={obs.observedAt} />
<span className="tw-mb-2 tw-block tw-w-full tw-text-center tw-opacity-50">{time === '00:00' && !obs.user ? null : time}</span>
</>
);
},
},
{ title: 'Territoire', dataKey: 'territory', render: (obs) => territories.find((t) => t._id === obs.territory)?.name },
{ title: 'Observation', dataKey: 'entityKey', render: (obs) => <Observation noTeams noBorder obs={obs} />, left: true },
{
title: 'Équipe en charge',
dataKey: 'team',
render: (obs) => <TagTeam teamId={obs?.team} />,
},
]}
/>
</div>
</ModalBody>
<ModalFooter>
<button
type="button"
name="cancel"
className="button-cancel"
onClick={() => {
onClose(null);
}}>
Fermer
</button>
</ModalFooter>
</ModalContainer>
<CreateObservation observation={observationToEdit} forceOpen={!!openObservationModaleKey} />
</>
);
};
Expand Down
7 changes: 7 additions & 0 deletions dashboard/src/scenes/stats/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,13 @@ const Stats = () => {
setSelectedTerritories={setSelectedTerritories}
observations={observations}
customFieldsObs={customFieldsObs}
allFilters={{
selectedTerritories,
viewAllOrganisationData,
period,
preset,
manuallySelectedTeams,
}}
/>
)}
{activeTab === 'Comptes-rendus' && <ReportsStats reports={reports} />}
Expand Down

0 comments on commit b2d508e

Please sign in to comment.