Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): nouvelle présentation du dossier médical #1407

Merged
merged 15 commits into from
Mar 20, 2023
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 }) {
<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
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}
/>
)}
</>
);
};