Skip to content

Commit

Permalink
Added 'DamageDisclosure' component
Browse files Browse the repository at this point in the history
  • Loading branch information
dlymonkai committed Jan 3, 2025
1 parent df13d49 commit 4f1928d
Show file tree
Hide file tree
Showing 17 changed files with 1,447 additions and 0 deletions.
53 changes: 53 additions & 0 deletions packages/inspection-capture-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ There are two main workflows for capturing pictures of a vehicle for a Monk insp
to take pictures of the vehicle by aligning the vehicle with the Sight overlays.
- The **VideoCapture** workflow : the user is asked to record a quick video of their vehicle by filming it and rotating
in a full circle around it.
- The **DamageDisclosure** workflow : The user is guided to capture close-up pictures of specific damaged parts of the vehicle. Before taking the picture, the user must first select the damaged part on the vehicle wireframe.

# Installing
To install the package, you can run the following command :
Expand Down Expand Up @@ -96,3 +97,55 @@ export function MonkPhotoCapturePage({ authToken }) {
| thumbnailDomain | `string` | The API domain used to communicate with the resize micro service. | ✔️ | |


# DamageDisclosure

The DamageDisclosure workflow is designed to guide users in documenting and disclosing damage to their vehicles during a Monk inspection. Once the damaged areas are identified, the user is prompted to take close-up photos of each selected area, ensuring accurate documentation for the inspection.
This workflow is ideal for capturing detailed images of specific damages such as dents, scratches, or other issues that need to be highlighted in the inspection report.

Please refer to the [official MonkJs documentation](https://monkvision.github.io/monkjs/docs/photo-capture-workflow) for a comprehensive overview of the Add damage workflow.

## DamageDisclosure component

This package exports a ready-to-use single-page component called DamageDisclosure that implements the DamageDisclosure workflow. You can integrate it into your application by creating a new page containing only this component. Before using it, you must generate an Auth0 authentication token and create a new inspection using the Monk API. Ensure that all task statuses in the inspection are set to NOT_STARTED. This component will automatically handle starting tasks after the capture process is complete.

You can then pass the inspection ID, API configuration (including the auth token), and a list of sights to be displayed to the user. Once the user completes the workflow, the onComplete callback is triggered, allowing you to navigate to another page or perform additional actions.

The following example demonstrates how to use the DamageDisclosure component:

```tsx
import { DamageDisclosure } from '@monkvision/inspection-capture-web';

const apiDomain = 'api.preview.monk.ai/v1';

export function MonkDamageDisclosurePage({ authToken }) {
return (
<DamageDisclosure
inspectionId={inspectionId}
apiConfig={{ apiDomain, authToken }}
onComplete={() => { /* Navigate to another page */ }}
/>
);
}
```

Props

| Prop | Type | Description | Required | Default Value |
|------------------------------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------------------------|
| inspectionId | string | The ID of the inspection to add images to. Make sure that the user that created the inspection if the same one as the one described in the auth token in the `apiConfig` prop. | ✔️ | |
| apiConfig | ApiConfig | The api config used to communicate with the API. Make sure that the user described in the auth token is the same one as the one that created the inspection provided in the `inspectionId` prop. | ✔️ | |
| onClose | `() => void` | Callback called when the user clicks on the Close button. If this callback is not provided, the button will not be displayed on the screen. | | |
| onComplete | `() => void` | Callback called when inspection capture is complete. | | |
| onPictureTaken | `(picture: MonkPicture) => void` | Callback called when the user has taken a picture in the Capture process. | | |
| lang | <code>string &#124; null</code> | The language to be used by this component. | | `'en'` |
| enforceOrientation | `DeviceOrientation` | Use this prop to enforce a specific device orientation for the Camera screen. | | |
| maxUploadDurationWarning | `number` | Max upload duration in milliseconds before showing a bad connection warning to the user. Use `-1` to never display the warning. | | `15000` |
| useAdaptiveImageQuality | `boolean` | Boolean indicating if the image quality should be downgraded automatically in case of low connection. | | `true` |
| showCloseButton | `boolean` | Indicates if the close button should be displayed in the HUD on top of the Camera preview. | | `false` |
| format | `CompressionFormat` | The output format of the compression. | | `CompressionFormat.JPEG` |
| quality | `number` | Value indicating image quality for the compression output. | | `0.6` |
| resolution | `CameraResolution` | Indicates the resolution of the pictures taken by the Camera. | | `CameraResolution.UHD_4K` |
| allowImageUpscaling | `boolean` | Allow images to be scaled up if the device does not support the specified resolution in the `resolution` prop. | | `false` |
| useLiveCompliance | `boolean` | Indicates if live compliance should be enabled or not. | | `false` |
| validateButtonLabel | `string` | Custom label for validate button in gallery view. | | |
| thumbnailDomain | `string` | The API domain used to communicate with the resize micro service. | ✔️ | |
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Styles } from '@monkvision/types';

export const styles: Styles = {
container: {
height: '100%',
width: '100%',
},
orientationErrorContainer: {
height: '100%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
boxSizing: 'border-box',
padding: '50px 10%',
},
orientationErrorTitleContainer: {
display: 'flex',
alignItems: 'center',
},
orientationErrorTitle: {
fontSize: 18,
marginLeft: 16,
},
orientationErrorDescription: {
fontSize: 16,
paddingTop: 16,
opacity: 0.8,
textAlign: 'center',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { useAnalytics } from '@monkvision/analytics';
import { Camera, CameraHUDProps, CameraProps } from '@monkvision/camera-web';
import {
useI18nSync,
useLoadingState,
useObjectMemo,
useWindowDimensions,
} from '@monkvision/common';
import { BackdropDialog, Icon, InspectionGallery } from '@monkvision/common-ui-web';
import { MonkApiConfig } from '@monkvision/network';
import {
AddDamage,
CameraConfig,
CaptureAppConfig,
ComplianceOptions,
CompressionOptions,
DeviceOrientation,
MonkPicture,
} from '@monkvision/types';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { styles } from './DamageDisclosure.styles';
import { DamageDisclosureHUD, DamageDisclosureHUDProps } from './DamageDisclosureHUD';
import { useDamageDisclosureState } from './hooks';
import {
useAdaptiveCameraConfig,
useAddDamageMode,
usePhotoCaptureImages,
usePictureTaken,
useUploadQueue,
useBadConnectionWarning,
useTracking,
} from '../hooks';
import { CaptureScreen } from '../types';

/**
* Props of the DamageDisclosure component.
*/
export interface DamageDisclosureProps
extends Pick<CameraProps<DamageDisclosureHUDProps>, 'resolution' | 'allowImageUpscaling'>,
Pick<
CaptureAppConfig,
| keyof CameraConfig
| 'maxUploadDurationWarning'
| 'useAdaptiveImageQuality'
| 'showCloseButton'
| 'enforceOrientation'
| 'addDamage'
>,
Partial<CompressionOptions>,
Partial<ComplianceOptions> {
/**
* The ID of the inspection to add images to. Make sure that the user that created the inspection if the same one as
* the one described in the auth token in the `apiConfig` prop.
*/
inspectionId: string;
/**
* The api config used to communicate with the API. Make sure that the user described in the auth token is the same
* one as the one that created the inspection provided in the `inspectionId` prop.
*/
apiConfig: MonkApiConfig;
/**
* Callback called when the user clicks on the Close button. If this callback is not provided, the button will not be
* displayed on the screen.
*/
onClose?: () => void;
/**
* Callback called when inspection capture is complete.
*/
onComplete?: () => void;
/**
* Callback called when a picture has been taken by the user.
*/
onPictureTaken?: (picture: MonkPicture) => void;
/**
* The language to be used by this component.
*
* @default en
*/
lang?: string | null;
}

// No ts-doc for this component : the component exported is DamageDisclosureHOC
export function DamageDisclosure({
inspectionId,
apiConfig,
onClose,
onComplete,
onPictureTaken,
useLiveCompliance = false,
maxUploadDurationWarning = 15000,
showCloseButton = false,
addDamage = AddDamage.PART_SELECT,
useAdaptiveImageQuality = true,
lang,
enforceOrientation,
...initialCameraConfig
}: DamageDisclosureProps) {
useI18nSync(lang);
const complianceOptions: ComplianceOptions = useObjectMemo({
useLiveCompliance,
});
const { t } = useTranslation();
const [currentScreen, setCurrentScreen] = useState(CaptureScreen.CAMERA);
const dimensions = useWindowDimensions();
const analytics = useAnalytics();
const loading = useLoadingState();
const handleOpenGallery = () => {
setCurrentScreen(CaptureScreen.GALLERY);
analytics.trackEvent('Gallery Opened');
};
const addDamageHandle = useAddDamageMode({
addDamage,
currentScreen,
damageDisclosure: true,
handleOpenGallery,
});
const disclosureState = useDamageDisclosureState({
inspectionId,
apiConfig,
loading,
complianceOptions,
});
useTracking({ inspectionId, authToken: apiConfig.authToken });
const { adaptiveCameraConfig, uploadEventHandlers: adaptiveUploadEventHandlers } =
useAdaptiveCameraConfig({
initialCameraConfig,
useAdaptiveImageQuality,
});
const {
isBadConnectionWarningDialogDisplayed,
closeBadConnectionWarningDialog,
uploadEventHandlers: badConnectionWarningUploadEventHandlers,
} = useBadConnectionWarning({ maxUploadDurationWarning });
const uploadQueue = useUploadQueue({
inspectionId,
apiConfig,
complianceOptions,
eventHandlers: [adaptiveUploadEventHandlers, badConnectionWarningUploadEventHandlers],
});
const images = usePhotoCaptureImages(inspectionId);
const handlePictureTaken = usePictureTaken({
sightState: disclosureState,
addDamageHandle,
uploadQueue,
onPictureTaken,
});
const handleGalleryBack = () => {
setCurrentScreen(CaptureScreen.CAMERA);
};
const isViolatingEnforcedOrientation =
enforceOrientation &&
(enforceOrientation === DeviceOrientation.PORTRAIT) !== dimensions.isPortrait;
const hudProps: Omit<DamageDisclosureHUDProps, keyof CameraHUDProps> = {
inspectionId,
mode: addDamageHandle.mode,
vehicleParts: addDamageHandle.vehicleParts,
lastPictureTakenUri: disclosureState.lastPictureTakenUri,
onOpenGallery: handleOpenGallery,
onAddDamage: addDamageHandle.handleAddDamage,
onAddDamagePartsSelected: addDamageHandle.handleAddDamagePartsSelected,
onCancelAddDamage: addDamageHandle.handleCancelAddDamage,
onRetry: disclosureState.retryLoadingInspection,
loading,
onClose,
showCloseButton,
images,
addDamage,
onValidateVehicleParts: addDamageHandle.handleValidateVehicleParts,
};

return (
<div style={styles['container']}>
{currentScreen === CaptureScreen.CAMERA && isViolatingEnforcedOrientation && (
<div style={styles['orientationErrorContainer']}>
<div style={styles['orientationErrorTitleContainer']}>
<Icon icon='rotate' primaryColor='text-primary' size={30} />
<div style={styles['orientationErrorTitle']}>{t('photo.orientationError.title')}</div>
</div>
<div style={styles['orientationErrorDescription']}>
{t('photo.orientationError.description')}
</div>
</div>
)}
{currentScreen === CaptureScreen.CAMERA && !isViolatingEnforcedOrientation && (
<Camera
HUDComponent={DamageDisclosureHUD}
onPictureTaken={handlePictureTaken}
hudProps={hudProps}
{...adaptiveCameraConfig}
/>
)}
{currentScreen === CaptureScreen.GALLERY && (
<InspectionGallery
inspectionId={inspectionId}
sights={[]}
apiConfig={apiConfig}
captureMode={true}
lang={lang}
showBackButton={true}
onBack={handleGalleryBack}
onNavigateToCapture={handleGalleryBack}
onValidate={onComplete}
addDamage={addDamage}
validateButtonLabel={t('photo.gallery.next')}
isInspectionCompleted={false}
disableSightPicture={true}
/>
)}
<BackdropDialog
show={isBadConnectionWarningDialogDisplayed}
showCancelButton={false}
dialogIcon='warning-outline'
dialogIconPrimaryColor='caution-base'
message={t('photo.badConnectionWarning.message')}
confirmLabel={t('photo.badConnectionWarning.confirm')}
onConfirm={closeBadConnectionWarningDialog}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { i18nWrap, MonkProvider } from '@monkvision/common';
import { i18nInspectionCaptureWeb } from '../i18n';
import { DamageDisclosure, DamageDisclosureProps } from './DamageDisclosure';

/**
* The DamageDisclosure component is a ready-to-use, single page component that implements a Camera app, allowing users
* to capture photos of damaged parts of their vehicle for the purpose of disclosing damage. In order to use this
* component, you first need to generate an Auth0 authentication token, and create an inspection using the Monk Api.
* When creating the inspection, don't forget to set the tasks statuses to `NOT_STARTED`. This component will handle the
* starting of the tasks at the end of the capturing process. You can then pass the inspection ID, the api config (with
* the auth token), as well as the list of sights to be taken by the user to this component, and everything will be
* handled automatically for you.
*
* @example
* import { DamageDisclosure } from '@monkvision/inspection-capture-web';
*
* export function PhotoCaptureScreen({ inspectionId, apiConfig }: PhotoCaptureScreenProps) {
* const { i18n } = useTranslation();
*
* return (
* <DamageDisclosure
* inspectionId={inspectionId}
* apiConfig={apiConfig}
* compliances={{ iqa: true }}
* onComplete={() => { / * Navigate to another page * / }}
* lang={i18n.language}
* />
* );
* }
*/
export const DamageDisclosureHOC = i18nWrap(function DamageDisclosureHOC(
props: DamageDisclosureProps,
) {
return (
<MonkProvider>
<DamageDisclosure {...props} />
</MonkProvider>
);
},
i18nInspectionCaptureWeb);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Styles } from '@monkvision/types';

export const styles: Styles = {
container: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
position: 'relative',
alignSelf: 'stretch',
},
containerPortrait: {
__media: { portrait: true },
flexDirection: 'column',
},
previewContainer: {
position: 'relative',
width: '100%',
height: '100%',
},
};
Loading

0 comments on commit 4f1928d

Please sign in to comment.