Skip to content

Commit

Permalink
feat: historique pour les traitements
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Sep 8, 2023
1 parent 3f7165a commit c0fb997
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 17 deletions.
10 changes: 10 additions & 0 deletions app/src/recoil/treatments.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export const treatmentsState = atom({

const encryptedFields = ['person', 'user', 'startDate', 'endDate', 'name', 'dosage', 'frequency', 'indication', 'documents', 'comments', 'history'];

export const allowedTreatmentFieldsInHistory = [
{ name: 'person', label: 'Personne suivie' },
{ name: 'name', label: "Nom de l'action" },
{ name: 'startDate', label: 'Faite le' },
{ name: 'endDate', label: 'Faite le' },
{ name: 'dosage', label: 'Faite le' },
{ name: 'frequency', label: 'Faite le' },
{ name: 'indication', label: 'Faite le' },
];

export const prepareTreatmentForEncryption = (treatment) => {
try {
if (!looseUuidRegex.test(treatment.person)) {
Expand Down
28 changes: 23 additions & 5 deletions app/src/scenes/Persons/Treatment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ScreenTitle from '../../components/ScreenTitle';
import InputLabelled from '../../components/InputLabelled';
import Button from '../../components/Button';
import API from '../../services/api';
import { prepareTreatmentForEncryption, treatmentsState } from '../../recoil/treatments';
import { allowedTreatmentFieldsInHistory, prepareTreatmentForEncryption, treatmentsState } from '../../recoil/treatments';
import DateAndTimeInput from '../../components/DateAndTimeInput';
import DocumentsManager from '../../components/DocumentsManager';
import Spacer from '../../components/Spacer';
Expand Down Expand Up @@ -63,7 +63,7 @@ const Treatment = ({ navigation, route }) => {
if (!startDate) return Alert.alert('Veuillez indiquer une date de début');
Keyboard.dismiss();
setPosting(true);
const body = prepareTreatmentForEncryption({
const body = {
name,
dosage,
frequency,
Expand All @@ -74,9 +74,27 @@ const Treatment = ({ navigation, route }) => {
documents,
comments,
user: treatmentDB?.user ?? user._id,
history: treatmentDB?.history ?? user._id,
});
const treatmentResponse = isNew ? await API.post({ path: '/treatment', body }) : await API.put({ path: `/treatment/${treatmentDB._id}`, body });
};

if (!isNew) {
const historyEntry = {
date: new Date(),
user: user._id,
data: {},
};
for (const key in body) {
if (!allowedTreatmentFieldsInHistory.map((field) => field.name).includes(key)) continue;
if (body[key] !== treatmentDB[key]) historyEntry.data[key] = { oldValue: treatmentDB[key], newValue: body[key] };
}
if (!!Object.keys(historyEntry.data).length) {
const prevHistory = Array.isArray(treatmentDB.history) ? treatmentDB.history : [];
body.history = [...prevHistory, historyEntry];
}
}

const treatmentResponse = isNew
? await API.post({ path: '/treatment', body: prepareTreatmentForEncryption(body) })
: await API.put({ path: `/treatment/${treatmentDB._id}`, body: prepareTreatmentForEncryption(body) });
if (!treatmentResponse.ok) return false;
if (isNew) {
setAllTreatments((all) => [...all, treatmentResponse.decryptedData].sort((a, b) => new Date(b.startDate) - new Date(a.startDate)));
Expand Down
11 changes: 11 additions & 0 deletions dashboard/src/recoil/treatments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ const encryptedFields: Array<keyof TreatmentInstance> = [
'indication',
'documents',
'comments',
'history',
];

export const allowedTreatmentFieldsInHistory = [
{ name: 'person', label: 'Personne suivie' },
{ name: 'name', label: 'Nom du traitement' },
{ name: 'startDate', label: 'Date de début' },
{ name: 'endDate', label: 'Date de fin' },
{ name: 'dosage', label: 'Dosage' },
{ name: 'frequency', label: 'Fréquence' },
{ name: 'indication', label: 'Indication' },
];

export const prepareTreatmentForEncryption = (treatment: TreatmentInstance, { checkRequiredFields = true } = {}) => {
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/scenes/person/components/MedicalFilePrint.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function MedicalFilePrint({ person }) {
'createdAt',
'person',
'organisation',
'history',
];
return (
<div key={c._id} className="tw-mb-8">
Expand Down
104 changes: 101 additions & 3 deletions dashboard/src/scenes/person/components/TreatmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useHistory, useLocation } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import { organisationState, userState } from '../../../recoil/auth';
import { outOfBoundariesDate } from '../../../services/date';
import { dayjsInstance, outOfBoundariesDate } from '../../../services/date';
import API from '../../../services/api';
import { prepareTreatmentForEncryption, treatmentsState } from '../../../recoil/treatments';
import { allowedTreatmentFieldsInHistory, prepareTreatmentForEncryption, treatmentsState } from '../../../recoil/treatments';
import DatePicker from '../../../components/DatePicker';
import { CommentsModule } from '../../../components/CommentsGeneric';
import { ModalContainer, ModalBody, ModalFooter, ModalHeader } from '../../../components/tailwind/Modal';
Expand All @@ -16,6 +16,7 @@ import CustomFieldDisplay from '../../../components/CustomFieldDisplay';
import UserName from '../../../components/UserName';
import { DocumentsModule } from '../../../components/DocumentsGeneric';
import TabsNav from '../../../components/tailwind/TabsNav';
import PersonName from '../../../components/PersonName';

export default function TreatmentModal() {
const treatmentsObjects = useRecoilValue(itemsGroupedByTreatmentSelector);
Expand Down Expand Up @@ -84,6 +85,7 @@ function TreatmentContent({ onClose, treatment, personId }) {
return {
documents: [],
comments: [],
history: [],
...treatment,
};
}
Expand All @@ -100,6 +102,7 @@ function TreatmentContent({ onClose, treatment, personId }) {
organisation: organisation._id,
documents: [],
comments: [],
history: [],
};
}, [treatment, user, personId, organisation]);
const [activeTab, setActiveTab] = useState('Informations');
Expand Down Expand Up @@ -138,6 +141,23 @@ function TreatmentContent({ onClose, treatment, personId }) {
toast.error('La date de fin de traitement est hors limites (entre 1900 et 2100)');
return false;
}

if (!isNewTreatment) {
const historyEntry = {
date: new Date(),
user: user._id,
data: {},
};
for (const key in body) {
if (!allowedTreatmentFieldsInHistory.map((field) => field.name).includes(key)) continue;
if (body[key] !== treatment[key]) historyEntry.data[key] = { oldValue: treatment[key], newValue: body[key] };
}
if (!!Object.keys(historyEntry.data).length) {
const prevHistory = Array.isArray(treatment.history) ? treatment.history : [];
body.history = [...prevHistory, historyEntry];
}
}

const treatmentResponse = isNewTreatment
? await API.post({
path: '/treatment',
Expand Down Expand Up @@ -215,13 +235,15 @@ function TreatmentContent({ onClose, treatment, personId }) {
'Informations',
`Documents ${data?.documents?.length ? `(${data.documents.length})` : ''}`,
`Commentaires ${data?.comments?.length ? `(${data.comments.length})` : ''}`,
'Historique',
]}
onClick={(tab) => {
if (tab.includes('Informations')) setActiveTab('Informations');
if (tab.includes('Documents')) setActiveTab('Documents');
if (tab.includes('Commentaires')) setActiveTab('Commentaires');
if (tab.includes('Historique')) setActiveTab('Historique');
}}
activeTabIndex={['Informations', 'Documents', 'Commentaires'].findIndex((tab) => tab === activeTab)}
activeTabIndex={['Informations', 'Documents', 'Commentaires', 'Historique'].findIndex((tab) => tab === activeTab)}
/>
<form
id="add-treatment-form"
Expand Down Expand Up @@ -378,6 +400,12 @@ function TreatmentContent({ onClose, treatment, personId }) {
}}
/>
</div>
<div
className={['tw-flex tw-h-[50vh] tw-w-full tw-flex-col tw-gap-4 tw-overflow-y-auto', activeTab !== 'Historique' && 'tw-hidden']
.filter(Boolean)
.join(' ')}>
<TreatmentHistory treatment={treatment} />
</div>
</div>
</ModalBody>
<ModalFooter>
Expand Down Expand Up @@ -429,3 +457,73 @@ function TreatmentContent({ onClose, treatment, personId }) {
</>
);
}

function TreatmentHistory({ treatment }) {
const history = useMemo(() => [...(treatment?.history || [])].reverse(), [treatment?.history]);

return (
<div>
{!history?.length ? (
<div className="tw-py-10 tw-text-center tw-text-gray-300">
<p className="tw-text-lg tw-font-bold">Ce traitement n'a pas encore d'historique.</p>
<p className="tw-mt-2 tw-text-sm">
Lorsqu'un traitement est modifié, les changements sont enregistrés dans un historique,
<br />
que vous pourrez ainsi retrouver sur cette page.
</p>
</div>
) : (
<table className="table table-striped table-bordered">
<thead>
<tr className="tw-cursor-default">
<th>Date</th>
<th>Utilisateur</th>
<th>Donnée</th>
</tr>
</thead>
<tbody className="small">
{history.map((h) => {
return (
<tr key={h.date} className="tw-cursor-default">
<td>{dayjsInstance(h.date).format('DD/MM/YYYY HH:mm')}</td>
<td>
<UserName id={h.user} />
</td>
<td className="tw-max-w-prose">
{Object.entries(h.data).map(([key, value]) => {
const treatmentField = allowedTreatmentFieldsInHistory.find((f) => f.name === key);

if (key === 'person') {
return (
<p key={key}>
{treatmentField?.label} : <br />
<code>
<PersonName item={{ person: value.oldValue }} />
</code>{' '}
{' '}
<code>
<PersonName item={{ person: value.newValue }} />
</code>
</p>
);
}

return (
<p
key={key}
data-test-id={`${treatmentField?.label}: ${JSON.stringify(value.oldValue || '')}${JSON.stringify(value.newValue)}`}>
{treatmentField?.label} : <br />
<code>{JSON.stringify(value.oldValue || '')}</code><code>{JSON.stringify(value.newValue)}</code>
</p>
);
})}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
);
}
21 changes: 12 additions & 9 deletions dashboard/src/scenes/person/components/Treatments.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useHistory } from 'react-router-dom';
import { usersState, userState } from '../../../recoil/auth';
import { userState } from '../../../recoil/auth';
import { formatDateWithFullMonth } from '../../../services/date';
import { ModalHeader, ModalBody, ModalContainer, ModalFooter } from '../../../components/tailwind/Modal';
import { treatmentsState } from '../../../recoil/treatments';
import { AgendaMutedIcon } from './AgendaMutedIcon';
import { FullScreenIcon } from './FullScreenIcon';
import UserName from '../../../components/UserName';

export const Treatments = ({ person }) => {
const [fullScreen, setFullScreen] = useState(false);
Expand Down Expand Up @@ -79,7 +80,6 @@ export const Treatments = ({ person }) => {

const TreatmentsTable = ({ filteredData, person }) => {
const user = useRecoilValue(userState);
const users = useRecoilValue(usersState);
const history = useHistory();

const displayTreatment = (treatment) => {
Expand Down Expand Up @@ -118,14 +118,17 @@ const TreatmentsTable = ({ filteredData, person }) => {
searchParams.set('treatmentId', treatment._id);
history.push(`?${searchParams.toString()}`);
}}>
<div className="tw-flex">
<div className="tw-flex tw-flex-1 tw-items-center">
<TreatmentDate treatment={treatment} />
{Boolean(treatment.documents?.length) && <div className="tw-ml-2 tw-text-xs">{treatment.documents?.length} document(s)</div>}
</div>
<div>Créé par {treatment.user ? users.find((u) => u._id === treatment.user)?.name : ''}</div>
<TreatmentDate treatment={treatment} />
<div className="tw-mt-2 tw-font-semibold">{displayTreatment(treatment)}</div>
<div className="tw-flex tw-w-full tw-justify-between">
<p className="tw-mt-2 tw-mb-0 tw-flex tw-basis-full tw-gap-1 tw-text-xs tw-opacity-50 [overflow-wrap:anywhere]">
<span>Créé par</span>
<UserName id={treatment.user} />
</p>
{Boolean(treatment.documents?.length) && (
<div className="tw-ml-2 tw-shrink-0 tw-text-xs">{treatment.documents?.length} document(s)</div>
)}
</div>
<div className="tw-mt-2">{displayTreatment(treatment)}</div>
</div>
</td>
</tr>
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/types/treatment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export interface TreatmentInstance {
comments: any[];
createdAt: Date;
updatedAt: Date;
history: any[];
}

0 comments on commit c0fb997

Please sign in to comment.