diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index ffa8e36b1..3f4015dc5 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -46,6 +46,7 @@ The following table lists the available configuration options in the `CaptureApp | allowImageUpscaling | `boolean` | Allow images to be scaled up if the device does not support the specified resolution in the `resolution` prop. | | `false` | | format | `CompressionFormat` | The output format of the compression. | | `CompressionFormat.JPEG` | | quality | `number` | Value indicating image quality for the compression output. | | `0.6` | +| addDamage | `AddDamage` | Options for Add Damage, If disabled, the `Add Damage` button will be hidden. | | `AddDamage.PART_SELECT` | | allowSkipRetake | `boolean` | If compliance is enabled, this prop indicate if the user is allowed to skip the retaking process if some pictures are not compliant. | | `false` | | enableCompliance | `boolean` | Indicates if compliance checks should be enabled or not. | | `true` | | enableCompliancePerSight | `string[]` | Array of Sight IDs that indicates for which sight IDs the compliance should be enabled. | | | diff --git a/packages/common-ui-web/README.md b/packages/common-ui-web/README.md index 6382fc62d..dcf57b644 100644 --- a/packages/common-ui-web/README.md +++ b/packages/common-ui-web/README.md @@ -134,6 +134,37 @@ function App() { --- +## CaptureSelection +### Description +A single page component that allows the user to select between "Add Damage" or "Photo Capture" workflow. + +### Example + +```tsx +import { CaptureSelection } from "@monkvision/common-ui-web"; +import { useNavigate } from "react-router-dom"; + +function App() { + const { navigate } = useNavigate(); + + return ( + navigate('/add-damage-page')} + onCapture={() => navigate('/photo-capture-page')} + /> + ); +} +``` + +### Props +| Prop | Type | Description | Required | Default Value | +|-------------|------------|----------------------------------------------------------------|----------|---------------| +| lang | string | The language used by the component. | | `'en'` | +| onAddDamage | () => void | Callback called when the user clicks on "Add Damage" button. | | | +| onCapture | () => void | Callback called when the user clicks on "Take Picture" button. | | | + +--- + ## Checkbox ### Description Custom component implementing a simple checkbox. diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx index eabec901b..b040d210f 100644 --- a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx @@ -1,36 +1,8 @@ -import { PartSelectionOrientation, VehiclePart, VehicleType } from '@monkvision/types'; -import { SVGProps } from 'react'; +import { PartSelectionOrientation } from '@monkvision/types'; import { DynamicSVG } from '../DynamicSVG'; -import { useVehicleDynamicWireframe } from './hooks'; +import { useVehicleDynamicWireframe, VehicleDynamicWireframeProps } from './hooks'; import { styles } from './VehicleDynamicWireframe.style'; -/** - * Props accepted by the VehicleDynamicWireframe component. - */ -export interface VehicleDynamicWireframeProps { - /** - * Vehicle type to display the wireframe for. - */ - vehicleType: VehicleType; - /** - * The orientation of the wireframe. - * - * @default PartSelectionOrientation.FRONT_LEFT - */ - orientation?: PartSelectionOrientation; - /** - * Callback when the user clicks a part. - */ - onClickPart?: (parts: VehiclePart) => void; - /** - * Callback used to customize the display style of each vehicle part on the wireframe. - * See `DynamicSVGCustomizationFunctions` for more details. - * - * @see DynamicSVGCustomizationFunctions - */ - getPartAttributes?: (part: VehiclePart) => SVGProps; -} - /** * Component that displays a dynamic wireframe of a vehicle, allowing the user to select parts of the vehicle. */ diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts b/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts index 699c27c7e..52c94e7a9 100644 --- a/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts @@ -8,7 +8,7 @@ function isCarPartElement(element: SVGElement): boolean { return element.id !== '' && element.classList.contains('car-part'); } -function getWireframes(vehicleType: VehicleType, orientation: PartSelectionOrientation) { +function getWireframes(vehicleType: VehicleType, orientation: PartSelectionOrientation): string { const wireframes = partSelectionWireframes[getVehicleModel(vehicleType)]; if (wireframes === undefined) { throw new Error(`No wireframe found for vehicle type ${vehicleType}`); @@ -16,9 +16,12 @@ function getWireframes(vehicleType: VehicleType, orientation: PartSelectionOrien return wireframes[orientation]; } -export interface VehicleDynamicWireframeParams { +/** + * Props accepted by the VehicleDynamicWireframe component. + */ +export interface VehicleDynamicWireframeProps { /** - * Vehicle type to display the wireframe for. + * The type of vehicle for which the wireframe will be displayed. */ vehicleType: VehicleType; /** @@ -28,11 +31,11 @@ export interface VehicleDynamicWireframeParams { */ orientation?: PartSelectionOrientation; /** - * Callback when the user clicks a part. + * Callback when the user clicks on a vehicle part. */ onClickPart?: (parts: VehiclePart) => void; /** - * Callback used to customize the display style of each vehicle part on the wireframe. + * Customizes the display attributes (e.g., styles, colors) of vehicle parts. * See `DynamicSVGCustomizationFunctions` for more details. * * @see DynamicSVGCustomizationFunctions @@ -45,7 +48,7 @@ export function useVehicleDynamicWireframe({ orientation = PartSelectionOrientation.FRONT_LEFT, onClickPart = () => {}, getPartAttributes = () => ({}), -}: VehicleDynamicWireframeParams) { +}: VehicleDynamicWireframeProps) { const overlay = useMemo( () => getWireframes(vehicleType, orientation), [vehicleType, orientation], diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/index.ts b/packages/common-ui-web/src/components/VehicleDynamicWireframe/index.ts index d3120c924..4e01db079 100644 --- a/packages/common-ui-web/src/components/VehicleDynamicWireframe/index.ts +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/index.ts @@ -1,4 +1,2 @@ -export { - VehicleDynamicWireframe, - type VehicleDynamicWireframeProps, -} from './VehicleDynamicWireframe'; +export { VehicleDynamicWireframe } from './VehicleDynamicWireframe'; +export { type VehicleDynamicWireframeProps } from './hooks'; diff --git a/packages/common/README.md b/packages/common/README.md index 69ac04519..6eb290935 100644 --- a/packages/common/README.md +++ b/packages/common/README.md @@ -18,6 +18,6 @@ you can refer to their own README directly : - [State Management](README/STATE_MANAGEMENT.md). - [Theming](README/THEMING.md). - [Internationalization](README/INTERNATIONALIZATION.md). -- [Hooks](README/APP_UTILS). +- [Hooks](README/APP_UTILS.md). - [Utilities](README/UTILITIES.md). -- [Application Utilities](README/HOOKS). +- [Application Utilities](README/HOOKS.md). diff --git a/packages/common/README/UTILITIES.md b/packages/common/README/UTILITIES.md index 3d0effa52..067070cd7 100644 --- a/packages/common/README/UTILITIES.md +++ b/packages/common/README/UTILITIES.md @@ -45,6 +45,29 @@ method, available on all versions of JavaScript. --- +# Browser Utils +### isMobileDevice + +```typescript +import { isMobileDevice } from "@monkvision/common"; + +console.log(isMobileDevice()); +// Output : true or false +``` +Checks if the current device is a mobile device. + +### getAspectRatio +```typescript +import { getAspectRatio } from "@monkvision/common"; + +const streamDimensions = {width: 1920, height: 1080} +console.log(getAspectRatio(streamDimensions)); +// Output : '16/9' +``` +Returns the aspect ratio of the stream. If not a mobile device, it will return 16/9 by default. + +--- + # Color Utils ### getRGBAFromString ```typescript @@ -286,6 +309,19 @@ Converts a string to camel case. --- +# Vehicle +### getVehicleModel +```typescript +import { getVehicleModel } from '@monkvision/common' +import { VehicleType } from '@monkvision/types' + +console.log(getVehicleModel(VehicleType.SUV)) +output : 'fesc20' +``` +Returns the vehicle model corresponding to the given vehicle type. + +--- + # Zlib Utils ### zlibCompress ```typescript diff --git a/packages/common/src/utils/browser.utils.ts b/packages/common/src/utils/browser.utils.ts index 3a6f8b945..09191512f 100644 --- a/packages/common/src/utils/browser.utils.ts +++ b/packages/common/src/utils/browser.utils.ts @@ -15,9 +15,9 @@ export function isMobileDevice(): boolean { } /** - * Returns the aspect ratio of the stream. + * Returns the aspect ratio of the stream. If not a mobile device, it will return 16/9 by default. */ -export function getAspectRatio(streamDimensions?: PixelDimensions | null) { +export function getAspectRatio(streamDimensions?: PixelDimensions | null): string { if (isMobileDevice() && streamDimensions) { return `${streamDimensions?.width}/${streamDimensions?.height}`; } diff --git a/packages/inspection-capture-web/README.md b/packages/inspection-capture-web/README.md index 198bc4f34..583f022cc 100644 --- a/packages/inspection-capture-web/README.md +++ b/packages/inspection-capture-web/README.md @@ -5,7 +5,9 @@ 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. +- The **DamageDisclosure** workflow : The user is guided to capture close-up pictures of specific damaged parts of the vehicle. There are 2 workflows available: + - Part-selection: Before taking the picture, the user must first select the damaged part on the vehicle wireframe then a close-up picture of the damage. + - Two-shot: The user is asked to take, first a wide picture of the vehicle, then a close-up picture of the damage. # Installing To install the package, you can run the following command : @@ -99,16 +101,17 @@ export function MonkPhotoCapturePage({ authToken }) { # 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. +The DamageDisclosure workflow is designed to guide users in documenting and disclosing damage to their vehicles during a Monk 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. +There are 2 workflows available. 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. +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. -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. +You can then pass the inspection ID, API configuration (including the auth token). 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: diff --git a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx index fb942512e..9bb41d703 100644 --- a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx +++ b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx @@ -147,7 +147,7 @@ export function DamageDisclosure({ }); const images = usePhotoCaptureImages(inspectionId); const handlePictureTaken = usePictureTaken({ - sightState: disclosureState, + captureState: disclosureState, addDamageHandle, uploadQueue, onPictureTaken, diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx index edeb491a4..bef6024f0 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx @@ -222,7 +222,7 @@ export function PhotoCapture({ }); const images = usePhotoCaptureImages(inspectionId); const handlePictureTaken = usePictureTaken({ - sightState, + captureState: sightState, addDamageHandle, uploadQueue, tasksBySight, diff --git a/packages/inspection-capture-web/src/hooks/usePictureTaken.ts b/packages/inspection-capture-web/src/hooks/usePictureTaken.ts index ba4a3b2b5..3b264e04d 100644 --- a/packages/inspection-capture-web/src/hooks/usePictureTaken.ts +++ b/packages/inspection-capture-web/src/hooks/usePictureTaken.ts @@ -11,9 +11,9 @@ import { PhotoCaptureSightState, DamageDisclosureState, CaptureMode } from '../t */ export interface UseTakePictureParams { /** - * The PhotoCapture sight state, created using the usePhotoCaptureSightState hook. + * The capture state, created using the usePhotoCaptureSightState or useDamageDisclosureState hook. */ - sightState: PhotoCaptureSightState | DamageDisclosureState; + captureState: PhotoCaptureSightState | DamageDisclosureState; /** * The PhotoCapture add damage handle, created using the useAddDamageMode hook. */ @@ -42,7 +42,7 @@ export type HandleTakePictureFunction = (picture: MonkPicture) => void; * Custom hook used to generate the callback called when the user has taken a picture to handle picture upload etc. */ export function usePictureTaken({ - sightState, + captureState, addDamageHandle, uploadQueue, tasksBySight, @@ -50,15 +50,16 @@ export function usePictureTaken({ }: UseTakePictureParams): HandleTakePictureFunction { const { trackEvent } = useAnalytics(); - const selectedSightId = 'selectedSight' in sightState ? sightState.selectedSight.id : undefined; + const selectedSightId = + 'selectedSight' in captureState ? captureState.selectedSight.id : undefined; const takeSelectedSight = - 'takeSelectedSight' in sightState ? sightState.takeSelectedSight : undefined; + 'takeSelectedSight' in captureState ? captureState.takeSelectedSight : undefined; return useCallback( (picture: MonkPicture) => { onPictureTaken?.(picture); - sightState.setLastPictureTakenUri(picture.uri); + captureState.setLastPictureTakenUri(picture.uri); if (addDamageHandle.mode === CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT) { uploadQueue.push({ mode: addDamageHandle.mode, @@ -66,12 +67,12 @@ export function usePictureTaken({ vehicleParts: addDamageHandle.vehicleParts, }); } - if (addDamageHandle.mode === CaptureMode.SIGHT && 'selectedSight' in sightState) { + if (addDamageHandle.mode === CaptureMode.SIGHT && 'selectedSight' in captureState) { uploadQueue.push({ mode: addDamageHandle.mode, picture, - sightId: sightState.selectedSight.id, - tasks: tasksBySight?.[sightState.selectedSight.id] ?? sightState.selectedSight.tasks, + sightId: captureState.selectedSight.id, + tasks: tasksBySight?.[captureState.selectedSight.id] ?? captureState.selectedSight.tasks, }); } if ( @@ -80,8 +81,8 @@ export function usePictureTaken({ ) { uploadQueue.push({ mode: addDamageHandle.mode, picture }); } - if (addDamageHandle.mode === CaptureMode.SIGHT && 'takeSelectedSight' in sightState) { - sightState.takeSelectedSight(); + if (addDamageHandle.mode === CaptureMode.SIGHT && 'takeSelectedSight' in captureState) { + captureState.takeSelectedSight(); } else { trackEvent('AddDamage Captured', { mode: addDamageHandle.mode, @@ -90,7 +91,7 @@ export function usePictureTaken({ addDamageHandle.updatePhotoCaptureModeAfterPictureTaken(); }, [ - sightState.setLastPictureTakenUri, + captureState.setLastPictureTakenUri, addDamageHandle.mode, selectedSightId, tasksBySight, diff --git a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx index f824a122d..46ec966cd 100644 --- a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx +++ b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx @@ -200,12 +200,12 @@ describe('DamageDisclosure component', () => { expect(useAddDamageMode).toHaveBeenCalled(); const addDamageHandle = (useAddDamageMode as jest.Mock).mock.results[0].value; expect(useDamageDisclosureState).toHaveBeenCalled(); - const sightState = (useDamageDisclosureState as jest.Mock).mock.results[0].value; + const captureState = (useDamageDisclosureState as jest.Mock).mock.results[0].value; expect(useUploadQueue).toHaveBeenCalled(); const uploadQueue = (useUploadQueue as jest.Mock).mock.results[0].value; expect(usePictureTaken).toHaveBeenCalledWith({ addDamageHandle, - sightState, + captureState, uploadQueue, onPictureTaken: props.onPictureTaken, }); diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx index 77241ca7f..6db190558 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx @@ -267,12 +267,12 @@ describe('PhotoCapture component', () => { expect(useAddDamageMode).toHaveBeenCalled(); const addDamageHandle = (useAddDamageMode as jest.Mock).mock.results[0].value; expect(usePhotoCaptureSightState).toHaveBeenCalled(); - const sightState = (usePhotoCaptureSightState as jest.Mock).mock.results[0].value; + const captureState = (usePhotoCaptureSightState as jest.Mock).mock.results[0].value; expect(useUploadQueue).toHaveBeenCalled(); const uploadQueue = (useUploadQueue as jest.Mock).mock.results[0].value; expect(usePictureTaken).toHaveBeenCalledWith({ addDamageHandle, - sightState, + captureState, uploadQueue, tasksBySight: props.tasksBySight, onPictureTaken: props.onPictureTaken, diff --git a/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts b/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts index b6b2f7b30..df36e59a7 100644 --- a/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts +++ b/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts @@ -6,7 +6,7 @@ import { PhotoCaptureSightState } from '../../src/PhotoCapture/hooks'; function createParams(): UseTakePictureParams { return { - sightState: { + captureState: { setLastPictureTakenUri: jest.fn(), takeSelectedSight: jest.fn(), selectedSight: { id: 'test-selected-sight', tasks: [TaskName.WHEEL_ANALYSIS] }, @@ -41,10 +41,10 @@ describe('usePictureTaken hook', () => { const initialProps = createParams(); const { result, unmount } = renderHook(usePictureTaken, { initialProps }); - expect(initialProps.sightState.setLastPictureTakenUri).not.toHaveBeenCalled(); + expect(initialProps.captureState.setLastPictureTakenUri).not.toHaveBeenCalled(); const picture = createMonkPicture(); result.current(picture); - expect(initialProps.sightState.setLastPictureTakenUri).toHaveBeenCalledWith(picture.uri); + expect(initialProps.captureState.setLastPictureTakenUri).toHaveBeenCalledWith(picture.uri); unmount(); }); @@ -59,8 +59,8 @@ describe('usePictureTaken hook', () => { expect(initialProps.uploadQueue.push).toHaveBeenCalledWith({ mode: initialProps.addDamageHandle.mode, picture, - sightId: (initialProps.sightState as PhotoCaptureSightState).selectedSight.id, - tasks: (initialProps.sightState as PhotoCaptureSightState).selectedSight.tasks, + sightId: (initialProps.captureState as PhotoCaptureSightState).selectedSight.id, + tasks: (initialProps.captureState as PhotoCaptureSightState).selectedSight.tasks, }); unmount(); @@ -71,7 +71,7 @@ describe('usePictureTaken hook', () => { const initialProps = { ...createParams(), tasksBySight: { - [(createParams().sightState as PhotoCaptureSightState).selectedSight.id]: tasks, + [(createParams().captureState as PhotoCaptureSightState).selectedSight.id]: tasks, }, }; const { result, unmount } = renderHook(usePictureTaken, { initialProps }); @@ -111,11 +111,11 @@ describe('usePictureTaken hook', () => { const { result, unmount } = renderHook(usePictureTaken, { initialProps }); expect( - (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + (initialProps.captureState as PhotoCaptureSightState).takeSelectedSight, ).not.toHaveBeenCalled(); result.current(createMonkPicture()); expect( - (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + (initialProps.captureState as PhotoCaptureSightState).takeSelectedSight, ).toHaveBeenCalled(); unmount(); @@ -134,7 +134,7 @@ describe('usePictureTaken hook', () => { result.current(createMonkPicture()); expect( - (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + (initialProps.captureState as PhotoCaptureSightState).takeSelectedSight, ).not.toHaveBeenCalled(); unmount();