Skip to content

Commit 17669ab

Browse files
committed
fix(prélèvement): affichage de la conformité des échantillons (#618)
* ajoute un debounce sur l'enregistrement à chaud des formulaires * Fix build * Fix design * Fix design * Affiche la conformité de l'échantillon * fix tests * Fix tests
1 parent ea0750b commit 17669ab

File tree

16 files changed

+195
-127
lines changed

16 files changed

+195
-127
lines changed

frontend/src/App.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ textarea.fr-input {
227227
text-align: right;
228228
}
229229

230+
.d-block {
231+
display: block;
232+
}
233+
230234
h6,
231235
.d-flex-align-center {
232236
display: flex;

frontend/src/components/Prescription/PrescriptionNotes/PrescriptionNotes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const PrescriptionNotes = ({
1919
const [notes, setNotes] = useState(value);
2020

2121
const { triggerSave } = useAutoSave({
22-
onSave: (value) => onSubmitNotes?.(value),
22+
onSave: (value) => onSubmitNotes?.(value as string),
2323
delay: 500
2424
});
2525

frontend/src/components/Prescription/PrescriptionProgrammingInstruction/PrescriptionProgrammingInstruction.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const PrescriptionProgrammingInstruction = ({
2121
const [instruction, setInstruction] = useState(value || '');
2222

2323
const { triggerSave } = useAutoSave({
24-
onSave: (value) => onSubmitInstruction?.(value),
24+
onSave: (value) => onSubmitInstruction?.(value as string),
2525
delay: 500
2626
});
2727

frontend/src/hooks/useAutoSave.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useRef } from 'react';
22

33
interface UseAutoSaveOptions {
4-
onSave: (value: string) => void | Promise<void>;
4+
onSave: (value?: string) => void | Promise<void>;
55
delay?: number;
66
}
77

@@ -16,7 +16,7 @@ export const useAutoSave = ({ onSave, delay = 500 }: UseAutoSaveOptions) => {
1616
};
1717
}, []);
1818

19-
const triggerSave = (value: string) => {
19+
const triggerSave = (value?: string) => {
2020
if (debounceTimerRef.current) {
2121
clearTimeout(debounceTimerRef.current);
2222
}

frontend/src/hooks/useForm.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isEqual } from 'lodash-es';
22
import { useEffect, useState } from 'react';
33
import { z, ZodObject } from 'zod';
4+
import { useAutoSave } from './useAutoSave';
45

56
type MessageType = 'error' | 'success' | 'default';
67

@@ -105,9 +106,14 @@ export function useForm<
105106
// eslint-disable-next-line react-hooks/exhaustive-deps
106107
}, [...Object.values(input)]);
107108

109+
const { triggerSave } = useAutoSave({
110+
onSave: () => onInputChange?.(),
111+
delay: 500
112+
});
113+
108114
useEffect(() => {
109115
if (isInitialized) {
110-
(async () => await onInputChange?.())();
116+
triggerSave?.();
111117
}
112118
setIsInitialized(true);
113119
}, [...Object.values(input)]); // eslint-disable-line react-hooks/exhaustive-deps

frontend/src/views/SampleView/DraftSample/MatrixStep/stories/MatrixStepPPV.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
genSampleContextData
1212
} from 'maestro-shared/test/sampleFixtures';
1313
import { genAuthUser, genUser } from 'maestro-shared/test/userFixtures';
14-
import { expect, fn, screen, userEvent, within } from 'storybook/test';
14+
import { expect, fn, screen, userEvent, waitFor, within } from 'storybook/test';
1515
import { getMockApi, MockApi } from '../../../../../services/mockApiClient';
1616
import MatrixStep from '../MatrixStep';
1717

@@ -187,7 +187,7 @@ export const MatrixStepPPVSaveOnBlurWithoutHandlingErrors: Story = {
187187
canvas.queryByText('Veuillez renseigner la partie du végétal.')
188188
).not.toBeInTheDocument();
189189

190-
await expect(createOrUpdateMock).toHaveBeenCalled();
190+
await waitFor(() => expect(createOrUpdateMock).toHaveBeenCalled());
191191
}
192192
};
193193

frontend/src/views/SampleView/SampleItemAnalysis/SampleItemAdmissibility/SampleItemAdmissibility.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import clsx from 'clsx';
55
import { format } from 'date-fns';
66
import './SampleItemAdmissibility.scss';
77

8-
import { PartialAnalysis } from 'maestro-shared/schema/Analysis/Analysis';
98
import { getLaboratoryFullName } from 'maestro-shared/schema/Laboratory/Laboratory';
109
import { SampleChecked } from 'maestro-shared/schema/Sample/Sample';
1110
import { SampleItem } from 'maestro-shared/schema/Sample/SampleItem';
@@ -17,7 +16,6 @@ import { SampleItemAdmissibilityEditModal } from './SampleItemAdmissibilityEditM
1716
interface Props {
1817
sample: SampleChecked;
1918
sampleItem: SampleItem;
20-
sampleItemAnalysis?: PartialAnalysis;
2119
readonly: boolean;
2220
}
2321

@@ -29,7 +27,6 @@ const editSampleAdmissibility = createModal({
2927
export const SampleItemAdmissibility: FunctionComponent<Props> = ({
3028
sample,
3129
sampleItem,
32-
sampleItemAnalysis,
3330
readonly,
3431
..._rest
3532
}) => {
@@ -39,7 +36,7 @@ export const SampleItemAdmissibility: FunctionComponent<Props> = ({
3936
let message: string = '';
4037

4138
if (sampleItem.receiptDate) {
42-
message = `${sampleItemAnalysis?.status !== 'NotAdmissible' ? 'Échantillon recevable' : 'Échantillon non recevable'} reçu par le laboratoire le ${format(sampleItem.receiptDate, 'dd/MM/yyyy')}`;
39+
message = `${sampleItem.analysis?.status !== 'NotAdmissible' ? 'Échantillon recevable' : 'Échantillon non recevable'} reçu par le laboratoire le ${format(sampleItem.receiptDate, 'dd/MM/yyyy')}`;
4340
} else {
4441
message = 'Aucune information relative à la réception par le laboratoire.';
4542
}
@@ -81,19 +78,18 @@ export const SampleItemAdmissibility: FunctionComponent<Props> = ({
8178
)}
8279
</div>
8380
<div className={cx('fr-text--bold')}>{message}</div>
81+
82+
{!!sampleItem.notesOnAdmissibility &&
83+
sampleItem.notesOnAdmissibility.length > 0 && (
84+
<i>{sampleItem.notesOnAdmissibility}</i>
85+
)}
8486
</div>
8587

8688
<SampleItemAdmissibilityEditModal
8789
modal={editSampleAdmissibility}
8890
sampleItem={sampleItem}
89-
sampleItemAnalysis={sampleItemAnalysis}
9091
/>
9192
</div>
92-
93-
{!!sampleItem.notesOnAdmissibility &&
94-
sampleItem.notesOnAdmissibility.length > 0 && (
95-
<i>{sampleItem.notesOnAdmissibility}</i>
96-
)}
9793
</div>
9894
);
9995
};

frontend/src/views/SampleView/SampleItemAnalysis/SampleItemAdmissibility/SampleItemAdmissibilityEditModal.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createModal } from '@codegouvfr/react-dsfr/Modal';
2-
import { PartialAnalysis } from 'maestro-shared/schema/Analysis/Analysis';
32
import { SampleItem } from 'maestro-shared/schema/Sample/SampleItem';
43
import React, { FunctionComponent, useContext, useRef } from 'react';
54
import { assert, type Equals } from 'tsafe';
@@ -11,13 +10,11 @@ import {
1110

1211
type Props = {
1312
sampleItem: SampleItem;
14-
sampleItemAnalysis?: PartialAnalysis;
1513
modal: ReturnType<typeof createModal>;
1614
};
1715
export const SampleItemAdmissibilityEditModal: FunctionComponent<Props> = ({
1816
modal,
1917
sampleItem,
20-
sampleItemAnalysis,
2118
..._rest
2219
}) => {
2320
assert<Equals<keyof typeof _rest, never>>();
@@ -42,9 +39,11 @@ export const SampleItemAdmissibilityEditModal: FunctionComponent<Props> = ({
4239
sampleItemUpdate: {
4340
...sampleItem,
4441
receiptDate,
45-
analysisStatus:
46-
isAdmissible === false ? 'NotAdmissible' : 'Report',
47-
notesOnAdmissibility: notesOnAdmissibility
42+
analysis: {
43+
...sampleItem.analysis,
44+
status: isAdmissible === false ? 'NotAdmissible' : 'Report'
45+
},
46+
notesOnAdmissibility
4847
}
4948
});
5049
form.reset();
@@ -76,7 +75,6 @@ export const SampleItemAdmissibilityEditModal: FunctionComponent<Props> = ({
7675
>
7776
<SampleItemAdmissibilityForm
7877
sampleItem={sampleItem}
79-
sampleItemAnalysis={sampleItemAnalysis}
8078
setForm={(f) => (admissibilityForm.current = f)}
8179
/>
8280
</modal.Component>

frontend/src/views/SampleView/SampleItemAnalysis/SampleItemAdmissibility/SampleItemAdmissibilityForm.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { useForm } from '../../../../hooks/useForm';
66
import { cx } from '@codegouvfr/react-dsfr/fr/cx';
77
import clsx from 'clsx';
88
import { isNil } from 'lodash-es';
9-
import { PartialAnalysis } from 'maestro-shared/schema/Analysis/Analysis';
109
import { SampleItem } from 'maestro-shared/schema/Sample/SampleItem';
1110
import { MaestroDate } from 'maestro-shared/utils/date';
1211
import { FunctionComponent } from 'react';
@@ -52,12 +51,10 @@ const FormChecked = z
5251
export type FormRefinement = ReturnType<typeof useForm<typeof FormChecked>>;
5352
type Props = {
5453
sampleItem: SampleItem;
55-
sampleItemAnalysis?: PartialAnalysis;
5654
setForm?: (form: FormRefinement) => void;
5755
};
5856
export const SampleItemAdmissibilityForm: FunctionComponent<Props> = ({
5957
sampleItem,
60-
sampleItemAnalysis,
6158
setForm,
6259
..._rest
6360
}) => {
@@ -68,9 +65,9 @@ export const SampleItemAdmissibilityForm: FunctionComponent<Props> = ({
6865
);
6966
const [receiptDate, setReceiptDate] = useState(sampleItem.receiptDate);
7067
const [isAdmissible, setIsAdmissible] = useState(
71-
sampleItemAnalysis?.status === 'NotAdmissible'
68+
sampleItem.analysis?.status === 'NotAdmissible'
7269
? false
73-
: !isNil(sampleItemAnalysis?.status)
70+
: !isNil(sampleItem.analysis?.status)
7471
? true
7572
: null
7673
);

frontend/src/views/SampleView/SampleItemAnalysis/SampleItemAnalysis.tsx

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,7 @@ const SampleItemAnalysis: FunctionComponent<Props> = ({
5353
apiClient.useUpdateAnalysisMutation({
5454
fixedCacheKey: `complete-analysis-${sample.id}`
5555
});
56-
const {
57-
data: analysis,
58-
isLoading: isAnalysisLoading,
59-
isFetching: isAnalysisFetching
60-
} = apiClient.useGetSampleItemAnalysisQuery({
56+
const { data: analysis } = apiClient.useGetSampleItemAnalysisQuery({
6157
sampleId: sample.id,
6258
itemNumber: sampleItem.itemNumber,
6359
copyNumber: sampleItem.copyNumber
@@ -119,7 +115,8 @@ const SampleItemAnalysis: FunctionComponent<Props> = ({
119115

120116
const isEditing: boolean =
121117
!readonly &&
122-
(location.pathname.endsWith('/edit') || analysis?.status !== 'Completed');
118+
(location.pathname.endsWith('/edit') ||
119+
sampleItem.analysis?.status !== 'Completed');
123120

124121
return (
125122
<div className={'analysis-container'}>
@@ -156,14 +153,11 @@ const SampleItemAnalysis: FunctionComponent<Props> = ({
156153
)}
157154

158155
<div>
159-
{!isAnalysisFetching && !isAnalysisLoading && (
160-
<SampleItemAdmissibility
161-
sample={sample}
162-
readonly={readonly}
163-
sampleItem={sampleItem}
164-
sampleItemAnalysis={analysis}
165-
/>
166-
)}
156+
<SampleItemAdmissibility
157+
sample={sample}
158+
readonly={readonly}
159+
sampleItem={sampleItem}
160+
/>
167161
{analysis?.status !== 'NotAdmissible' && (
168162
<AnalysisDocumentPreview
169163
partialAnalysis={analysis}
@@ -358,25 +352,24 @@ const SampleItemAnalysis: FunctionComponent<Props> = ({
358352
</Accordion>
359353
</div>
360354

361-
{['Analysis', 'InReview', 'Completed'].includes(sample.status) &&
362-
analysis && (
363-
<>
364-
{!isEditing ? (
365-
<SampleAnalysisOverview
366-
sample={sample}
367-
analysis={analysis}
368-
readonly={readonly}
369-
onEdit={() => navigateToSampleEdit(sample.id)}
370-
/>
371-
) : (
372-
<SampleAnalysisForm
373-
partialAnalysis={analysis}
374-
sample={sample}
375-
onDone={() => navigateToSample(sample.id)}
376-
/>
377-
)}
378-
</>
379-
)}
355+
{analysis && (
356+
<>
357+
{!isEditing ? (
358+
<SampleAnalysisOverview
359+
sample={sample}
360+
analysis={analysis}
361+
readonly={readonly}
362+
onEdit={() => navigateToSampleEdit(sample.id)}
363+
/>
364+
) : (
365+
<SampleAnalysisForm
366+
partialAnalysis={analysis}
367+
sample={sample}
368+
onDone={() => navigateToSample(sample.id)}
369+
/>
370+
)}
371+
</>
372+
)}
380373
{sample.status === 'InReview' && <UserFeedback />}
381374
</div>
382375
);

0 commit comments

Comments
 (0)