Skip to content

Commit 2ddd305

Browse files
authored
Allow autoValidate/startTaskOnComplete on compliant inspections (#908)
1 parent 0f91893 commit 2ddd305

File tree

15 files changed

+181
-124
lines changed

15 files changed

+181
-124
lines changed

packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { useAnalytics } from '@monkvision/analytics';
22
import { Camera, CameraHUDProps } from '@monkvision/camera-web';
3-
import { useI18nSync, useLoadingState, useObjectMemo, usePreventExit } from '@monkvision/common';
3+
import { useI18nSync, useLoadingState, useObjectMemo } from '@monkvision/common';
44
import {
55
BackdropDialog,
66
InspectionGallery,
77
NavigateToCaptureOptions,
88
NavigateToCaptureReason,
99
} from '@monkvision/common-ui-web';
10-
import { useMonitoring } from '@monkvision/monitoring';
1110
import { MonkApiConfig } from '@monkvision/network';
1211
import {
1312
AddDamage,
@@ -39,6 +38,7 @@ import {
3938
usePhotoCaptureSightState,
4039
usePhotoCaptureTutorial,
4140
usePhotoCaptureSightGuidelines,
41+
useInspectionComplete,
4242
} from './hooks';
4343

4444
/**
@@ -158,7 +158,6 @@ export function PhotoCapture({
158158
customComplianceThresholdsPerSight,
159159
});
160160
const { t } = useTranslation();
161-
const monitoring = useMonitoring();
162161
const [currentScreen, setCurrentScreen] = useState(PhotoCaptureScreen.CAMERA);
163162
const analytics = useAnalytics();
164163
const loading = useLoadingState();
@@ -198,6 +197,8 @@ export function PhotoCapture({
198197
tasksBySight,
199198
complianceOptions,
200199
setIsInitialInspectionFetched,
200+
startTasksOnComplete,
201+
onComplete,
201202
});
202203
const { currentTutorialStep, goToNextTutorialStep, closeTutorial } = usePhotoCaptureTutorial({
203204
enableTutorial,
@@ -227,6 +228,13 @@ export function PhotoCapture({
227228
tasksBySight,
228229
onPictureTaken,
229230
});
231+
const { handleInspectionCompleted } = useInspectionComplete({
232+
startTasks,
233+
sightState,
234+
loading,
235+
startTasksOnComplete,
236+
onComplete,
237+
});
230238
const handleGalleryBack = () => setCurrentScreen(PhotoCaptureScreen.CAMERA);
231239
const handleNavigateToCapture = (options: NavigateToCaptureOptions) => {
232240
if (options.reason === NavigateToCaptureReason.ADD_DAMAGE) {
@@ -242,24 +250,6 @@ export function PhotoCapture({
242250
}
243251
setCurrentScreen(PhotoCaptureScreen.CAMERA);
244252
};
245-
const { allowRedirect } = usePreventExit(sightState.sightsTaken.length !== 0);
246-
const handleGalleryValidate = () => {
247-
startTasks()
248-
.then(() => {
249-
analytics.trackEvent('Capture Completed');
250-
analytics.setUserProperties({
251-
captureCompleted: true,
252-
sightSelected: 'inspection-completed',
253-
});
254-
allowRedirect();
255-
onComplete?.();
256-
sightState.setIsInspectionCompleted(true);
257-
})
258-
.catch((err) => {
259-
loading.onError(err);
260-
monitoring.handleError(err);
261-
});
262-
};
263253
const hudProps: Omit<PhotoCaptureHUDProps, keyof CameraHUDProps> = {
264254
sights,
265255
selectedSight: sightState.selectedSight,
@@ -313,7 +303,7 @@ export function PhotoCapture({
313303
allowSkipRetake={allowSkipRetake}
314304
onBack={handleGalleryBack}
315305
onNavigateToCapture={handleNavigateToCapture}
316-
onValidate={handleGalleryValidate}
306+
onValidate={handleInspectionCompleted}
317307
addDamage={addDamage}
318308
validateButtonLabel={validateButtonLabel}
319309
isInspectionCompleted={sightState.isInspectionCompleted}

packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ export function PhotoCaptureHUD({
205205
onSelectSight={onSelectSight}
206206
onRetakeSight={onRetakeSight}
207207
onValidateVehicleParts={onValidateVehicleParts}
208-
isLoading={loading.isLoading}
209208
error={loading.error ?? handle.error}
210209
previewDimensions={handle.previewDimensions}
211210
images={images}

packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ export interface PhotoCaptureHUDElementsProps
7272
* The effective pixel dimensions of the Camera video stream on the screen.
7373
*/
7474
previewDimensions: PixelDimensions | null;
75-
/**
76-
* Boolean indicating if the global loading state of the PhotoCapture component is loading or not.
77-
*/
78-
isLoading?: boolean;
7975
/**
8076
* The error that occurred in the PhotoCapture component. Set this value to `null` if there is no error.
8177
*/
@@ -98,7 +94,7 @@ export interface PhotoCaptureHUDElementsProps
9894
* Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process.
9995
*/
10096
export function PhotoCaptureHUDElements(params: PhotoCaptureHUDElementsProps) {
101-
if (params.isLoading || !!params.error) {
97+
if (params.error) {
10298
return null;
10399
}
104100
if (params.mode === CaptureMode.SIGHT) {

packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './usePhotoCaptureSightState';
22
export * from './useComplianceAnalytics';
33
export * from './usePhotoCaptureTutorial';
44
export * from './usePhotoCaptureSightGuidelines';
5+
export * from './useInspectionComplete';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { LoadingState, useObjectMemo, usePreventExit } from '@monkvision/common';
2+
import { useCallback, useEffect } from 'react';
3+
import { useAnalytics } from '@monkvision/analytics';
4+
import { useMonitoring } from '@monkvision/monitoring';
5+
import { PhotoCaptureAppConfig } from '@monkvision/types';
6+
import { StartTasksFunction } from '../../hooks';
7+
import { PhotoCaptureSightState } from './usePhotoCaptureSightState';
8+
9+
/**
10+
* Parameters of the useInspectionComplete hook.
11+
*/
12+
export interface InspectionCompleteParams
13+
extends Pick<PhotoCaptureAppConfig, 'startTasksOnComplete'> {
14+
/**
15+
* Callback called when the user has started the inspection tasks.
16+
*/
17+
startTasks: StartTasksFunction;
18+
/**
19+
* The sight state, created using the usePhotoCaptureSightState hook.
20+
*/
21+
sightState: PhotoCaptureSightState;
22+
/**
23+
* Global loading state of the PhotoCapture component.
24+
*/
25+
loading: LoadingState;
26+
/**
27+
* Callback called when the user clicks on the "Complete" button in the HUD.
28+
*/
29+
onComplete?: () => void;
30+
}
31+
32+
/**
33+
* Handle used to manage the completion of the inspection.
34+
*/
35+
export interface InspectionCompleteHandle {
36+
/**
37+
* Callback called when the user has completed the inspection.
38+
*/
39+
handleInspectionCompleted: () => void;
40+
}
41+
42+
/**
43+
* Custom hook used to generate the callback called when the user has completed the inspection.
44+
*/
45+
export function useInspectionComplete({
46+
startTasks,
47+
sightState,
48+
loading,
49+
startTasksOnComplete,
50+
onComplete,
51+
}: InspectionCompleteParams): InspectionCompleteHandle {
52+
const analytics = useAnalytics();
53+
const monitoring = useMonitoring();
54+
const { allowRedirect } = usePreventExit(sightState.sightsTaken.length !== 0);
55+
56+
const handleInspectionCompleted = useCallback(() => {
57+
startTasks()
58+
.then(() => {
59+
analytics.trackEvent('Capture Completed');
60+
analytics.setUserProperties({
61+
captureCompleted: true,
62+
sightSelected: 'inspection-completed',
63+
});
64+
allowRedirect();
65+
onComplete?.();
66+
sightState.setIsInspectionCompleted(true);
67+
})
68+
.catch((err) => {
69+
loading.onError(err);
70+
monitoring.handleError(err);
71+
});
72+
}, []);
73+
74+
useEffect(() => {
75+
const { isInspectionCompliant, isInspectionCompleted } = sightState;
76+
if (startTasksOnComplete && isInspectionCompliant && !isInspectionCompleted) {
77+
handleInspectionCompleted();
78+
}
79+
}, [sightState.isInspectionCompliant]);
80+
81+
return useObjectMemo({ handleInspectionCompleted });
82+
}

packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightGuidelines.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ export const TTL_MS = 48 * 60 * 60 * 1000;
77

88
function isTTLExpired(): boolean {
99
const timestamp = localStorage.getItem(STORAGE_KEY_PHOTO_CAPTURE_GUIDELINES);
10-
if (timestamp) {
11-
console.log(Date.now() - parseInt(timestamp, 10) > TTL_MS);
12-
}
13-
1410
return !timestamp || Date.now() - parseInt(timestamp, 10) > TTL_MS;
1511
}
1612

packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
1+
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react';
22
import {
33
GetInspectionResponse,
44
MonkApiConfig,
@@ -9,6 +9,7 @@ import { useMonitoring } from '@monkvision/monitoring';
99
import {
1010
getInspectionImages,
1111
LoadingState,
12+
MonkState,
1213
uniq,
1314
useAsyncEffect,
1415
useMonkState,
@@ -21,6 +22,7 @@ import {
2122
TaskName,
2223
ImageStatus,
2324
ProgressStatus,
25+
PhotoCaptureAppConfig,
2426
} from '@monkvision/types';
2527
import { sights } from '@monkvision/sights';
2628
import { useAnalytics } from '@monkvision/analytics';
@@ -70,12 +72,17 @@ export interface PhotoCaptureSightState {
7072
* Callback used to manually update the completion state of the inspection.
7173
*/
7274
setIsInspectionCompleted: Dispatch<SetStateAction<boolean>>;
75+
/**
76+
* Boolean indicating if the inspection is compliant or not.
77+
*/
78+
isInspectionCompliant: boolean;
7379
}
7480

7581
/**
7682
* Parameters of the usePhotoCaptureSightState hook.
7783
*/
78-
export interface PhotoCaptureSightsParams {
84+
export interface PhotoCaptureSightsParams
85+
extends Pick<PhotoCaptureAppConfig, 'startTasksOnComplete'> {
7986
/**
8087
* The inspection ID.
8188
*/
@@ -109,6 +116,10 @@ export interface PhotoCaptureSightsParams {
109116
* sight will be used.
110117
*/
111118
tasksBySight?: Record<string, TaskName[]>;
119+
/**
120+
* Callback called when inspection capture is complete.
121+
*/
122+
onComplete?: () => void;
112123
}
113124

114125
function getCaptureTasks(
@@ -174,6 +185,23 @@ function getLastPictureTakenUri(
174185
return images && images.length > 0 ? images[images.length - 1].path : null;
175186
}
176187

188+
function getNotCompliantSights(
189+
inspectionId: string,
190+
captureSights: Sight[],
191+
entities: MonkState,
192+
notCompliantStatus = [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED],
193+
) {
194+
return captureSights
195+
.map((s) => ({
196+
sight: s,
197+
image: getInspectionImages(inspectionId, entities.images, undefined, true).find(
198+
(i) => i.inspectionId === inspectionId && i.sightId === s.id,
199+
),
200+
}))
201+
.filter(({ image }) => !!image && notCompliantStatus.includes(image.status))
202+
.map(({ sight }) => sight);
203+
}
204+
177205
/**
178206
* Custom hook used to manage the state of the PhotoCapture sights. This state is automatically fetched from the API at
179207
* the start of the PhotoCapture process in order to allow users to start the inspection where they left it before.
@@ -187,6 +215,8 @@ export function usePhotoCaptureSightState({
187215
tasksBySight,
188216
setIsInitialInspectionFetched,
189217
complianceOptions,
218+
startTasksOnComplete,
219+
onComplete,
190220
}: PhotoCaptureSightsParams): PhotoCaptureSightState {
191221
if (captureSights.length === 0) {
192222
throw new Error('Empty sight list given to the Monk PhotoCapture component.');
@@ -223,19 +253,7 @@ export function usePhotoCaptureSightState({
223253
setSelectedSight(notCapturedSights[0]);
224254
} else {
225255
onLastSightTaken();
226-
const notCompliantSights = captureSights
227-
.map((s) => ({
228-
sight: s,
229-
image: getInspectionImages(inspectionId, state.images, undefined, true).find(
230-
(i) => i.inspectionId === inspectionId && i.sightId === s.id,
231-
),
232-
}))
233-
.filter(
234-
({ image }) =>
235-
!!image &&
236-
[ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED].includes(image.status),
237-
)
238-
.map(({ sight }) => sight);
256+
const notCompliantSights = getNotCompliantSights(inspectionId, captureSights, state);
239257
const sightToRetake =
240258
notCompliantSights.length > 0
241259
? notCompliantSights[0]
@@ -281,6 +299,16 @@ export function usePhotoCaptureSightState({
281299
setRetryCount((value) => value + 1);
282300
}, []);
283301

302+
const isInspectionCompliant = useMemo(() => {
303+
const notCapturedSights = captureSights.filter((s) => !sightsTaken.includes(s));
304+
const notCompliantSights = getNotCompliantSights(inspectionId, captureSights, state, [
305+
ImageStatus.UPLOADING,
306+
ImageStatus.NOT_COMPLIANT,
307+
ImageStatus.UPLOAD_FAILED,
308+
]);
309+
return !notCapturedSights.length && !notCompliantSights.length;
310+
}, [state.images]);
311+
284312
const takeSelectedSight = useCallback(() => {
285313
const isRetake = sightsTaken.includes(selectedSight);
286314
const updatedSightsTaken = isRetake ? sightsTaken : [...sightsTaken, selectedSight];
@@ -302,7 +330,7 @@ export function usePhotoCaptureSightState({
302330
});
303331
if (nextSight) {
304332
setSelectedSight(nextSight);
305-
} else {
333+
} else if (!startTasksOnComplete || !onComplete) {
306334
onLastSightTaken();
307335
}
308336
}, [sightsTaken, selectedSight, captureSights, onLastSightTaken]);
@@ -341,5 +369,6 @@ export function usePhotoCaptureSightState({
341369
setLastPictureTakenUri,
342370
retryLoadingInspection,
343371
retakeSight,
372+
isInspectionCompliant,
344373
});
345374
}

packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Styles } from '@monkvision/types';
22

33
export const styles: Styles = {
44
overlay: {
5-
position: 'absolute',
5+
position: 'fixed',
66
top: 0,
77
left: 0,
88
width: '100%',

packages/inspection-capture-web/src/hooks/useStartTasksOnComplete.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ function getTasksToStart({
5656
}
5757
});
5858
}
59-
return tasks.filter((task) => !TASKS_NOT_TO_START.includes(task));
59+
return tasks.filter(
60+
(task) =>
61+
!TASKS_NOT_TO_START.includes(task) &&
62+
!(task === TaskName.COMPLIANCES && tasks.includes(TaskName.DAMAGE_DETECTION)),
63+
);
6064
}
6165

6266
/**
@@ -81,8 +85,8 @@ export function useStartTasksOnComplete({
8185
}
8286
const names = getTasksToStart({ sights, additionalTasks, tasksBySight, startTasksOnComplete });
8387

84-
loading.start();
8588
try {
89+
loading.start();
8690
await startInspectionTasks({ inspectionId, names });
8791
loading.onSuccess();
8892
} catch (err) {

0 commit comments

Comments
 (0)