Skip to content

Commit

Permalink
fix: more debug to observations (#1836)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaudambro authored Jan 16, 2024
1 parent 1862678 commit a124187
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 11 deletions.
8 changes: 5 additions & 3 deletions dashboard/src/components/Card.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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 }) => {
const Component = !!onClick ? 'button' : 'div';
const props = !!onClick ? { onClick, type: 'button', name: 'card', className: 'button-cancel' } : {};
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">
Expand All @@ -12,12 +14,12 @@ const Card = ({ title, count, unit, children, countId, dataTestId, help }) => {
</p>
</div>
)}
<div className={['tw-flex tw-items-end tw-text-6xl tw-text-main', !!children ? 'tw-mb-4' : ''].join(' ')}>
<Component {...props} className={['tw-flex tw-items-end tw-text-6xl tw-text-main', !!children ? 'tw-mb-4' : ''].join(' ')}>
<span data-test-id={`${dataTestId}-${count}`} id={countId}>
{count}
</span>
{!!unit && <span className="tw-ml-2.5 tw-text-base">{unit}</span>}
</div>
</Component>
{children}
</div>
</>
Expand Down
65 changes: 60 additions & 5 deletions dashboard/src/scenes/report/components/ObservationsReport.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import dayjs from 'dayjs';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { utils, writeFile } from 'xlsx';
import { ModalHeader, ModalBody, ModalContainer, ModalFooter } from '../../../components/tailwind/Modal';
import { FullScreenIcon } from '../../../assets/icons/FullScreenIcon';
import Table from '../../../components/table';
Expand All @@ -9,6 +9,9 @@ import DateBloc from '../../../components/DateBloc';
import CreateObservation from '../../../components/CreateObservation';
import Observation from '../../territory-observations/view';
import { territoriesState } from '../../../recoil/territory';
import { dayjsInstance } from '../../../services/date';
import { teamsState, usersState } from '../../../recoil/auth';
import { customFieldsObsSelector } from '../../../recoil/territoryObservations';

export const ObservationsReport = ({ observations, period, selectedTeams }) => {
const [fullScreen, setFullScreen] = useState(false);
Expand Down Expand Up @@ -44,18 +47,70 @@ const ObservationsTable = ({ period, observations, selectedTeams }) => {
const [observationToEdit, setObservationToEdit] = useState({});
const [openObservationModaleKey, setOpenObservationModaleKey] = useState(0);
const territories = useRecoilValue(territoriesState);
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)');
utils.book_append_sheet(wb, utils.json_to_sheet(selectedTeams), 'Filtres (équipes)');
const otherFilters = [
{
'Période - début': period.startDate,
'Période - fin': period.endDate,
},
];
utils.book_append_sheet(wb, utils.json_to_sheet(otherFilters), 'Filtres (autres)');
writeFile(
wb,
`Compte rendu (${dayjsInstance(period.startDate).format('YYYY-MM-DD')} - ${dayjsInstance(period.endDate).format(
'YYYY-MM-DD'
)}) - Observations de territoires (${observations.length}).xlsx`
);
};

return (
<>
<div className="tw-py-2 tw-px-4 print:tw-mb-4">
<div className="tw-mb-5 tw-flex tw-justify-between">
<h3 className="tw-w-full tw-px-3 tw-py-2 tw-text-2xl tw-font-medium tw-text-black">Observations</h3>
<button onClick={exportXlsx} className="button-submit tw-ml-auto tw-mr-4">
Télécharger un export
</button>
<button
type="button"
className="button-submit tw-ml-auto tw-mb-2.5"
className="button-submit"
onClick={() => {
setObservationToEdit({
date: dayjs(period.startDate),
date: dayjsInstance(period.startDate),
observations: [],
});
setOpenObservationModaleKey((k) => k + 1);
Expand All @@ -80,7 +135,7 @@ const ObservationsTable = ({ period, observations, selectedTeams }) => {
// anonymous comment migrated from `report.observations`
// have no time
// have no user assigned either
const time = dayjs(obs.observedAt).format('D MMM HH:mm');
const time = dayjsInstance(obs.observedAt).format('D MMM HH:mm');
return (
<>
<DateBloc date={obs.observedAt} />
Expand Down
9 changes: 8 additions & 1 deletion dashboard/src/scenes/stats/CustomFieldsStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
179 changes: 177 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,161 @@ 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)');
utils.book_append_sheet(wb, utils.json_to_sheet(allFilters.selectedTerritories), 'Filtres (territoires)');
utils.book_append_sheet(wb, utils.json_to_sheet(allFilters.selectedTeams), 'Filtres (équipes)');
const otherFilters = [
{
'Période - début': allFilters.period.startDate,
'Période - fin': allFilters.period.endDate,
},
];
utils.book_append_sheet(wb, utils.json_to_sheet(otherFilters), 'Filtres (autres)');
writeFile(
wb,
`Statistiques (${dayjsInstance(allFilters.period.startDate).format('YYYY-MM-DD')} - ${dayjsInstance(allFilters.period.endDate).format(
'YYYY-MM-DD'
)}) - ${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>
}
/>
<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` is for debug purpose only
// TODO: remove when debugged
allFilters={{
selectedTerritories,
period,
selectedTeams,
}}
/>
)}
{activeTab === 'Comptes-rendus' && <ReportsStats reports={reports} />}
Expand Down

0 comments on commit a124187

Please sign in to comment.