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

fix(dashboard): on peut voir les stats médicales même si on n'est pas un personnel médical #1645

Merged
merged 2 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 67 additions & 58 deletions dashboard/src/recoil/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,66 +216,64 @@ export const itemsGroupedByPersonSelector = selector({
personsObject[relPersonPlace.person].relsPersonPlace.push(relPersonPlace);
personsObject[relPersonPlace.person].interactions.push(relPersonPlace.createdAt);
}
if (user.healthcareProfessional) {
for (const consultation of consultations) {
if (!personsObject[consultation.person]) continue;
personsObject[consultation.person].consultations = personsObject[consultation.person].consultations || [];
personsObject[consultation.person].consultations.push(consultation);
personsObject[consultation.person].hasAtLeastOneConsultation = true;
personsObject[consultation.person].interactions.push(consultation.dueAt);
personsObject[consultation.person].interactions.push(consultation.createdAt);
personsObject[consultation.person].interactions.push(consultation.completedAt);
const consultationIsVisibleByMe = consultation.onlyVisibleBy.length === 0 || consultation.onlyVisibleBy.includes(user._id);
for (const comment of consultation.comments || []) {
personsObject[consultation.person].interactions.push(comment.date);
if (!consultationIsVisibleByMe) continue;
personsObject[consultation.person].commentsMedical = personsObject[consultation.person].commentsMedical || [];
personsObject[consultation.person].commentsMedical.push({
...comment,
consultation,
person: consultation.person,
type: 'consultation',
});
}
for (const consultation of consultations) {
if (!personsObject[consultation.person]) continue;
personsObject[consultation.person].consultations = personsObject[consultation.person].consultations || [];
personsObject[consultation.person].consultations.push(consultation);
personsObject[consultation.person].hasAtLeastOneConsultation = true;
personsObject[consultation.person].interactions.push(consultation.dueAt);
personsObject[consultation.person].interactions.push(consultation.createdAt);
personsObject[consultation.person].interactions.push(consultation.completedAt);
const consultationIsVisibleByMe = consultation.onlyVisibleBy.length === 0 || consultation.onlyVisibleBy.includes(user._id);
for (const comment of consultation.comments || []) {
personsObject[consultation.person].interactions.push(comment.date);
if (!consultationIsVisibleByMe) continue;
personsObject[consultation.person].commentsMedical = personsObject[consultation.person].commentsMedical || [];
personsObject[consultation.person].commentsMedical.push({
...comment,
consultation,
person: consultation.person,
type: 'consultation',
});
}
for (const treatment of treatments) {
if (!personsObject[treatment.person]) continue;
personsObject[treatment.person].treatments = personsObject[treatment.person].treatments || [];
personsObject[treatment.person].treatments.push(treatment);
personsObject[treatment.person].interactions.push(treatment.createdAt);
for (const comment of treatment.comments || []) {
personsObject[treatment.person].interactions.push(comment.date);
personsObject[treatment.person].commentsMedical = personsObject[treatment.person].commentsMedical || [];
personsObject[treatment.person].commentsMedical.push({
...comment,
treatment,
person: treatment.person,
type: 'treatment',
});
}
}
for (const treatment of treatments) {
if (!personsObject[treatment.person]) continue;
personsObject[treatment.person].treatments = personsObject[treatment.person].treatments || [];
personsObject[treatment.person].treatments.push(treatment);
personsObject[treatment.person].interactions.push(treatment.createdAt);
for (const comment of treatment.comments || []) {
personsObject[treatment.person].interactions.push(comment.date);
personsObject[treatment.person].commentsMedical = personsObject[treatment.person].commentsMedical || [];
personsObject[treatment.person].commentsMedical.push({
...comment,
treatment,
person: treatment.person,
type: 'treatment',
});
}
for (const medicalFile of medicalFiles) {
if (!personsObject[medicalFile.person]) continue;
if (personsObject[medicalFile.person].medicalFile) {
personsObject[medicalFile.person].medicalFile = {
...medicalFile,
...personsObject[medicalFile.person].medicalFile,
documents: [...(medicalFile?.documents || []), ...(personsObject[medicalFile.person].medicalFile?.documents || [])],
comments: [...(medicalFile?.comments || []), ...(personsObject[medicalFile.person].medicalFile?.comments || [])],
};
} else {
personsObject[medicalFile.person].medicalFile = medicalFile;
}
personsObject[medicalFile.person].interactions.push(medicalFile.createdAt);
for (const comment of medicalFile.comments || []) {
personsObject[medicalFile.person].interactions.push(comment.date);
personsObject[medicalFile.person].commentsMedical = personsObject[medicalFile.person].commentsMedical || [];
personsObject[medicalFile.person].commentsMedical.push({
...comment,
person: medicalFile.person,
type: 'medical-file',
});
}
}
for (const medicalFile of medicalFiles) {
if (!personsObject[medicalFile.person]) continue;
if (personsObject[medicalFile.person].medicalFile) {
personsObject[medicalFile.person].medicalFile = {
...medicalFile,
...personsObject[medicalFile.person].medicalFile,
documents: [...(medicalFile?.documents || []), ...(personsObject[medicalFile.person].medicalFile?.documents || [])],
comments: [...(medicalFile?.comments || []), ...(personsObject[medicalFile.person].medicalFile?.comments || [])],
};
} else {
personsObject[medicalFile.person].medicalFile = medicalFile;
}
personsObject[medicalFile.person].interactions.push(medicalFile.createdAt);
for (const comment of medicalFile.comments || []) {
personsObject[medicalFile.person].interactions.push(comment.date);
personsObject[medicalFile.person].commentsMedical = personsObject[medicalFile.person].commentsMedical || [];
personsObject[medicalFile.person].commentsMedical.push({
...comment,
person: medicalFile.person,
type: 'medical-file',
});
}
}
for (const passage of passages) {
Expand Down Expand Up @@ -364,6 +362,17 @@ export const personsWithMedicalFileMergedSelector = selector({
},
});

export const personsForStatsSelector = selector({
key: 'personsForStatsSelector',
get: ({ get }) => {
const persons = get(arrayOfitemsGroupedByPersonSelector);
return persons.map((p) => ({
...(p.medicalFile || {}),
...p,
}));
},
});

const personsWithPlacesSelector = selector({
key: 'personsWithPlacesSelector',
get: ({ get }) => {
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/scenes/person/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ const personsFilteredBySearchSelector = selectorFamily({
if (!search?.length) {
return personsSorted;
}

const personsfilteredBySearch = filterBySearch(search, personsSorted);
const user = get(userState);
const excludeFields = user.healthcareProfessional ? [] : ['consultations', 'treatments', 'commentsMedical', 'medicalFile'];
const personsfilteredBySearch = filterBySearch(search, personsSorted, excludeFields);

return personsfilteredBySearch;
},
Expand Down
8 changes: 5 additions & 3 deletions dashboard/src/scenes/search/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { capture } from '../../services/sentry';
import UserName from '../../components/UserName';
import Search from '../../components/search';
import TagTeam from '../../components/TagTeam';
import { organisationState, teamsState } from '../../recoil/auth';
import { organisationState, teamsState, userState } from '../../recoil/auth';
import { actionsState, CANCEL, DONE, sortActionsOrConsultations } from '../../recoil/actions';
import { personsState, sortPersons } from '../../recoil/persons';
import { relsPersonPlaceState } from '../../recoil/relPersonPlace';
Expand Down Expand Up @@ -48,8 +48,10 @@ const personsFilteredBySearchForSearchSelector = selectorFamily({
({ get }) => {
const persons = get(personsWithFormattedBirthDateSelector);
const personsPopulated = get(itemsGroupedByPersonSelector);
const user = get(userState);
const excludeFields = user.healthcareProfessional ? [] : ['consultations', 'treatments', 'commentsMedical', 'medicalFile'];
if (!search?.length) return [];
return filterBySearch(search, persons).map((p) => personsPopulated[p._id]);
return filterBySearch(search, persons, excludeFields).map((p) => personsPopulated[p._id]);
},
});
const actionsObjectSelector = selector({
Expand Down Expand Up @@ -138,7 +140,7 @@ const observationsBySearchSelector = selectorFamily({
const populatedObservations = get(populatedObservationsSelector);
const observations = get(onlyFilledObservationsTerritories);
if (!search?.length) return [];
const observationsFilteredBySearch = filterBySearch(search, observations, true);
const observationsFilteredBySearch = filterBySearch(search, observations);
return observationsFilteredBySearch.map((obs) => populatedObservations[obs._id]).filter(Boolean);
},
});
Expand Down
12 changes: 7 additions & 5 deletions dashboard/src/scenes/search/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@ const excludeFields = new Set([
]);
const isObject = (variable) => typeof variable === 'object' && variable !== null && !Array.isArray(variable);

const prepareItemForSearch = (item) => {
const prepareItemForSearch = (item, userSpecificExcludeFields) => {
if (typeof item === 'string') return item;
if (!item) return '';
const itemClean = {};
for (let key of Object.keys(item)) {
if (excludeFields.has(key)) continue;
if (userSpecificExcludeFields.has(key)) continue;
if (isObject(item[key])) {
itemClean[key] = prepareItemForSearch(item[key]);
itemClean[key] = prepareItemForSearch(item[key], userSpecificExcludeFields);
} else if (Array.isArray(item[key])) {
itemClean[key] = item[key].map(prepareItemForSearch);
itemClean[key] = item[key].map((subItem) => prepareItemForSearch(subItem, userSpecificExcludeFields));
} else {
itemClean[key] = item[key];
}
}
return itemClean;
};

export const filterBySearch = (search, items = [], debug = false) => {
export const filterBySearch = (search, items = [], userSpecificExcludeFields = []) => {
const searchLowercased = search.toLocaleLowerCase();
// replace all accents with normal letters
const searchNormalized = searchLowercased.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
Expand All @@ -55,6 +56,7 @@ export const filterBySearch = (search, items = [], debug = false) => {
itemsNameStartWithWordWithNoAccent.push(item);
continue;
}

if (searchTerms.every((word) => lowerCaseName.includes(word))) {
itemsNameContainsOneOfTheWords.push(item);
continue;
Expand All @@ -63,7 +65,7 @@ export const filterBySearch = (search, items = [], debug = false) => {
itemsNameContainsOneOfTheWordsWithNoAccent.push(item);
continue;
}
const stringifiedItem = JSON.stringify(prepareItemForSearch(item)).toLocaleLowerCase();
const stringifiedItem = JSON.stringify(prepareItemForSearch(item, new Set(userSpecificExcludeFields))).toLocaleLowerCase();
if (searchTerms.filter((word) => stringifiedItem.includes(word)).length === searchTerms.length) {
anyOtherPrropertyContainsOneOfTheWords.push(item);
continue;
Expand Down
78 changes: 35 additions & 43 deletions dashboard/src/scenes/stats/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
flattenedCustomFieldsPersonsSelector,
} from '../../recoil/persons';
import { customFieldsObsSelector, territoryObservationsState } from '../../recoil/territoryObservations';
import { currentTeamState, organisationState, teamsState, userState } from '../../recoil/auth';
import { currentTeamState, organisationState, teamsState } from '../../recoil/auth';
import { actionsCategoriesSelector, DONE, flattenedActionsCategoriesSelector } from '../../recoil/actions';
import { reportsState } from '../../recoil/reports';
import { territoriesState } from '../../recoil/territory';
import { customFieldsMedicalFileSelector } from '../../recoil/medicalFiles';
import { personsWithMedicalFileMergedSelector, populatedPassagesSelector } from '../../recoil/selectors';
import { personsForStatsSelector, populatedPassagesSelector } from '../../recoil/selectors';
import useTitle from '../../services/useTitle';
import DateRangePickerWithPresets, { formatPeriod } from '../../components/DateRangePickerWithPresets';
import { useDataLoader } from '../../components/DataLoader';
Expand Down Expand Up @@ -100,7 +100,7 @@ const itemsForStatsSelector = selectorFamily({
const filtersExceptOutOfActiveList = activeFilters.filter((f) => f.field !== 'outOfActiveList');
const outOfActiveListFilter = activeFilters.find((f) => f.field === 'outOfActiveList')?.value;

const allPersons = get(personsWithMedicalFileMergedSelector);
const allPersons = get(personsForStatsSelector);

const offsetHours = allSelectedTeamsAreNightSession ? 12 : 0;
const isoStartDate = period.startDate ? dayjs(period.startDate).startOf('day').add(offsetHours, 'hour').toISOString() : null;
Expand Down Expand Up @@ -234,7 +234,6 @@ const initFilters = [filterMakingThingsClearAboutOutOfActiveListStatus];

const Stats = () => {
const organisation = useRecoilValue(organisationState);
const user = useRecoilValue(userState);
const currentTeam = useRecoilValue(currentTeamState);
const teams = useRecoilValue(teamsState);

Expand Down Expand Up @@ -510,9 +509,6 @@ const Stats = () => {
<ul className="noprint tw-mb-5 tw-flex tw-list-none tw-flex-wrap tw-border-b tw-border-zinc-200 tw-pl-0">
{tabs
.filter((tabCaption) => {
if (['Consultations', 'Dossiers médicaux des personnes créées', 'Dossiers médicaux des personnes suivies'].includes(tabCaption)) {
return !!user.healthcareProfessional;
}
if (['Observations'].includes(tabCaption)) {
return !!organisation.territoriesEnabled;
}
Expand Down Expand Up @@ -640,42 +636,38 @@ const Stats = () => {
/>
)}
{activeTab === 'Comptes-rendus' && <ReportsStats reports={reports} />}
{user.healthcareProfessional && (
<>
{activeTab === 'Consultations' && (
<ConsultationsStats
consultations={consultationsFilteredByPersons} // filter by persons
// filter by persons
personsWithConsultations={personsWithConsultations}
filterBase={filterPersonsWithAllFields()}
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
/>
)}
{activeTab === 'Dossiers médicaux des personnes créées' && (
<MedicalFilesStats
filterBase={filterPersonsWithAllFields(true)}
title="personnes créées"
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
personsForStats={personsCreated}
customFieldsMedicalFile={customFieldsMedicalFile}
personFields={personFields}
/>
)}
{activeTab === 'Dossiers médicaux des personnes suivies' && (
<MedicalFilesStats
title="personnes suivies"
personsForStats={personsUpdated}
customFieldsMedicalFile={customFieldsMedicalFile}
personFields={personFields}
// filter by persons
filterBase={filterPersonsWithAllFields(true)}
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
/>
)}
</>
{activeTab === 'Consultations' && (
<ConsultationsStats
consultations={consultationsFilteredByPersons} // filter by persons
// filter by persons
personsWithConsultations={personsWithConsultations}
filterBase={filterPersonsWithAllFields()}
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
/>
)}
{activeTab === 'Dossiers médicaux des personnes créées' && (
<MedicalFilesStats
filterBase={filterPersonsWithAllFields(true)}
title="personnes créées"
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
personsForStats={personsCreated}
customFieldsMedicalFile={customFieldsMedicalFile}
personFields={personFields}
/>
)}
{activeTab === 'Dossiers médicaux des personnes suivies' && (
<MedicalFilesStats
title="personnes suivies"
personsForStats={personsUpdated}
customFieldsMedicalFile={customFieldsMedicalFile}
personFields={personFields}
// filter by persons
filterBase={filterPersonsWithAllFields(true)}
filterPersons={filterPersons}
setFilterPersons={setFilterPersons}
/>
)}
</div>
{/* HACK: this last div is because Chrome crop the end of the page - I didn't find any better solution */}
Expand Down
6 changes: 3 additions & 3 deletions e2e/global_restricted-roles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,9 @@ test("test restricted accesses", async ({ page }) => {
await expect(page.getByRole("list").getByText("Rencontres")).toBeVisible();
await expect(page.getByRole("list").getByText("Observations")).toBeVisible();
await expect(page.getByRole("list").getByText("Comptes-rendus")).toBeVisible();
await expect(page.getByRole("list").getByText("Consultations")).not.toBeVisible();
await expect(page.getByRole("list").getByText("Dossiers médicaux des personnes suivies", { exact: true })).not.toBeVisible();
await expect(page.getByRole("list").getByText("Dossiers médicaux des personnes créées", { exact: true })).not.toBeVisible();
await expect(page.getByRole("list").getByText("Consultations")).toBeVisible();
await expect(page.getByRole("list").getByText("Dossiers médicaux des personnes suivies", { exact: true })).toBeVisible();
await expect(page.getByRole("list").getByText("Dossiers médicaux des personnes créées", { exact: true })).toBeVisible();

await expect(page.getByRole("link", { name: "Organisation" })).not.toBeVisible();
await expect(page.getByRole("link", { name: "Équipes" })).not.toBeVisible();
Expand Down
Loading