Skip to content
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
34 changes: 12 additions & 22 deletions packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useAnalytics } from '@monkvision/analytics';
import { Camera, CameraHUDProps } from '@monkvision/camera-web';
import { useI18nSync, useLoadingState, useObjectMemo, usePreventExit } from '@monkvision/common';
import { useI18nSync, useLoadingState, useObjectMemo } from '@monkvision/common';
import {
BackdropDialog,
InspectionGallery,
NavigateToCaptureOptions,
NavigateToCaptureReason,
} from '@monkvision/common-ui-web';
import { useMonitoring } from '@monkvision/monitoring';
import { MonkApiConfig } from '@monkvision/network';
import {
AddDamage,
Expand Down Expand Up @@ -39,6 +38,7 @@ import {
usePhotoCaptureSightState,
usePhotoCaptureTutorial,
usePhotoCaptureSightGuidelines,
useInspectionComplete,
} from './hooks';

/**
Expand Down Expand Up @@ -158,7 +158,6 @@ export function PhotoCapture({
customComplianceThresholdsPerSight,
});
const { t } = useTranslation();
const monitoring = useMonitoring();
const [currentScreen, setCurrentScreen] = useState(PhotoCaptureScreen.CAMERA);
const analytics = useAnalytics();
const loading = useLoadingState();
Expand Down Expand Up @@ -198,6 +197,8 @@ export function PhotoCapture({
tasksBySight,
complianceOptions,
setIsInitialInspectionFetched,
startTasksOnComplete,
onComplete,
});
const { currentTutorialStep, goToNextTutorialStep, closeTutorial } = usePhotoCaptureTutorial({
enableTutorial,
Expand Down Expand Up @@ -227,6 +228,13 @@ export function PhotoCapture({
tasksBySight,
onPictureTaken,
});
const { handleInspectionCompleted } = useInspectionComplete({
startTasks,
sightState,
loading,
startTasksOnComplete,
onComplete,
});
const handleGalleryBack = () => setCurrentScreen(PhotoCaptureScreen.CAMERA);
const handleNavigateToCapture = (options: NavigateToCaptureOptions) => {
if (options.reason === NavigateToCaptureReason.ADD_DAMAGE) {
Expand All @@ -242,24 +250,6 @@ export function PhotoCapture({
}
setCurrentScreen(PhotoCaptureScreen.CAMERA);
};
const { allowRedirect } = usePreventExit(sightState.sightsTaken.length !== 0);
const handleGalleryValidate = () => {
startTasks()
.then(() => {
analytics.trackEvent('Capture Completed');
analytics.setUserProperties({
captureCompleted: true,
sightSelected: 'inspection-completed',
});
allowRedirect();
onComplete?.();
sightState.setIsInspectionCompleted(true);
})
.catch((err) => {
loading.onError(err);
monitoring.handleError(err);
});
};
const hudProps: Omit<PhotoCaptureHUDProps, keyof CameraHUDProps> = {
sights,
selectedSight: sightState.selectedSight,
Expand Down Expand Up @@ -313,7 +303,7 @@ export function PhotoCapture({
allowSkipRetake={allowSkipRetake}
onBack={handleGalleryBack}
onNavigateToCapture={handleNavigateToCapture}
onValidate={handleGalleryValidate}
onValidate={handleInspectionCompleted}
addDamage={addDamage}
validateButtonLabel={validateButtonLabel}
isInspectionCompleted={sightState.isInspectionCompleted}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ export function PhotoCaptureHUD({
onSelectSight={onSelectSight}
onRetakeSight={onRetakeSight}
onValidateVehicleParts={onValidateVehicleParts}
isLoading={loading.isLoading}
error={loading.error ?? handle.error}
previewDimensions={handle.previewDimensions}
images={images}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,6 @@ export interface PhotoCaptureHUDElementsProps
* The effective pixel dimensions of the Camera video stream on the screen.
*/
previewDimensions: PixelDimensions | null;
/**
* Boolean indicating if the global loading state of the PhotoCapture component is loading or not.
*/
isLoading?: boolean;
/**
* The error that occurred in the PhotoCapture component. Set this value to `null` if there is no error.
*/
Expand All @@ -98,7 +94,7 @@ export interface PhotoCaptureHUDElementsProps
* Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process.
*/
export function PhotoCaptureHUDElements(params: PhotoCaptureHUDElementsProps) {
if (params.isLoading || !!params.error) {
if (params.error) {
return null;
}
if (params.mode === CaptureMode.SIGHT) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './usePhotoCaptureSightState';
export * from './useComplianceAnalytics';
export * from './usePhotoCaptureTutorial';
export * from './usePhotoCaptureSightGuidelines';
export * from './useInspectionComplete';
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { LoadingState, useObjectMemo, usePreventExit } from '@monkvision/common';
import { useCallback, useEffect } from 'react';
import { useAnalytics } from '@monkvision/analytics';
import { useMonitoring } from '@monkvision/monitoring';
import { PhotoCaptureAppConfig } from '@monkvision/types';
import { StartTasksFunction } from '../../hooks';
import { PhotoCaptureSightState } from './usePhotoCaptureSightState';

/**
* Parameters of the useInspectionComplete hook.
*/
export interface InspectionCompleteParams
extends Pick<PhotoCaptureAppConfig, 'startTasksOnComplete'> {
/**
* Callback called when the user has started the inspection tasks.
*/
startTasks: StartTasksFunction;
/**
* The sight state, created using the usePhotoCaptureSightState hook.
*/
sightState: PhotoCaptureSightState;
/**
* Global loading state of the PhotoCapture component.
*/
loading: LoadingState;
/**
* Callback called when the user clicks on the "Complete" button in the HUD.
*/
onComplete?: () => void;
}

/**
* Handle used to manage the completion of the inspection.
*/
export interface InspectionCompleteHandle {
/**
* Callback called when the user has completed the inspection.
*/
handleInspectionCompleted: () => void;
}

/**
* Custom hook used to generate the callback called when the user has completed the inspection.
*/
export function useInspectionComplete({
startTasks,
sightState,
loading,
startTasksOnComplete,
onComplete,
}: InspectionCompleteParams): InspectionCompleteHandle {
const analytics = useAnalytics();
const monitoring = useMonitoring();
const { allowRedirect } = usePreventExit(sightState.sightsTaken.length !== 0);

const handleInspectionCompleted = useCallback(() => {
startTasks()
.then(() => {
analytics.trackEvent('Capture Completed');
analytics.setUserProperties({
captureCompleted: true,
sightSelected: 'inspection-completed',
});
allowRedirect();
onComplete?.();
sightState.setIsInspectionCompleted(true);
})
.catch((err) => {
loading.onError(err);
monitoring.handleError(err);
});
}, []);

useEffect(() => {
const { isInspectionCompliant, isInspectionCompleted } = sightState;
if (startTasksOnComplete && isInspectionCompliant && !isInspectionCompleted) {
handleInspectionCompleted();
}
}, [sightState.isInspectionCompliant]);

return useObjectMemo({ handleInspectionCompleted });
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ export const TTL_MS = 48 * 60 * 60 * 1000;

function isTTLExpired(): boolean {
const timestamp = localStorage.getItem(STORAGE_KEY_PHOTO_CAPTURE_GUIDELINES);
if (timestamp) {
console.log(Date.now() - parseInt(timestamp, 10) > TTL_MS);
}

return !timestamp || Date.now() - parseInt(timestamp, 10) > TTL_MS;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react';
import {
GetInspectionResponse,
MonkApiConfig,
Expand All @@ -9,6 +9,7 @@ import { useMonitoring } from '@monkvision/monitoring';
import {
getInspectionImages,
LoadingState,
MonkState,
uniq,
useAsyncEffect,
useMonkState,
Expand All @@ -21,6 +22,7 @@ import {
TaskName,
ImageStatus,
ProgressStatus,
PhotoCaptureAppConfig,
} from '@monkvision/types';
import { sights } from '@monkvision/sights';
import { useAnalytics } from '@monkvision/analytics';
Expand Down Expand Up @@ -70,12 +72,17 @@ export interface PhotoCaptureSightState {
* Callback used to manually update the completion state of the inspection.
*/
setIsInspectionCompleted: Dispatch<SetStateAction<boolean>>;
/**
* Boolean indicating if the inspection is compliant or not.
*/
isInspectionCompliant: boolean;
}

/**
* Parameters of the usePhotoCaptureSightState hook.
*/
export interface PhotoCaptureSightsParams {
export interface PhotoCaptureSightsParams
extends Pick<PhotoCaptureAppConfig, 'startTasksOnComplete'> {
/**
* The inspection ID.
*/
Expand Down Expand Up @@ -109,6 +116,10 @@ export interface PhotoCaptureSightsParams {
* sight will be used.
*/
tasksBySight?: Record<string, TaskName[]>;
/**
* Callback called when inspection capture is complete.
*/
onComplete?: () => void;
}

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

function getNotCompliantSights(
inspectionId: string,
captureSights: Sight[],
entities: MonkState,
notCompliantStatus = [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED],
) {
return captureSights
.map((s) => ({
sight: s,
image: getInspectionImages(inspectionId, entities.images, undefined, true).find(
(i) => i.inspectionId === inspectionId && i.sightId === s.id,
),
}))
.filter(({ image }) => !!image && notCompliantStatus.includes(image.status))
.map(({ sight }) => sight);
}

/**
* Custom hook used to manage the state of the PhotoCapture sights. This state is automatically fetched from the API at
* the start of the PhotoCapture process in order to allow users to start the inspection where they left it before.
Expand All @@ -187,6 +215,8 @@ export function usePhotoCaptureSightState({
tasksBySight,
setIsInitialInspectionFetched,
complianceOptions,
startTasksOnComplete,
onComplete,
}: PhotoCaptureSightsParams): PhotoCaptureSightState {
if (captureSights.length === 0) {
throw new Error('Empty sight list given to the Monk PhotoCapture component.');
Expand Down Expand Up @@ -223,19 +253,7 @@ export function usePhotoCaptureSightState({
setSelectedSight(notCapturedSights[0]);
} else {
onLastSightTaken();
const notCompliantSights = captureSights
.map((s) => ({
sight: s,
image: getInspectionImages(inspectionId, state.images, undefined, true).find(
(i) => i.inspectionId === inspectionId && i.sightId === s.id,
),
}))
.filter(
({ image }) =>
!!image &&
[ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED].includes(image.status),
)
.map(({ sight }) => sight);
const notCompliantSights = getNotCompliantSights(inspectionId, captureSights, state);
const sightToRetake =
notCompliantSights.length > 0
? notCompliantSights[0]
Expand Down Expand Up @@ -281,6 +299,16 @@ export function usePhotoCaptureSightState({
setRetryCount((value) => value + 1);
}, []);

const isInspectionCompliant = useMemo(() => {
const notCapturedSights = captureSights.filter((s) => !sightsTaken.includes(s));
const notCompliantSights = getNotCompliantSights(inspectionId, captureSights, state, [
ImageStatus.UPLOADING,
ImageStatus.NOT_COMPLIANT,
ImageStatus.UPLOAD_FAILED,
]);
return !notCapturedSights.length && !notCompliantSights.length;
}, [state.images]);

const takeSelectedSight = useCallback(() => {
const isRetake = sightsTaken.includes(selectedSight);
const updatedSightsTaken = isRetake ? sightsTaken : [...sightsTaken, selectedSight];
Expand All @@ -302,7 +330,7 @@ export function usePhotoCaptureSightState({
});
if (nextSight) {
setSelectedSight(nextSight);
} else {
} else if (!startTasksOnComplete || !onComplete) {
onLastSightTaken();
}
}, [sightsTaken, selectedSight, captureSights, onLastSightTaken]);
Expand Down Expand Up @@ -341,5 +369,6 @@ export function usePhotoCaptureSightState({
setLastPictureTakenUri,
retryLoadingInspection,
retakeSight,
isInspectionCompliant,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Styles } from '@monkvision/types';

export const styles: Styles = {
overlay: {
position: 'absolute',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ function getTasksToStart({
}
});
}
return tasks.filter((task) => !TASKS_NOT_TO_START.includes(task));
return tasks.filter(
(task) =>
!TASKS_NOT_TO_START.includes(task) &&
!(task === TaskName.COMPLIANCES && tasks.includes(TaskName.DAMAGE_DETECTION)),
);
}

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

loading.start();
try {
loading.start();
await startInspectionTasks({ inspectionId, names });
loading.onSuccess();
} catch (err) {
Expand Down
Loading