Skip to content

Commit

Permalink
feat(dashboard): nouvelle présentation du dossier médical (#1407)
Browse files Browse the repository at this point in the history
* feat: new medical file

* feat: new dossier medical

* feat: new dossier medical

* feat: new dossier medical

* feat: new dossier medical

* feat: new dossier medical

* fix: fix

* fix: up

* fix: fix tests 1

* fix: fix tests 2

* fix: fix tests 3

* fix: fix tests 4

* fix: remove old file

* fix: clean

* fix: fix
  • Loading branch information
rap2hpoutre committed Mar 20, 2023
1 parent ccbc1b1 commit 76cf6d2
Show file tree
Hide file tree
Showing 29 changed files with 1,213 additions and 1,180 deletions.
6 changes: 3 additions & 3 deletions dashboard/src/components/ActionOrConsultationName.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { disableConsultationRow } from '../recoil/consultations';
import { getName } from '../recoil/actions';
import UserName from './UserName';

export default function ActionOrConsultationName({ item }) {
export default function ActionOrConsultationName({ item, hideType = false }) {
const me = useRecoilValue(userState);
if (!!item.isConsultation && disableConsultationRow(item, me)) {
if (!me.healthcareProfessional) return <div />; // a non healthcare professional cannot see the name of a consultation anyway
return (
<div className="tw-italic tw-opacity-30">
Seulement visible par
<br />
{hideType ? ' ' : <br />}
<UserName id={item.user} />
</div>
);
Expand All @@ -30,7 +30,7 @@ export default function ActionOrConsultationName({ item }) {
{category}
</span>
))}
{!!item.isConsultation && <small className="text-muted">{item.type}</small>}
{!!item.isConsultation && !hideType && <small className="text-muted">{item.type}</small>}
</div>
</>
);
Expand Down
17 changes: 17 additions & 0 deletions dashboard/src/components/ConsultationModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,23 @@ export default function ConsultationModal({ onClose, personId, consultation, dat
<button name="Annuler" type="button" className="button-cancel" onClick={() => onClose()}>
Annuler
</button>
{!isNewConsultation && (
<button
type="button"
name="cancel"
className="button-destructive"
onClick={async (e) => {
e.stopPropagation();
if (!window.confirm('Voulez-vous supprimer cette consultation ?')) return;
const response = await API.delete({ path: `/consultation/${consultation._id}` });
if (!response.ok) return;
setAllConsultations((all) => all.filter((t) => t._id !== consultation._id));
toast.success('Consultation supprimée !');
onClose();
}}>
Supprimer
</button>
)}
<button type="submit" className="button-submit" form="add-consultation-form">
Sauvegarder
</button>
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/recoil/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export const itemsGroupedByPersonSelector = selector({
}
for (const medicalFile of medicalFiles) {
if (!personsObject[medicalFile.person]) continue;
if (personsObject[medicalFile.person].medicalFile) continue;
personsObject[medicalFile.person].medicalFile = medicalFile;
if (medicalFile.creatededAt > personsObject[medicalFile.person].lastUpdateCheckForGDPR) {
personsObject[medicalFile.person].lastUpdateCheckForGDPR = medicalFile.createdAt;
Expand Down
912 changes: 0 additions & 912 deletions dashboard/src/scenes/person/MedicalFile.js

This file was deleted.

32 changes: 5 additions & 27 deletions dashboard/src/scenes/person/components/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import TagTeam from '../../../components/TagTeam';
import ActionOrConsultationName from '../../../components/ActionOrConsultationName';
import { formatDateWithNameOfDay, formatTime } from '../../../services/date';
import { ModalHeader, ModalBody, ModalContainer, ModalFooter } from '../../../components/tailwind/Modal';
import { AgendaMutedIcon } from './AgendaMutedIcon';
import { FullScreenIcon } from './FullScreenIcon';

export const Actions = ({ person }) => {
const data = person?.actions || [];
Expand All @@ -25,7 +27,7 @@ export const Actions = ({ person }) => {
<>
<div className="tw-relative">
<div className="tw-sticky tw-top-0 tw-flex tw-bg-white tw-p-3">
<h4 className="tw-flex-1">Actions {filteredData.length ? `(${filteredData.length})` : ''}</h4>
<h4 className="tw-flex-1 tw-text-xl">Actions {filteredData.length ? `(${filteredData.length})` : ''}</h4>
<div className="flex-col tw-flex tw-items-center tw-gap-2">
<button
aria-label="Ajouter une action"
Expand All @@ -35,13 +37,7 @@ export const Actions = ({ person }) => {
</button>
{Boolean(filteredData.length) && (
<button className="tw-h-6 tw-w-6 tw-rounded-full tw-text-main tw-transition hover:tw-scale-125" onClick={() => setFullScreen(true)}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path
fillRule="evenodd"
d="M15 3.75a.75.75 0 01.75-.75h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0V5.56l-3.97 3.97a.75.75 0 11-1.06-1.06l3.97-3.97h-2.69a.75.75 0 01-.75-.75zm-12 0A.75.75 0 013.75 3h4.5a.75.75 0 010 1.5H5.56l3.97 3.97a.75.75 0 01-1.06 1.06L4.5 5.56v2.69a.75.75 0 01-1.5 0v-4.5zm11.47 11.78a.75.75 0 111.06-1.06l3.97 3.97v-2.69a.75.75 0 011.5 0v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 010-1.5h2.69l-3.97-3.97zm-4.94-1.06a.75.75 0 010 1.06L5.56 19.5h2.69a.75.75 0 010 1.5h-4.5a.75.75 0 01-.75-.75v-4.5a.75.75 0 011.5 0v2.69l3.97-3.97a.75.75 0 011.06 0z"
clipRule="evenodd"
/>
</svg>
<FullScreenIcon />
</button>
)}
</div>
Expand Down Expand Up @@ -126,25 +122,7 @@ const ActionsFilters = ({ data, filteredData, setFilterCategories, setFilterStat
</div>
) : (
<div className="tw-mt-8 tw-w-full tw-text-center tw-text-gray-300">
<svg
xmlns="http://www.w3.org/2000/svg"
className="tw-mx-auto tw-mb-2 tw-h-16 tw-w-16 tw-text-gray-200"
width={24}
height={24}
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4"></path>
<circle cx={18} cy={18} r={4}></circle>
<path d="M15 3v4"></path>
<path d="M7 3v4"></path>
<path d="M3 11h16"></path>
<path d="M18 16.496v1.504l1 1"></path>
</svg>
<AgendaMutedIcon />
Aucune action pour le moment
</div>
)}
Expand Down
23 changes: 23 additions & 0 deletions dashboard/src/scenes/person/components/AgendaMutedIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function AgendaMutedIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="tw-mx-auto tw-mb-2 tw-h-16 tw-w-16 tw-text-gray-200"
width={24}
height={24}
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4"></path>
<circle cx={18} cy={18} r={4}></circle>
<path d="M15 3v4"></path>
<path d="M7 3v4"></path>
<path d="M3 11h16"></path>
<path d="M18 16.496v1.504l1 1"></path>
</svg>
);
}
11 changes: 3 additions & 8 deletions dashboard/src/scenes/person/components/Comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { organisationState, usersState } from '../../../recoil/auth';
import { ModalBody, ModalContainer, ModalFooter, ModalHeader } from '../../../components/tailwind/Modal';
import { formatDateTimeWithNameOfDay } from '../../../services/date';
import CommentModal from './CommentModal';
import { FullScreenIcon } from './FullScreenIcon';

export default function Comments({ person }) {
const [modalCreateOpen, setModalCreateOpen] = useState(false);
Expand All @@ -20,7 +21,7 @@ export default function Comments({ person }) {
<>
<div className="tw-relative">
<div className="tw-sticky tw-top-0 tw-z-50 tw-flex tw-bg-white tw-p-3">
<h4 className="tw-flex-1">Commentaires {comments.length ? `(${comments.length})` : ''}</h4>
<h4 className="tw-flex-1 tw-text-xl">Commentaires {comments.length ? `(${comments.length})` : ''}</h4>
<div className="flex-col tw-flex tw-items-center tw-gap-2">
<button
aria-label="Ajouter un commentaire"
Expand All @@ -30,13 +31,7 @@ export default function Comments({ person }) {
</button>
{Boolean(comments.length) && (
<button className="tw-h-6 tw-w-6 tw-rounded-full tw-text-main tw-transition hover:tw-scale-125" onClick={() => setFullScreen(true)}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path
fillRule="evenodd"
d="M15 3.75a.75.75 0 01.75-.75h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0V5.56l-3.97 3.97a.75.75 0 11-1.06-1.06l3.97-3.97h-2.69a.75.75 0 01-.75-.75zm-12 0A.75.75 0 013.75 3h4.5a.75.75 0 010 1.5H5.56l3.97 3.97a.75.75 0 01-1.06 1.06L4.5 5.56v2.69a.75.75 0 01-1.5 0v-4.5zm11.47 11.78a.75.75 0 111.06-1.06l3.97 3.97v-2.69a.75.75 0 011.5 0v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 010-1.5h2.69l-3.97-3.97zm-4.94-1.06a.75.75 0 010 1.06L5.56 19.5h2.69a.75.75 0 010 1.5h-4.5a.75.75 0 01-.75-.75v-4.5a.75.75 0 011.5 0v2.69l3.97-3.97a.75.75 0 011.06 0z"
clipRule="evenodd"
/>
</svg>
<FullScreenIcon />
</button>
)}
</div>
Expand Down
223 changes: 223 additions & 0 deletions dashboard/src/scenes/person/components/Consultations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import React, { useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { organisationState, userState } from '../../../recoil/auth';
import { CANCEL, DONE, mappedIdsToLabels } from '../../../recoil/actions';
import SelectCustom from '../../../components/SelectCustom';
import ActionStatus from '../../../components/ActionStatus';
import TagTeam from '../../../components/TagTeam';
import ActionOrConsultationName from '../../../components/ActionOrConsultationName';
import { formatDateWithNameOfDay, formatTime } from '../../../services/date';
import { ModalHeader, ModalBody, ModalContainer, ModalFooter } from '../../../components/tailwind/Modal';
import { arrayOfitemsGroupedByConsultationSelector } from '../../../recoil/selectors';
import { useLocalStorage } from 'react-use';
import ConsultationModal from '../../../components/ConsultationModal';
import { AgendaMutedIcon } from './AgendaMutedIcon';
import { disableConsultationRow } from '../../../recoil/consultations';
import { FullScreenIcon } from './FullScreenIcon';
import useSearchParamState from '../../../services/useSearchParamState';

export const Consultations = ({ person }) => {
const [currentConsultationId, setCurrentConsultationId] = useSearchParamState('consultationId', null);
const [modalOpen, setModalOpen] = useState(!!currentConsultationId);
const [fullScreen, setFullScreen] = useState(false);

const allConsultations = useRecoilValue(arrayOfitemsGroupedByConsultationSelector);
const [consultationTypes, setConsultationTypes] = useLocalStorage('consultation-types', []);
const [consultationStatuses, setConsultationStatuses] = useLocalStorage('consultation-statuses', []);

const personConsultations = useMemo(() => (allConsultations || []).filter((c) => c.person === person._id), [allConsultations, person._id]);
const personConsultationsFiltered = useMemo(
() =>
personConsultations
.filter((c) => !consultationStatuses.length || consultationStatuses.includes(c.status))
.filter((c) => !consultationTypes.length || consultationTypes.includes(c.type))
.sort((p1, p2) => ((p1.completedAt || p1.dueAt) > (p2.completedAt || p2.dueAt) ? -1 : 1)),
[personConsultations, consultationStatuses, consultationTypes]
);

const data = personConsultations;
const filteredData = personConsultationsFiltered;

return (
<>
<div className="tw-relative">
<div className="tw-sticky tw-top-0 tw-z-10 tw-flex tw-bg-white tw-p-3">
<h4 className="tw-flex-1 tw-text-xl">Consultations {filteredData.length ? `(${filteredData.length})` : ''}</h4>
<div className="flex-col tw-flex tw-items-center tw-gap-2">
<button
aria-label="Ajouter une consultation"
className="tw-text-md tw-h-8 tw-w-8 tw-rounded-full tw-bg-blue-900 tw-font-bold tw-text-white tw-transition hover:tw-scale-125"
onClick={() => setModalOpen(true)}>
</button>
{Boolean(filteredData.length) && (
<button className="tw-h-6 tw-w-6 tw-rounded-full tw-text-blue-900 tw-transition hover:tw-scale-125" onClick={() => setFullScreen(true)}>
<FullScreenIcon />
</button>
)}
</div>
</div>
<ConsultationsFilters
data={data}
filteredData={filteredData}
consultationTypes={consultationTypes}
setConsultationTypes={setConsultationTypes}
setConsultationStatuses={setConsultationStatuses}
consultationStatuses={consultationStatuses}
/>
<ModalContainer open={!!fullScreen} className="" size="full" onClose={() => setFullScreen(false)}>
<ModalHeader title={`Consultations de ${person?.name} (${filteredData.length})`}>
<div className="tw-mt-2 tw-w-full tw-max-w-2xl">
<ConsultationsFilters
data={data}
filteredData={filteredData}
consultationTypes={consultationTypes}
setConsultationTypes={setConsultationTypes}
setConsultationStatuses={setConsultationStatuses}
consultationStatuses={consultationStatuses}
/>
</div>
</ModalHeader>
<ModalBody>
<ConsultationsTable filteredData={filteredData} person={person} />
</ModalBody>
<ModalFooter>
<button type="button" name="cancel" className="button-cancel" onClick={() => setFullScreen(false)}>
Fermer
</button>
<button type="button" className="button-submit" onClick={() => setModalOpen(true)}>
+ Ajouter une consultation
</button>
</ModalFooter>
</ModalContainer>
<ConsultationsTable filteredData={filteredData} person={person} setCurrentConsultationId={setCurrentConsultationId} />
</div>
{modalOpen && (
<ConsultationModal
onClose={() => {
setCurrentConsultationId(null);
setModalOpen(false);
}}
personId={person._id}
/>
)}
</>
);
};

const ConsultationsFilters = ({ data, filteredData, setConsultationTypes, setConsultationStatuses, consultationStatuses, consultationTypes }) => {
const organisation = useRecoilValue(organisationState);

return (
<>
{data.length ? (
<div className="tw-mb-4 tw-flex tw-basis-full tw-justify-between tw-gap-2 tw-px-3">
<div className="tw-shrink-0 tw-flex-grow">
<label htmlFor="consultation-select-types-filter">Filtrer par type</label>
<SelectCustom
options={organisation.consultations.map((e) => ({ _id: e.name, name: e.name }))}
value={organisation.consultations.map((e) => ({ _id: e.name, name: e.name })).filter((s) => consultationTypes.includes(s._id))}
getOptionValue={(s) => s._id}
getOptionLabel={(s) => s.name}
onChange={(selectedTypes) => setConsultationTypes(selectedTypes.map((t) => t._id))}
inputId="consultation-select-types-filter"
name="types"
isClearable
isMulti
/>
</div>
<div className="tw-shrink-0 tw-flex-grow">
<label htmlFor="consultation-select-status-filter">Filtrer par statut</label>
<SelectCustom
inputId="consultation-select-status-filter"
options={mappedIdsToLabels}
getOptionValue={(s) => s._id}
getOptionLabel={(s) => s.name}
name="status"
onChange={(s) => setConsultationStatuses(s.map((s) => s._id))}
isClearable
isMulti
value={mappedIdsToLabels.filter((s) => consultationStatuses.includes(s._id))}
/>
</div>
</div>
) : (
<div className="tw-mt-8 tw-w-full tw-text-center tw-text-gray-300">
<AgendaMutedIcon />
Aucune consultation pour le moment
</div>
)}
</>
);
};

const ConsultationsTable = ({ filteredData, person, setCurrentConsultationId }) => {
const user = useRecoilValue(userState);
const [consultationEditOpen, setConsultationEditOpen] = useState(false);

return (
<>
<table className="table">
<tbody className="small">
{filteredData.map((consultation, i) => {
const date = formatDateWithNameOfDay([DONE, CANCEL].includes(consultation.status) ? consultation.completedAt : consultation.dueAt);
const time = consultation.withTime && consultation.dueAt ? ` ${formatTime(consultation.dueAt)}` : '';
return (
<tr key={consultation._id} className={i % 2 ? 'tw-bg-sky-800/80 tw-text-white' : 'tw-bg-sky-100/80'}>
<td>
<div
className={
['restricted-access'].includes(user.role) || disableConsultationRow(consultation, user)
? 'tw-cursor-not-allowed tw-py-2'
: 'tw-cursor-pointer tw-py-2'
}
onClick={() => {
if (disableConsultationRow(consultation, user)) return;
setCurrentConsultationId(consultation._id);
setConsultationEditOpen(consultation);
}}>
<div className="tw-flex">
<div className="tw-flex-1">{`${date}${time}`}</div>
<div>
<ActionStatus status={consultation.status} />
</div>
</div>
<div className="tw-mt-2 tw-flex">
<div className="tw-flex tw-flex-1 tw-flex-row tw-items-center">
{!['restricted-access'].includes(user.role) && (
<>
<ActionOrConsultationName item={consultation} hideType />
{Boolean(consultation.name) && ![consultation.type, `Consultation ${consultation.type}`].includes(consultation.name) && (
<span className="tw-ml-2">- {consultation.type}</span>
)}
</>
)}
</div>
<div className="tw-flex tw-flex-col tw-gap-px">
{Array.isArray(consultation?.teams) ? (
consultation.teams.map((e) => <TagTeam key={e} teamId={e} />)
) : (
<TagTeam teamId={consultation?.team} />
)}
</div>
</div>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
{Boolean(consultationEditOpen) && (
<ConsultationModal
consultation={consultationEditOpen}
onClose={() => {
setCurrentConsultationId(null);
setConsultationEditOpen(false);
}}
personId={person._id}
/>
)}
</>
);
};

0 comments on commit 76cf6d2

Please sign in to comment.