From 6162f20d7d58c1fea1516b3964b6b5911cfd157f Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:03:15 +0100 Subject: [PATCH 01/12] Added new images post options: PART_SELECT && TWO_SHOT --- apps/demo-app/src/local-config.json | 2 +- .../test/pages/PhotoCapturePage.test.tsx | 4 +- documentation/docs/photo-capture-workflow.md | 2 +- packages/network/src/api/image/requests.ts | 92 ++++++++++++++++++- packages/network/src/api/models/image.ts | 1 + .../network/test/api/image/requests.test.ts | 77 +++++++++++++++- packages/types/src/config.ts | 31 +++++-- 7 files changed, 189 insertions(+), 20 deletions(-) diff --git a/apps/demo-app/src/local-config.json b/apps/demo-app/src/local-config.json index c3033a559..dd3cb92ed 100644 --- a/apps/demo-app/src/local-config.json +++ b/apps/demo-app/src/local-config.json @@ -3,7 +3,7 @@ "description": "Config for the local Demo App.", "workflow": "photo", "allowSkipRetake": true, - "enableAddDamage": true, + "addDamage": "part_select", "enableSightGuidelines": true, "allowVehicleTypeSelection": true, "allowManualLogin": true, diff --git a/apps/demo-app/test/pages/PhotoCapturePage.test.tsx b/apps/demo-app/test/pages/PhotoCapturePage.test.tsx index 187a62d64..82894cf9a 100644 --- a/apps/demo-app/test/pages/PhotoCapturePage.test.tsx +++ b/apps/demo-app/test/pages/PhotoCapturePage.test.tsx @@ -20,7 +20,7 @@ const appState = { enforceOrientation: 'test-enforceOrientation-test', maxUploadDurationWarning: 'test-maxUploadDurationWarning-test', allowSkipRetake: 'test-allowSkipRetake-test', - enableAddDamage: 'test-enableAddDamage-test', + addDamage: 'test-addDamage-test', enableCompliance: 'test-enableCompliance-test', enableCompliancePerSight: 'test-enableCompliancePerSight-test', complianceIssues: 'test-complianceIssues-test', @@ -53,7 +53,7 @@ describe('PhotoCapture page', () => { enforceOrientation: appState.config.enforceOrientation, maxUploadDurationWarning: appState.config.maxUploadDurationWarning, allowSkipRetake: appState.config.allowSkipRetake, - enableAddDamage: appState.config.enableAddDamage, + addDamage: appState.config.addDamage, enableCompliance: appState.config.enableCompliance, enableCompliancePerSight: appState.config.enableCompliancePerSight, complianceIssues: appState.config.complianceIssues, diff --git a/documentation/docs/photo-capture-workflow.md b/documentation/docs/photo-capture-workflow.md index 5490e6453..682be0f81 100644 --- a/documentation/docs/photo-capture-workflow.md +++ b/documentation/docs/photo-capture-workflow.md @@ -58,7 +58,7 @@ increase the detection rate. This feature is called `Add Damage`, and there two take a close-up picture of the damage. For now, only the 2-shot workflow is implemented in the PhotoCapture workflow. This feature is enabled by default in the -`PhotoCapture` component. To disable it, pass the `enableAddDamage` prop to `false`. +`PhotoCapture` component. To disable it, pass the `addDamage` prop to `AddDamage.DISABLED`. ## Using Compliance The compliance is a feature that allows our AI models to analyze the quality of the pictures taken by the user, and if diff --git a/packages/network/src/api/image/requests.ts b/packages/network/src/api/image/requests.ts index ea2c4d3f8..2cdc907c8 100644 --- a/packages/network/src/api/image/requests.ts +++ b/packages/network/src/api/image/requests.ts @@ -1,6 +1,11 @@ import ky from 'ky'; import { Dispatch } from 'react'; -import { getFileExtensions, MonkActionType, MonkCreatedOneImageAction } from '@monkvision/common'; +import { + getFileExtensions, + MonkActionType, + MonkCreatedOneImageAction, + vehiclePartLabels, +} from '@monkvision/common'; import { ComplianceOptions, Image, @@ -12,11 +17,12 @@ import { MonkPicture, TaskName, TranslationObject, + VehiclePart, } from '@monkvision/types'; import { v4 } from 'uuid'; import { labels, sights } from '@monkvision/sights'; import { getDefaultOptions, MonkApiConfig } from '../config'; -import { ApiImage, ApiImagePost, ApiImagePostTask } from '../models'; +import { ApiCenterOnElement, ApiImage, ApiImagePost, ApiImagePostTask } from '../models'; import { MonkApiResponse } from '../types'; import { mapApiImage } from './mappers'; @@ -33,6 +39,11 @@ export enum ImageUploadType { * add damage workflow. */ CLOSE_UP_2_SHOT = 'close_up_2_shot', + /** + * Upload type corresponding to a close-up picture (add-damage) in the PhotoCapture process, when using the + * part-select-shot add damage workflow. + */ + PART_SELECT_SHOT = 'part_select_shot', /** * Upload type corresponding to a video frame in the VideoCapture process. */ @@ -114,6 +125,36 @@ export interface Add2ShotCloseUpImageOptions { compliance?: ComplianceOptions; } +/** + * Options specified when adding a close up (an "add damage" image) to an inspection using the part select process. + */ +export interface AddPartSelectCloseUpImageOptions { + /** + * The type of the image upload : `ImageUploadType.PART_SELECT_SHOT`; + */ + uploadType: ImageUploadType.PART_SELECT_SHOT; + /** + * The picture to add to the inspection. + */ + picture: MonkPicture; + /** + * The ID of the inspection to add the image to. + */ + inspectionId: string; + /** + * List of damage parts chosen by User with part selected wireframe + */ + vehicleParts: VehiclePart[]; + /** + * Boolean indicating if a thumbnail request will be sent when addImage is called. + */ + useThumbnailCaching?: boolean; + /** + * Additional options used to configure the compliance locally. + */ + compliance?: ComplianceOptions; +} + /** * Options specififed when adding a video frame to a VideoCapture inspection. */ @@ -166,6 +207,7 @@ export type AddImageOptions = | Add2ShotCloseUpImageOptions | AddVideoFrameOptions | AddVideoManualPhotoOptions; + | AddPartSelectCloseUpImageOptions interface AddImageData { filename: string; @@ -173,7 +215,9 @@ interface AddImageData { } function getImageType(options: AddImageOptions): ImageType { - if (options.uploadType === ImageUploadType.CLOSE_UP_2_SHOT) { + if ( + [ImageUploadType.CLOSE_UP_2_SHOT, ImageUploadType.PART_SELECT_SHOT].includes(options.uploadType) + ) { return ImageType.CLOSE_UP; } return ImageType.BEAUTY_SHOT; @@ -197,6 +241,13 @@ function getImageLabel(options: AddImageOptions): TranslationObject | undefined fr: `Photo Manuelle Vidéo`, de: `Foto Manuell Video`, nl: `Foto-handleiding Video`, + if (options.uploadType === ImageUploadType.PART_SELECT_SHOT) { + const partsTranslation = options.vehicleParts.map((part) => vehiclePartLabels[part]); + return { + en: `Close Up on ${partsTranslation.map((part) => part.en).join(', ')}`, + fr: `Photo Zoomée sur ${partsTranslation.map((part) => part.en).join(', ')}`, + de: `Gezoomtes an ${partsTranslation.map((part) => part.en).join(', ')}`, + nl: `Nabij aan ${partsTranslation.map((part) => part.en).join(', ')}`, }; } return { @@ -259,7 +310,7 @@ function createBeautyShotImageData( return { filename, body }; } -function createCloseUpImageData( +function create2ShotCloseUpImageData( options: Add2ShotCloseUpImageOptions, filetype: string, ): AddImageData { @@ -288,6 +339,35 @@ function createCloseUpImageData( return { filename, body }; } +function createPartSelectCloseUpImageData( + options: AddPartSelectCloseUpImageOptions, + filetype: string, +): AddImageData { + const filename = `part-select-${options.inspectionId}-${Date.now()}.${filetype}`; + + const body: ApiImagePost = { + acquisition: { + strategy: 'upload_multipart_form_keys', + file_key: MULTIPART_KEY_IMAGE, + }, + image_type: ImageType.CLOSE_UP, + tasks: [ + TaskName.DAMAGE_DETECTION, + { + name: TaskName.COMPLIANCES, + wait_for_result: + options.compliance?.enableCompliance && options.compliance?.useLiveCompliance, + }, + ], + detailed_viewpoint: { + centers_on: options.vehicleParts as ApiCenterOnElement[], + }, + additional_data: getAdditionalData(options), + }; + + return { filename, body }; +} + function createVideoFrameData(options: AddVideoFrameOptions, filetype: string): AddImageData { const filename = `video-frame-${options.frameIndex}.${filetype}`; @@ -326,7 +406,9 @@ function getAddImageData(options: AddImageOptions, filetype: string): AddImageDa case ImageUploadType.BEAUTY_SHOT: return createBeautyShotImageData(options, filetype); case ImageUploadType.CLOSE_UP_2_SHOT: - return createCloseUpImageData(options, filetype); + return create2ShotCloseUpImageData(options, filetype); + case ImageUploadType.PART_SELECT_SHOT: + return createPartSelectCloseUpImageData(options, filetype); case ImageUploadType.VIDEO_FRAME: return createVideoFrameData(options, filetype); case ImageUploadType.VIDEO_MANUAL_PHOTO: diff --git a/packages/network/src/api/models/image.ts b/packages/network/src/api/models/image.ts index 06da6b64f..00633e1f7 100644 --- a/packages/network/src/api/models/image.ts +++ b/packages/network/src/api/models/image.ts @@ -89,5 +89,6 @@ export interface ApiImagePost { image_subtype?: ApiImageSubType; image_sibling_key?: string; compliances?: ApiCompliance; + detailed_viewpoint?: ApiViewpointComponent; additional_data?: ApiImageAdditionalData; } diff --git a/packages/network/test/api/image/requests.test.ts b/packages/network/test/api/image/requests.test.ts index 00d853622..6ef809a16 100644 --- a/packages/network/test/api/image/requests.test.ts +++ b/packages/network/test/api/image/requests.test.ts @@ -11,13 +11,21 @@ jest.mock('../../../src/api/image/mappers', () => ({ import { labels, sights } from '@monkvision/sights'; import ky from 'ky'; -import { ComplianceIssue, ImageStatus, ImageSubtype, ImageType, TaskName } from '@monkvision/types'; -import { getFileExtensions, MonkActionType } from '@monkvision/common'; +import { + ComplianceIssue, + ImageStatus, + ImageSubtype, + ImageType, + TaskName, + VehiclePart, +} from '@monkvision/types'; +import { getFileExtensions, MonkActionType, vehiclePartLabels } from '@monkvision/common'; import { getDefaultOptions } from '../../../src/api/config'; import { Add2ShotCloseUpImageOptions, AddBeautyShotImageOptions, addImage, + AddPartSelectCloseUpImageOptions, AddVideoFrameOptions, AddVideoManualPhotoOptions, ImageUploadType, @@ -51,7 +59,7 @@ function createBeautyShotImageOptions(): AddBeautyShotImageOptions { }; } -function createCloseUpImageOptions(): Add2ShotCloseUpImageOptions { +function create2ShotCloseUpImageOptions(): Add2ShotCloseUpImageOptions { return { uploadType: ImageUploadType.CLOSE_UP_2_SHOT, picture: { @@ -72,6 +80,26 @@ function createCloseUpImageOptions(): Add2ShotCloseUpImageOptions { }; } +function createPartSelectCloseUpImageOptions(): AddPartSelectCloseUpImageOptions { + return { + uploadType: ImageUploadType.PART_SELECT_SHOT, + picture: { + blob: { size: 424 } as Blob, + uri: 'test-uri', + height: 720, + width: 1280, + mimetype: 'image/jpeg', + }, + inspectionId: 'test-inspection-id', + vehicleParts: [VehiclePart.HEAD_LIGHT_RIGHT], + compliance: { + enableCompliance: true, + complianceIssues: [ComplianceIssue.INTERIOR_NOT_SUPPORTED], + }, + useThumbnailCaching: true, + }; +} + function createVideoFrameOptions(): AddVideoFrameOptions { return { uploadType: ImageUploadType.VIDEO_FRAME, @@ -291,8 +319,8 @@ describe('Image requests', () => { ); }); - it('should properly create the formdata for a closeup', async () => { - const options = createCloseUpImageOptions(); + it('should properly create the formdata for a 2Shot closeup', async () => { + const options = create2ShotCloseUpImageOptions(); await addImage(options, apiConfig); expect(ky.post).toHaveBeenCalled(); @@ -329,6 +357,45 @@ describe('Image requests', () => { ); }); + it('should properly create the formdata for a part select closeup', async () => { + const options = createPartSelectCloseUpImageOptions(); + await addImage(options, apiConfig); + + expect(ky.post).toHaveBeenCalled(); + const formData = (ky.post as jest.Mock).mock.calls[0][1].body as FormData; + expect(typeof formData?.get('json')).toBe('string'); + + const partsTranslation = options.vehicleParts.map((part) => vehiclePartLabels[part]); + expect(JSON.parse(formData.get('json') as string)).toEqual({ + acquisition: { + strategy: 'upload_multipart_form_keys', + file_key: 'image', + }, + image_type: ImageType.CLOSE_UP, + tasks: [TaskName.DAMAGE_DETECTION, { name: TaskName.COMPLIANCES }], + additional_data: { + label: { + en: `Close Up on ${partsTranslation.map((part) => part.en).join(', ')}`, + fr: `Photo Zoomée sur ${partsTranslation.map((part) => part.en).join(', ')}`, + de: `Gezoomtes an ${partsTranslation.map((part) => part.en).join(', ')}`, + nl: `Nabij aan ${partsTranslation.map((part) => part.en).join(', ')}`, + }, + created_at: expect.any(String), + }, + detailed_viewpoint: { + centers_on: options.vehicleParts, + }, + }); + expect(getFileExtensions).toHaveBeenCalledWith(options.picture.mimetype); + const filetype = (getFileExtensions as jest.Mock).mock.results[0].value[0]; + const prefix = 'part-select'; + expect(fileConstructorSpy).toHaveBeenCalledWith( + [options.picture.blob], + expect.stringMatching(new RegExp(`${prefix}-${options.inspectionId}-\\d{13}.${filetype}`)), + { type: filetype }, + ); + }); + it('should properly create the formdata for a video frame', async () => { const options = createVideoFrameOptions(); await addImage(options, apiConfig); diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index 2867fe546..6d91f593a 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -28,15 +28,35 @@ export enum PhotoCaptureTutorialOption { */ DISABLED = 'disabled', /** - * Photo capture is enable. + * Photo capture is enabled. */ ENABLED = 'enabled', /** - * Photo capture is enable only time. + * Photo capture is enabled only time. */ FIRST_TIME_ONLY = 'first_time_only', } +/** + * Enumeration of the Add Damage options. + */ +export enum AddDamage { + /** + * Add Damage is disabled. + */ + DISABLED = 'disabled', + /** + * Add Damage is enabled with Two Shot: + * First shot for the zoom out image and second for the close up. The vehicle part will be infered. + */ + TWO_SHOT = 'two_shot', + /** + * Add Damage is enabled with Part select: + * Parts must be selected before taken a single close up shot. + */ + PART_SELECT = 'part_select', +} + /** * Configuration used to configure the Camera and picture output of the SDK. */ @@ -174,12 +194,11 @@ export type PhotoCaptureAppConfig = SharedCaptureAppConfig & */ allowSkipRetake?: boolean; /** - * Boolean indicating if `Add Damage` feature should be enabled or not. If disabled, the `Add Damage` button will - * be hidden. + * Options for Add Damage. If disabled, the `Add Damage` button will be hidden. * - * @default true + * @default AddDamage.PART_SELECT. */ - enableAddDamage?: boolean; + addDamage?: AddDamage; /** * A collection of sight guidelines in different language with a list of sightIds associate to it. */ From 505bc440864e49e3034cd40be6361106f0e9e44d Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:09:34 +0100 Subject: [PATCH 02/12] Added 'VehicleDynamicWireframe' component --- .../src/__mocks__/@monkvision/common.tsx | 4 + .../src/__mocks__/@monkvision/sights.ts | 6 ++ .../VehicleDynamicWireframe.style.ts | 4 + .../VehicleDynamicWireframe.tsx | 77 +++--------------- .../VehicleDynamicWireframe/hooks.ts | 79 +++++++++++++++++++ packages/common/src/utils/browser.utils.ts | 12 +++ packages/common/src/utils/index.ts | 1 + packages/common/src/utils/vehicle.utils.ts | 16 ++++ .../common/test/utils/vehicle.utils.test.ts | 29 +++++++ 9 files changed, 162 insertions(+), 66 deletions(-) create mode 100644 packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts create mode 100644 packages/common/src/utils/vehicle.utils.ts create mode 100644 packages/common/test/utils/vehicle.utils.test.ts diff --git a/configs/test-utils/src/__mocks__/@monkvision/common.tsx b/configs/test-utils/src/__mocks__/@monkvision/common.tsx index b0732b070..6c69763c8 100644 --- a/configs/test-utils/src/__mocks__/@monkvision/common.tsx +++ b/configs/test-utils/src/__mocks__/@monkvision/common.tsx @@ -37,6 +37,8 @@ const { mergeValidationFunctions, required, email, + getVehicleModel, + getAspectRatio, } = jest.requireActual('@monkvision/common'); export = { @@ -67,6 +69,8 @@ export = { mergeValidationFunctions, required, email, + getVehicleModel, + getAspectRatio, /* Mocks */ useMonkTheme: jest.fn(() => createTheme()), diff --git a/configs/test-utils/src/__mocks__/@monkvision/sights.ts b/configs/test-utils/src/__mocks__/@monkvision/sights.ts index f0b388214..e1d937901 100644 --- a/configs/test-utils/src/__mocks__/@monkvision/sights.ts +++ b/configs/test-utils/src/__mocks__/@monkvision/sights.ts @@ -20,6 +20,12 @@ const vehicles = { model: 'F-150 Super Cab XL 2014', type: VehicleType.PICKUP, }, + [VehicleModel.AUDIA7]: { + id: VehicleModel.AUDIA7, + make: 'Audi', + model: 'A7', + type: VehicleType.HATCHBACK, + }, }; const labels = { diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.style.ts b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.style.ts index b15a0fd9c..7932bb937 100644 --- a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.style.ts +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.style.ts @@ -8,4 +8,8 @@ export const styles: Styles = { pointerEvents: 'fill', cursor: 'pointer', }, + svg: { + width: '100%', + height: '100%', + }, }; diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx index 74a92ab6f..eabec901b 100644 --- a/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/VehicleDynamicWireframe.tsx @@ -1,13 +1,7 @@ -import { useMonkTheme } from '@monkvision/common'; -import { - PartSelectionOrientation, - VehicleModel, - VehiclePart, - VehicleType, -} from '@monkvision/types'; +import { PartSelectionOrientation, VehiclePart, VehicleType } from '@monkvision/types'; import { SVGProps } from 'react'; -import { partSelectionWireframes, vehicles } from '@monkvision/sights'; -import { DynamicSVG, DynamicSVGCustomizationFunctions } from '../DynamicSVG'; +import { DynamicSVG } from '../DynamicSVG'; +import { useVehicleDynamicWireframe } from './hooks'; import { styles } from './VehicleDynamicWireframe.style'; /** @@ -37,70 +31,21 @@ export interface VehicleDynamicWireframeProps { getPartAttributes?: (part: VehiclePart) => SVGProps; } -function isCarPartElement(element: SVGElement) { - return element.id !== '' && element.classList.contains('car-part'); -} - -function createGetAttributesCallback( - onClickPart: NonNullable, - getPartAttributes: NonNullable, -): NonNullable { - return (element, groups) => { - const groupElement: SVGGElement | undefined = groups[0]; - let part: VehiclePart; - if (groupElement && isCarPartElement(groupElement)) { - part = groupElement.id as VehiclePart; - } else if (isCarPartElement(element)) { - part = element.id as VehiclePart; - } else { - return { style: styles['notCarPart'] }; - } - const attributes = getPartAttributes(part); - if (element.tagName === 'g') { - delete attributes.style; - } - if (element.classList.contains('selectable') && element.id) { - attributes.onClick = () => onClickPart(part); - attributes.style = { ...attributes.style, ...styles['selectable'] }; - } - return attributes; - }; -} - -function getVehicleModel(vehicleType: VehicleType): VehicleModel { - const detail = Object.entries(vehicles) - .filter(([type]) => type !== VehicleModel.AUDIA7) - .find(([, details]) => details.type === vehicleType)?.[1]; - if (detail === undefined) { - throw new Error(`No vehicle model found for vehicle type ${vehicleType}`); - } - return detail.id; -} - /** * Component that displays a dynamic wireframe of a vehicle, allowing the user to select parts of the vehicle. */ export function VehicleDynamicWireframe({ vehicleType, - orientation = PartSelectionOrientation.FRONT_LEFT, onClickPart = () => {}, + orientation = PartSelectionOrientation.FRONT_LEFT, getPartAttributes = () => ({}), }: VehicleDynamicWireframeProps) { - const wireframes = partSelectionWireframes[getVehicleModel(vehicleType)]; - if (wireframes === undefined) { - throw new Error(`No wireframe found for vehicle type ${vehicleType}`); - } - const overlay = wireframes[orientation]; - const { utils } = useMonkTheme(); + const { overlay, getAttributes } = useVehicleDynamicWireframe({ + vehicleType, + orientation, + onClickPart, + getPartAttributes, + }); - return ( - - ); + return ; } diff --git a/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts b/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts new file mode 100644 index 000000000..699c27c7e --- /dev/null +++ b/packages/common-ui-web/src/components/VehicleDynamicWireframe/hooks.ts @@ -0,0 +1,79 @@ +import { partSelectionWireframes } from '@monkvision/sights'; +import { getVehicleModel } from '@monkvision/common'; +import { VehicleType, VehiclePart, PartSelectionOrientation } from '@monkvision/types'; +import { SVGProps, useCallback, useMemo } from 'react'; +import { styles } from './VehicleDynamicWireframe.style'; + +function isCarPartElement(element: SVGElement): boolean { + return element.id !== '' && element.classList.contains('car-part'); +} + +function getWireframes(vehicleType: VehicleType, orientation: PartSelectionOrientation) { + const wireframes = partSelectionWireframes[getVehicleModel(vehicleType)]; + if (wireframes === undefined) { + throw new Error(`No wireframe found for vehicle type ${vehicleType}`); + } + return wireframes[orientation]; +} + +export interface VehicleDynamicWireframeParams { + /** + * 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; +} + +export function useVehicleDynamicWireframe({ + vehicleType, + orientation = PartSelectionOrientation.FRONT_LEFT, + onClickPart = () => {}, + getPartAttributes = () => ({}), +}: VehicleDynamicWireframeParams) { + const overlay = useMemo( + () => getWireframes(vehicleType, orientation), + [vehicleType, orientation], + ); + + const getAttributes = useCallback( + (element, groups) => { + const groupElement: SVGGElement | undefined = groups[0]; + let part: VehiclePart; + if (groupElement && isCarPartElement(groupElement)) { + part = groupElement.id as VehiclePart; + } else if (isCarPartElement(element)) { + part = element.id as VehiclePart; + } else { + return { style: styles['notCarPart'] }; + } + const attributes = getPartAttributes(part); + if (element.tagName === 'g') { + delete attributes.style; + } + if (element.classList.contains('selectable') && element.id) { + attributes.onClick = () => onClickPart(part); + attributes.style = { ...attributes.style, ...styles['selectable'] }; + } + return attributes; + }, + [onClickPart, getPartAttributes], + ); + + return { overlay, getAttributes }; +} diff --git a/packages/common/src/utils/browser.utils.ts b/packages/common/src/utils/browser.utils.ts index d66e40934..3a6f8b945 100644 --- a/packages/common/src/utils/browser.utils.ts +++ b/packages/common/src/utils/browser.utils.ts @@ -1,3 +1,5 @@ +import { PixelDimensions } from '@monkvision/types'; + /** * Checks if the current device is a mobile device. */ @@ -11,3 +13,13 @@ export function isMobileDevice(): boolean { userAgent.includes('windows phone') ); } + +/** + * Returns the aspect ratio of the stream. + */ +export function getAspectRatio(streamDimensions?: PixelDimensions | null) { + if (isMobileDevice() && streamDimensions) { + return `${streamDimensions?.width}/${streamDimensions?.height}`; + } + return '16/9'; +} diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index dd296ca28..4436e1dcb 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -9,3 +9,4 @@ export * from './env.utils'; export * from './state.utils'; export * from './config.utils'; export * from './formValidation.utils'; +export * from './vehicle.utils'; diff --git a/packages/common/src/utils/vehicle.utils.ts b/packages/common/src/utils/vehicle.utils.ts new file mode 100644 index 000000000..455ed1ad9 --- /dev/null +++ b/packages/common/src/utils/vehicle.utils.ts @@ -0,0 +1,16 @@ +import { vehicles } from '@monkvision/sights'; +import { VehicleType, VehicleModel } from '@monkvision/types'; + +/** + * Returns the vehicle model corresponding to the given vehicle type. + */ +export function getVehicleModel(vehicleType: VehicleType): VehicleModel { + const ajustedVehicletype = vehicleType === VehicleType.SUV ? VehicleType.CUV : vehicleType; + const detail = Object.entries(vehicles) + .filter(([type]) => type !== VehicleModel.AUDIA7) + .find(([, details]) => details.type === ajustedVehicletype)?.[1]; + if (detail === undefined) { + throw new Error(`No vehicle model found for vehicle type ${ajustedVehicletype}`); + } + return detail.id; +} diff --git a/packages/common/test/utils/vehicle.utils.test.ts b/packages/common/test/utils/vehicle.utils.test.ts new file mode 100644 index 000000000..463bef0ba --- /dev/null +++ b/packages/common/test/utils/vehicle.utils.test.ts @@ -0,0 +1,29 @@ +import { VehicleModel, VehicleType } from '@monkvision/types'; +import { getVehicleModel } from '../../src'; + +describe('getVehicleModel', () => { + it('should return the correct vehicle model for a given vehicle type', () => { + const vehicleType = VehicleType.PICKUP; + const expectedModel = VehicleModel.FF150; + expect(getVehicleModel(vehicleType)).toBe(expectedModel); + }); + + it('should return the correct vehicle model for SUV type', () => { + const vehicleType = VehicleType.SUV; + const expectedModel = VehicleModel.FESC20; + expect(getVehicleModel(vehicleType)).toBe(expectedModel); + }); + + it('should return the correct vehicle model for CUV type', () => { + const vehicleType = VehicleType.CUV; + const expectedModel = VehicleModel.FESC20; + expect(getVehicleModel(vehicleType)).toBe(expectedModel); + }); + + it('should throw an error if no vehicle model is found', () => { + const vehicleType = VehicleType.HATCHBACK; + expect(() => getVehicleModel(vehicleType)).toThrowError( + `No vehicle model found for vehicle type ${vehicleType}`, + ); + }); +}); From e585c7def43c6f7190963d2386364f927eccc1b0 Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:09:56 +0100 Subject: [PATCH 03/12] Added 'VehiclePartSelection' component --- .../VehiclePartSelection.style.ts | 27 +++- .../VehiclePartSelection.tsx | 120 +++++++++++++----- 2 files changed, 110 insertions(+), 37 deletions(-) diff --git a/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.style.ts b/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.style.ts index e00d5c53f..6e7d22472 100644 --- a/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.style.ts +++ b/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.style.ts @@ -1,12 +1,31 @@ import { Styles } from '@monkvision/types'; +export const ICON_SIZE = 40; + export const styles: Styles = { wrapper: { - position: 'absolute', display: 'flex', - justifyContent: 'center', alignItems: 'center', - inset: '0 0 0 0', - gap: '30px', + justifyContent: 'space-between', + height: '100%', + width: '100%', + }, + wireframeContainer: { + height: '90%', + width: '100%', + position: 'absolute', + top: '50%', + transform: 'translateY(-50%)', + }, + leftArrowContainer: { + display: 'flex', + }, + leftArrow: { + zIndex: 1, + }, + spacer: { width: `${ICON_SIZE}px` }, + rightArrow: { + position: 'absolute', + right: 0, }, }; diff --git a/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.tsx b/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.tsx index 50dadec22..ba4a51694 100644 --- a/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.tsx +++ b/packages/common-ui-web/src/components/VehiclePartSelection/VehiclePartSelection.tsx @@ -1,9 +1,15 @@ -import { PartSelectionOrientation, VehiclePart, VehicleType } from '@monkvision/types'; +import { + ColorProp, + PartSelectionOrientation, + Styles, + VehiclePart, + VehicleType, +} from '@monkvision/types'; import { useState } from 'react'; import { useMonkTheme } from '@monkvision/common'; import { Icon } from '../../icons'; import { VehicleDynamicWireframe, VehicleDynamicWireframeProps } from '../VehicleDynamicWireframe'; -import { styles } from './VehiclePartSelection.style'; +import { ICON_SIZE, styles } from './VehiclePartSelection.style'; /** * Props accepted by the VehiclePartSelection component @@ -23,9 +29,27 @@ export interface VehiclePartSelectionProps { * Callback called when the selected parts are updated (the user selects or unselects a new part). */ onPartsSelected?: (parts: VehiclePart[]) => void; + /** + * The name or the hexcode of the color to apply to the part selected. + * + * @default 'primary-base' + */ + primaryColor?: ColorProp; + /** + * The name or the hexcode of the color to apply to the vehicle wireframe. + * + * @default 'text-primary' + */ + secondaryColor?: ColorProp; + /** + * The maximum number of parts that can be selected. + * + * @default Infinity + */ + maxSelectableParts?: number; } -const ORIENTATIONS = [ +const ORIENTATIONS_ORDER = [ PartSelectionOrientation.FRONT_LEFT, PartSelectionOrientation.REAR_LEFT, PartSelectionOrientation.REAR_RIGHT, @@ -37,51 +61,81 @@ const ORIENTATIONS = [ */ export function VehiclePartSelection({ vehicleType, - orientation: initialOrientation, + orientation: initialOrientation = PartSelectionOrientation.FRONT_LEFT, onPartsSelected = () => {}, + primaryColor = 'primary-base', + secondaryColor = 'text-primary', + maxSelectableParts = Infinity, }: VehiclePartSelectionProps) { - const [orientation, setOrientation] = useState(initialOrientation ?? ORIENTATIONS[0]); - const [selectedParts, setSelectedParts] = useState>([]); - const { palette } = useMonkTheme(); + const [orientation, setOrientation] = useState(initialOrientation); + const [selectedParts, setSelectedParts] = useState([]); + const { utils } = useMonkTheme(); const rotateRight = () => { - const currentIndex = ORIENTATIONS.indexOf(orientation); - const nextIndex = (currentIndex + 1) % ORIENTATIONS.length; - setOrientation(ORIENTATIONS[nextIndex]); + const currentIndex = ORIENTATIONS_ORDER.indexOf(orientation); + const nextIndex = (currentIndex + 1) % ORIENTATIONS_ORDER.length; + setOrientation(ORIENTATIONS_ORDER[nextIndex]); }; const rotateLeft = () => { - const currentIndex = ORIENTATIONS.indexOf(orientation); - const nextIndex = (currentIndex - 1 + ORIENTATIONS.length) % ORIENTATIONS.length; - setOrientation(ORIENTATIONS[nextIndex]); + const currentIndex = ORIENTATIONS_ORDER.indexOf(orientation); + const nextIndex = (currentIndex - 1 + ORIENTATIONS_ORDER.length) % ORIENTATIONS_ORDER.length; + setOrientation(ORIENTATIONS_ORDER[nextIndex]); }; + const togglePart = (part: VehiclePart) => { - const newSelectedParts = selectedParts.includes(part) - ? selectedParts.filter((p) => p !== part) - : [...selectedParts, part]; - setSelectedParts(newSelectedParts); - onPartsSelected(newSelectedParts); + const isSelected = selectedParts.includes(part); + if (isSelected) { + const newSelectedParts = selectedParts.filter((p) => p !== part); + setSelectedParts(newSelectedParts); + onPartsSelected(newSelectedParts); + } else { + let newSelectedParts = [...selectedParts, part]; + if (newSelectedParts.length > maxSelectableParts) { + newSelectedParts = [...newSelectedParts.slice(1)]; + } + setSelectedParts(newSelectedParts); + onPartsSelected(newSelectedParts); + } }; + const getPartAttributes: VehicleDynamicWireframeProps['getPartAttributes'] = ( part: VehiclePart, - ) => ({ - style: { - // TODO: need to finalize the color for the selected parts. - fill: selectedParts.includes(part) ? palette.primary.xlight : undefined, - stroke: palette.primary.light, - display: 'block', - }, - }); + ): Styles => { + return { + style: { + fill: selectedParts.includes(part) ? utils.getColor(primaryColor) : undefined, + stroke: utils.getColor(secondaryColor), + }, + }; + }; return (
- - + + +
+
+ +
+ - ); } From a905b6dc2dc88a2edd7bc11d1eaa2ffd64480df2 Mon Sep 17 00:00:00 2001 From: David Ly Date: Tue, 7 Jan 2025 09:47:18 +0100 Subject: [PATCH 04/12] Added 'CaptureSelection' component --- .../CaptureSelection.styles.ts | 62 ++++++++++++++++ .../CaptureSelection/CaptureSelection.tsx | 73 +++++++++++++++++++ .../src/components/CaptureSelection/hooks.ts | 19 +++++ .../src/components/CaptureSelection/i18n.ts | 20 +++++ .../src/components/CaptureSelection/index.ts | 1 + .../CaptureSelection/translations/de.json | 12 +++ .../CaptureSelection/translations/en.json | 12 +++ .../CaptureSelection/translations/fr.json | 12 +++ .../CaptureSelection/translations/nl.json | 12 +++ .../common-ui-web/src/components/index.ts | 1 + .../test/components/CaptureSelection.test.tsx | 44 +++++++++++ 11 files changed, 268 insertions(+) create mode 100644 packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.styles.ts create mode 100644 packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.tsx create mode 100644 packages/common-ui-web/src/components/CaptureSelection/hooks.ts create mode 100644 packages/common-ui-web/src/components/CaptureSelection/i18n.ts create mode 100644 packages/common-ui-web/src/components/CaptureSelection/index.ts create mode 100644 packages/common-ui-web/src/components/CaptureSelection/translations/de.json create mode 100644 packages/common-ui-web/src/components/CaptureSelection/translations/en.json create mode 100644 packages/common-ui-web/src/components/CaptureSelection/translations/fr.json create mode 100644 packages/common-ui-web/src/components/CaptureSelection/translations/nl.json create mode 100644 packages/common-ui-web/test/components/CaptureSelection.test.tsx diff --git a/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.styles.ts b/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.styles.ts new file mode 100644 index 000000000..b88b8d102 --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.styles.ts @@ -0,0 +1,62 @@ +import { Styles } from '@monkvision/types'; + +export const SMALL_WIDTH_BREAKPOINT = 700; + +export const styles: Styles = { + container: { + height: '100%', + width: '100%', + position: 'fixed', + inset: 0, + display: 'flex', + justifyContent: 'space-evenly', + alignItems: 'center', + zIndex: 10, + gap: '15px', + flexDirection: 'row', + }, + containerSmall: { + __media: { maxWidth: SMALL_WIDTH_BREAKPOINT }, + justifyContent: 'center', + flexDirection: 'column', + }, + elementsContainer: { + display: 'flex', + justifyContent: 'space-around', + alignItems: 'center', + }, + contentContainer: { + width: '40%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '10px', + flexDirection: 'column', + paddingBottom: '20px', + }, + contentContainerSmall: { + __media: { maxWidth: SMALL_WIDTH_BREAKPOINT }, + width: '80%', + }, + textcontainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '10px', + flexDirection: 'column', + }, + title: { + fontSize: '22px', + }, + description: { + fontSize: '14px', + textAlign: 'center', + paddingBottom: '10px', + }, + buttonsContainer: { + display: 'flex', + width: '100%', + justifyContent: 'space-between', + gap: '3px', + }, +}; diff --git a/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.tsx b/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.tsx new file mode 100644 index 000000000..5e015134c --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/CaptureSelection.tsx @@ -0,0 +1,73 @@ +import { useTranslation } from 'react-i18next'; +import { i18nWrap, useI18nSync, useMonkTheme } from '@monkvision/common'; +import { styles } from './CaptureSelection.styles'; +import { Button } from '../Button'; +import { i18nCreateInspection } from './i18n'; +import { useCaptureSelectionStyles } from './hooks'; + +/** + * Props of the CaptureSelection component. + */ +export interface CaptureSelectionProps { + /** + * The language used by the component. + * + * @default en + */ + lang?: string; + /** + * Callback called when the user clicks on "Add Damage" button. + */ + onAddDamage?: () => void; + /** + * Callback called when the user clicks on "Take Picture" button. + */ + onCapture?: () => void; +} + +/** + * A single page component that allows the user to select between "Add Damage" or "Photo Capture" workflow. + */ +export const CaptureSelection = i18nWrap(function CaptureSelection({ + lang, + onAddDamage = () => {}, + onCapture = () => {}, +}: CaptureSelectionProps) { + useI18nSync(lang); + const { t } = useTranslation(); + const { palette } = useMonkTheme(); + const style = useCaptureSelectionStyles(); + + return ( +
+
+
+ {t('addDamage.title')} + {t('addDamage.description')} +
+ +
+
+ {t('capture.title')} + {t('capture.description')} + +
+
+ ); +}, +i18nCreateInspection); diff --git a/packages/common-ui-web/src/components/CaptureSelection/hooks.ts b/packages/common-ui-web/src/components/CaptureSelection/hooks.ts new file mode 100644 index 000000000..d352cc2e7 --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/hooks.ts @@ -0,0 +1,19 @@ +import { useMonkTheme, useResponsiveStyle } from '@monkvision/common'; +import { styles } from './CaptureSelection.styles'; + +export function useCaptureSelectionStyles() { + const { responsive } = useResponsiveStyle(); + const { palette } = useMonkTheme(); + + return { + container: { + ...styles['container'], + ...responsive(styles['containerSmall']), + }, + contentContainer: { + ...styles['contentContainer'], + ...responsive(styles['contentContainerSmall']), + }, + description: { color: palette.secondary.xlight, ...styles['description'] }, + }; +} diff --git a/packages/common-ui-web/src/components/CaptureSelection/i18n.ts b/packages/common-ui-web/src/components/CaptureSelection/i18n.ts new file mode 100644 index 000000000..b7c0890e4 --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/i18n.ts @@ -0,0 +1,20 @@ +import { i18nCreateSDKInstance } from '@monkvision/common'; +import en from './translations/en.json'; +import fr from './translations/fr.json'; +import de from './translations/de.json'; +import nl from './translations/nl.json'; + +/** + * i18n instance of the CaptureSelection component. You can use this instance to automatically sync your application + * current language with the one used by the components of the package. + */ +const i18nCreateInspection = i18nCreateSDKInstance({ + resources: { + en: { translation: en }, + fr: { translation: fr }, + de: { translation: de }, + nl: { translation: nl }, + }, +}); + +export { i18nCreateInspection }; diff --git a/packages/common-ui-web/src/components/CaptureSelection/index.ts b/packages/common-ui-web/src/components/CaptureSelection/index.ts new file mode 100644 index 000000000..3cea91b95 --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/index.ts @@ -0,0 +1 @@ +export * from './CaptureSelection'; diff --git a/packages/common-ui-web/src/components/CaptureSelection/translations/de.json b/packages/common-ui-web/src/components/CaptureSelection/translations/de.json new file mode 100644 index 000000000..31ae3f0c7 --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/translations/de.json @@ -0,0 +1,12 @@ +{ + "addDamage": { + "title": "Schaden melden", + "description": "Fügen Sie Fotos von Dellen, Kratzern oder anderen Schäden am Fahrzeug hinzu.", + "button": "Schadensfotos hinzufügen" + }, + "capture": { + "title": "Zustand erfassen", + "description": "Keine Dellen oder Kratzer? Gehen Sie um das Fahrzeug herum und machen Sie Fotos.", + "button": "Fotos machen" + } +} diff --git a/packages/common-ui-web/src/components/CaptureSelection/translations/en.json b/packages/common-ui-web/src/components/CaptureSelection/translations/en.json new file mode 100644 index 000000000..7a6eaadad --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/translations/en.json @@ -0,0 +1,12 @@ +{ + "addDamage": { + "title": "Report Damage", + "description": "Add photos of dents, scratches or other damages present on the vehicle.", + "button": "Add Damage Photos" + }, + "capture": { + "title": "Capture Condition", + "description": "No dents or scratches? Walk around the vehicle and take photos.", + "button": "Take Photos" + } +} diff --git a/packages/common-ui-web/src/components/CaptureSelection/translations/fr.json b/packages/common-ui-web/src/components/CaptureSelection/translations/fr.json new file mode 100644 index 000000000..be5d8615c --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/translations/fr.json @@ -0,0 +1,12 @@ +{ + "addDamage": { + "title": "Signaler un dégât", + "description": "Ajoutez des photos d'enfoncements, rayures ou autres dégâts présents sur le véhicule.", + "button": "Ajouter des photos de dégâts" + }, + "capture": { + "title": "Capturer l'état", + "description": "Pas d'enfoncements ni de rayures ? Faites le tour du véhicule et prenez des photos.", + "button": "Prendre des photos" + } +} diff --git a/packages/common-ui-web/src/components/CaptureSelection/translations/nl.json b/packages/common-ui-web/src/components/CaptureSelection/translations/nl.json new file mode 100644 index 000000000..9b4d2786a --- /dev/null +++ b/packages/common-ui-web/src/components/CaptureSelection/translations/nl.json @@ -0,0 +1,12 @@ +{ + "addDamage": { + "title": "Schade melden", + "description": "Voeg foto's toe van deuken, krassen of andere schade aan het voertuig.", + "button": "Schadefoto's toevoegen" + }, + "capture": { + "title": "Conditie vastleggen", + "description": "Geen deuken of krassen? Loop rond het voertuig en maak foto's.", + "button": "Foto's maken" + } +} diff --git a/packages/common-ui-web/src/components/index.ts b/packages/common-ui-web/src/components/index.ts index 59784da71..d525af00f 100644 --- a/packages/common-ui-web/src/components/index.ts +++ b/packages/common-ui-web/src/components/index.ts @@ -20,3 +20,4 @@ export * from './VehiclePartSelection'; export * from './VehicleTypeAsset'; export * from './VehicleTypeSelection'; export * from './VehicleWalkaroundIndicator'; +export * from './CaptureSelection'; diff --git a/packages/common-ui-web/test/components/CaptureSelection.test.tsx b/packages/common-ui-web/test/components/CaptureSelection.test.tsx new file mode 100644 index 000000000..432c94856 --- /dev/null +++ b/packages/common-ui-web/test/components/CaptureSelection.test.tsx @@ -0,0 +1,44 @@ +import '@testing-library/jest-dom'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { CaptureSelection } from '../../src'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +describe('CaptureSelection', () => { + it('renders correctly', () => { + const { unmount } = render(); + + expect(screen.getByText('addDamage.title')).toBeInTheDocument(); + expect(screen.getByText('addDamage.description')).toBeInTheDocument(); + expect(screen.getByText('addDamage.button')).toBeInTheDocument(); + expect(screen.getByText('capture.title')).toBeInTheDocument(); + expect(screen.getByText('capture.description')).toBeInTheDocument(); + expect(screen.getByText('capture.button')).toBeInTheDocument(); + + unmount(); + }); + + it('calls onAddDamage when "Add Damage" button is clicked', () => { + const onAddDamage = jest.fn(); + const { unmount } = render(); + + fireEvent.click(screen.getByText('addDamage.button')); + expect(onAddDamage).toHaveBeenCalled(); + + unmount(); + }); + + it('calls onCapture when "Take Picture" button is clicked', () => { + const onCapture = jest.fn(); + const { unmount } = render(); + + fireEvent.click(screen.getByText('capture.button')); + expect(onCapture).toHaveBeenCalled(); + + unmount(); + }); +}); From 866af569f083719feeac47e86bcccc83d9168204 Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:10:44 +0100 Subject: [PATCH 05/12] Enabled gallery to hide Sight pictures --- .../hooks/useInspectionGalleryItems.ts | 21 ++- .../src/components/InspectionGallery/types.ts | 15 +- packages/common/src/utils/state.utils.ts | 36 +++-- .../common/test/utils/state.utils.test.ts | 133 ++++++++++-------- 4 files changed, 121 insertions(+), 84 deletions(-) diff --git a/packages/common-ui-web/src/components/InspectionGallery/hooks/useInspectionGalleryItems.ts b/packages/common-ui-web/src/components/InspectionGallery/hooks/useInspectionGalleryItems.ts index 576b3c14c..9cf3eddc6 100644 --- a/packages/common-ui-web/src/components/InspectionGallery/hooks/useInspectionGalleryItems.ts +++ b/packages/common-ui-web/src/components/InspectionGallery/hooks/useInspectionGalleryItems.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { ImageStatus, Sight } from '@monkvision/types'; +import { AddDamage, ImageStatus, ImageType, Sight } from '@monkvision/types'; import { getInspectionImages, MonkState, useMonkState } from '@monkvision/common'; import { useInspectionPoll } from '@monkvision/network'; import { InspectionGalleryItem, InspectionGalleryProps } from '../types'; @@ -57,9 +57,10 @@ function getItems( captureMode: boolean, entities: MonkState, inspectionSights?: Sight[], - enableAddDamage?: boolean, + addDamage?: AddDamage, + filterByImageType?: ImageType, ): InspectionGalleryItem[] { - const images = getInspectionImages(inspectionId, entities.images, captureMode); + const images = getInspectionImages(inspectionId, entities.images, filterByImageType, captureMode); const items: InspectionGalleryItem[] = images.map((image) => ({ isTaken: true, isAddDamage: false, @@ -73,7 +74,7 @@ function getItems( items.push({ isTaken: false, isAddDamage: false, sightId: sight.id }); } }); - if (captureMode && enableAddDamage !== false) { + if (captureMode && addDamage !== AddDamage.DISABLED) { items.push({ isAddDamage: true }); } return items.sort((a, b) => compareGalleryItems(a, b, captureMode, inspectionSights)); @@ -99,9 +100,17 @@ export function useInspectionGalleryItems(props: InspectionGalleryProps): Inspec props.captureMode, state, inspectionSights, - props.enableAddDamage, + props.addDamage, + props.filterByImageType, ), - [props.inspectionId, props.captureMode, state, inspectionSights, props.enableAddDamage], + [ + props.inspectionId, + props.captureMode, + state, + inspectionSights, + props.addDamage, + props.filterByImageType, + ], ); const shouldFetch = useMemo(() => shouldContinueToFetch(items), items); diff --git a/packages/common-ui-web/src/components/InspectionGallery/types.ts b/packages/common-ui-web/src/components/InspectionGallery/types.ts index 01fbe9410..b68cdc960 100644 --- a/packages/common-ui-web/src/components/InspectionGallery/types.ts +++ b/packages/common-ui-web/src/components/InspectionGallery/types.ts @@ -1,4 +1,4 @@ -import { ComplianceOptions, Image, Sight } from '@monkvision/types'; +import { AddDamage, ComplianceOptions, Image, ImageType, Sight } from '@monkvision/types'; import { MonkApiConfig } from '@monkvision/network'; /** @@ -100,12 +100,17 @@ export type InspectionGalleryProps = { */ onValidate?: () => void; /** - * Boolean indicating if `Add Damage` feature should be enabled or not. If disabled, the `Add Custom Damage` button - * will be hidden. + * Options for Add Damage. If disabled, the `Add Custom Damage` button will be hidden. * - * @default true + * @default AddDamage.PART_SELECT. */ - enableAddDamage?: boolean; + addDamage?: AddDamage; + /** + * The specific image type to filter by. If not provided, no type-based filtering is applied. + * + * @default undefined + */ + filterByImageType?: ImageType; /** * Custom label for validate button. */ diff --git a/packages/common/src/utils/state.utils.ts b/packages/common/src/utils/state.utils.ts index 6568fa492..c9915d4e0 100644 --- a/packages/common/src/utils/state.utils.ts +++ b/packages/common/src/utils/state.utils.ts @@ -1,37 +1,43 @@ -import { Image } from '@monkvision/types'; +import { Image, ImageType } from '@monkvision/types'; /** * Utility function that extracts the images of the given inspection. * * @param inspectionId The ID of the inspection to get the images of. * @param images Array containing every image existing in the current local state. + * @param filterImageType The specific image type to filter by. If not provided, no type-based filtering is applied. * @param filterRetakes Boolean indicating if retaken pictures should be filtered out or not (default: false). */ export function getInspectionImages( inspectionId: string, images: Image[], + filterImageType?: ImageType, filterRetakes = false, ): Image[] { - const inspectionImages = images.filter((image) => image.inspectionId === inspectionId); + let inspectionImages = images.filter((image) => image.inspectionId === inspectionId); + + if (filterImageType) { + inspectionImages = inspectionImages.filter((image) => filterImageType === image.type); + } + if (!filterRetakes) { return inspectionImages; } - const filteredRetakes: Image[] = []; + + const filteredRetakes: Record = {}; inspectionImages.forEach((image) => { if (image.sightId) { - const index = filteredRetakes.findIndex((i) => i.sightId === image.sightId); - if (index >= 0) { - if ( - image.createdAt && - filteredRetakes[index].createdAt && - image.createdAt > (filteredRetakes[index].createdAt as number) - ) { - filteredRetakes[index] = image; - } - return; + const existingImage = filteredRetakes[image.sightId]; + if ( + !existingImage || + (image.createdAt && existingImage.createdAt && image.createdAt > existingImage.createdAt) + ) { + filteredRetakes[image.sightId] = image; } + } else { + filteredRetakes[image.id] = image; } - filteredRetakes.push(image); }); - return filteredRetakes; + + return Object.values(filteredRetakes); } diff --git a/packages/common/test/utils/state.utils.test.ts b/packages/common/test/utils/state.utils.test.ts index 54d3d5e58..267ae7297 100644 --- a/packages/common/test/utils/state.utils.test.ts +++ b/packages/common/test/utils/state.utils.test.ts @@ -1,6 +1,48 @@ -import { Image } from '@monkvision/types'; +import { Image, ImageType } from '@monkvision/types'; import { getInspectionImages } from '../../src'; +const inspectionIdMock = 'test-inspection-1'; +const imagesMock = [ + { id: 'test-1', inspectionId: 'test-1', type: ImageType.CLOSE_UP }, + { + id: 'test-2', + inspectionId: inspectionIdMock, + sightId: 'sight-1', + createdAt: Date.parse('1998-01-01T01:01:01.001Z'), + type: ImageType.BEAUTY_SHOT, + }, + { + id: 'test-3', + inspectionId: inspectionIdMock, + sightId: 'sight-1', + createdAt: Date.parse('2020-01-01T01:01:01.001Z'), + type: ImageType.BEAUTY_SHOT, + }, + { + id: 'test-4', + inspectionId: inspectionIdMock, + sightId: 'sight-1', + createdAt: Date.parse('2024-01-01T01:01:01.001Z'), + type: ImageType.BEAUTY_SHOT, + }, + { id: 'test-5', inspectionId: inspectionIdMock, type: ImageType.CLOSE_UP }, + { id: 'test-6', inspectionId: inspectionIdMock, type: ImageType.CLOSE_UP }, + { + id: 'test-7', + inspectionId: inspectionIdMock, + sightId: 'sight-2', + createdAt: Date.parse('2024-01-01T01:01:01.001Z'), + type: ImageType.BEAUTY_SHOT, + }, + { + id: 'test-8', + inspectionId: inspectionIdMock, + sightId: 'sight-2', + createdAt: Date.parse('1998-01-01T01:01:01.001Z'), + type: ImageType.BEAUTY_SHOT, + }, +] as Image[]; + describe('State utils', () => { describe('getInspectionImages util function', () => { it('should return an empty array if there are no image in the inspection', () => { @@ -38,64 +80,39 @@ describe('State utils', () => { }); it('should properly filter retakes', () => { - const inspectionId = 'test-inspection-1'; - const images = [ - { id: 'test-1', inspectionId: 'test-1' }, - { - id: 'test-2', - inspectionId, - sightId: 'sight-1', - createdAt: Date.parse('1998-01-01T01:01:01.001Z'), - }, - { - id: 'test-3', - inspectionId, - sightId: 'sight-1', - createdAt: Date.parse('2020-01-01T01:01:01.001Z'), - }, - { - id: 'test-4', - inspectionId, - sightId: 'sight-1', - createdAt: Date.parse('2024-01-01T01:01:01.001Z'), - }, - { id: 'test-5', inspectionId }, - { id: 'test-6', inspectionId }, - { - id: 'test-7', - inspectionId, - sightId: 'sight-2', - createdAt: Date.parse('2024-01-01T01:01:01.001Z'), - }, - { - id: 'test-8', - inspectionId, - sightId: 'sight-2', - createdAt: Date.parse('1998-01-01T01:01:01.001Z'), - }, - ] as Image[]; - const inspectionImages = getInspectionImages(inspectionId, images, true); + const inspectionImages = getInspectionImages(inspectionIdMock, imagesMock, undefined, true); expect(inspectionImages.length).toBe(4); - expect(inspectionImages).toContainEqual({ - id: 'test-4', - inspectionId, - sightId: 'sight-1', - createdAt: Date.parse('2024-01-01T01:01:01.001Z'), - }); - expect(inspectionImages).toContainEqual({ - id: 'test-5', - inspectionId, - }); - expect(inspectionImages).toContainEqual({ - id: 'test-6', - inspectionId, - }); - expect(inspectionImages).toContainEqual({ - id: 'test-7', - inspectionId, - sightId: 'sight-2', - createdAt: Date.parse('2024-01-01T01:01:01.001Z'), - }); + expect(inspectionImages).toContainEqual(imagesMock.at(3)); + expect(inspectionImages).toContainEqual(imagesMock.at(4)); + expect(inspectionImages).toContainEqual(imagesMock.at(5)); + expect(inspectionImages).toContainEqual(imagesMock.at(6)); + }); + + it('should properly filter image type by Image.CLOSE_UP', () => { + const inspectionImages = getInspectionImages( + inspectionIdMock, + imagesMock, + ImageType.CLOSE_UP, + false, + ); + expect(inspectionImages.length).toBe(2); + expect(inspectionImages).toContainEqual(imagesMock.at(4)); + expect(inspectionImages).toContainEqual(imagesMock.at(5)); + }); + + it('should properly filter image type by Image.BEAUTY_SHOT', () => { + const inspectionImages = getInspectionImages( + inspectionIdMock, + imagesMock, + ImageType.BEAUTY_SHOT, + false, + ); + expect(inspectionImages.length).toBe(5); + expect(inspectionImages).toContainEqual(imagesMock.at(1)); + expect(inspectionImages).toContainEqual(imagesMock.at(2)); + expect(inspectionImages).toContainEqual(imagesMock.at(3)); + expect(inspectionImages).toContainEqual(imagesMock.at(6)); + expect(inspectionImages).toContainEqual(imagesMock.at(7)); }); }); }); From af35c7258af7874daece93b56690dba4fd348b24 Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:13:18 +0100 Subject: [PATCH 06/12] Shared components between `PhotoCapture' and 'DamageDisclosure' --- .../PhotoCaptureHUDButtons/index.ts | 1 - .../PhotoCaptureHUDCancelButton.tsx | 27 ----- .../PhotoCaptureHUDCancelButton/index.ts | 4 - .../PhotoCaptureHUDCounter.tsx | 17 ---- .../PhotoCaptureHUDCounter/index.ts | 2 - .../PhotoCaptureHUDElements.tsx | 4 +- ...hotoCaptureHUDElementsAddDamage1stShot.tsx | 53 ---------- .../index.ts | 4 - ...hotoCaptureHUDElementsAddDamage2ndShot.tsx | 56 ----------- .../index.ts | 4 - .../PhotoCaptureHUDOverlay/index.ts | 2 - .../PhotoCaptureHUD/hooks/index.ts | 1 - .../usePhotoCaptureHUDButtonBackground.ts | 11 --- .../src/PhotoCapture/PhotoCaptureHUD/index.ts | 6 -- .../components/CancelButton/CancelButton.tsx | 27 +++++ .../src/components/CancelButton/index.ts | 1 + .../CloseUpShot/CloseUpShot.styles.ts} | 3 +- .../components/CloseUpShot/CloseUpShot.tsx | 54 ++++++++++ .../CloseUpShot}/hooks.ts | 6 +- .../src/components/CloseUpShot/index.ts | 4 + .../Counter/Counter.styles.ts} | 0 .../src/components/Counter/Counter.tsx | 17 ++++ .../Counter}/hooks.ts | 20 ++-- .../src/components/Counter/index.ts | 2 + .../HUDButtons/HUDButtons.styles.ts} | 0 .../HUDButtons/HUDButtons.tsx} | 14 +-- .../HUDButtons}/hooks.ts | 10 +- .../src/components/HUDButtons/index.ts | 1 + .../HUDOverlay/HUDOverlay.styles.ts} | 0 .../HUDOverlay/HUDOverlay.tsx} | 14 +-- .../HUDOverlay}/hooks.ts | 8 +- .../src/components/HUDOverlay/index.ts | 2 + .../PartSelection/PartSelection.styles.ts | 53 ++++++++++ .../PartSelection/PartSelection.tsx | 99 +++++++++++++++++++ .../src/components/PartSelection/index.ts | 2 + .../ZoomOutShot/ZoomOutShot.styles.ts} | 2 +- .../components/ZoomOutShot/ZoomOutShot.tsx | 51 ++++++++++ .../ZoomOutShot}/hooks.ts | 6 +- .../src/components/ZoomOutShot/index.ts | 1 + .../src/components/index.ts | 7 ++ .../src/translations/de.json | 7 +- .../src/translations/en.json | 7 +- .../src/translations/fr.json | 7 +- .../src/translations/nl.json | 7 +- .../CancelButton.test.tsx} | 8 +- .../CloseUpShot.test.tsx} | 50 +++++----- .../Counter.test.tsx} | 18 ++-- .../HUDButtons.test.tsx} | 44 ++++----- .../HUDOverlay.test.tsx} | 28 +++--- .../ZoomOutShot.test.tsx} | 38 ++++--- 50 files changed, 477 insertions(+), 333 deletions(-) delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.tsx delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.tsx delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts create mode 100644 packages/inspection-capture-web/src/components/CancelButton/CancelButton.tsx create mode 100644 packages/inspection-capture-web/src/components/CancelButton/index.ts rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.styles.ts => components/CloseUpShot/CloseUpShot.styles.ts} (93%) create mode 100644 packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.tsx rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot => components/CloseUpShot}/hooks.ts (67%) create mode 100644 packages/inspection-capture-web/src/components/CloseUpShot/index.ts rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.styles.ts => components/Counter/Counter.styles.ts} (100%) create mode 100644 packages/inspection-capture-web/src/components/Counter/Counter.tsx rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter => components/Counter}/hooks.ts (57%) create mode 100644 packages/inspection-capture-web/src/components/Counter/index.ts rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles.ts => components/HUDButtons/HUDButtons.styles.ts} (100%) rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx => components/HUDButtons/HUDButtons.tsx} (90%) rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons => components/HUDButtons}/hooks.ts (91%) create mode 100644 packages/inspection-capture-web/src/components/HUDButtons/index.ts rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts => components/HUDOverlay/HUDOverlay.styles.ts} (100%) rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx => components/HUDOverlay/HUDOverlay.tsx} (76%) rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay => components/HUDOverlay}/hooks.ts (91%) create mode 100644 packages/inspection-capture-web/src/components/HUDOverlay/index.ts create mode 100644 packages/inspection-capture-web/src/components/PartSelection/PartSelection.styles.ts create mode 100644 packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx create mode 100644 packages/inspection-capture-web/src/components/PartSelection/index.ts rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.styles.ts => components/ZoomOutShot/ZoomOutShot.styles.ts} (88%) create mode 100644 packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.tsx rename packages/inspection-capture-web/src/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot => components/ZoomOutShot}/hooks.ts (61%) create mode 100644 packages/inspection-capture-web/src/components/ZoomOutShot/index.ts rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx => components/CancelButton.test.tsx} (75%) rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot.test.tsx => components/CloseUpShot.test.tsx} (53%) rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx => components/Counter.test.tsx} (65%) rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx => components/HUDButtons.test.tsx} (75%) rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx => components/HUDOverlay.test.tsx} (84%) rename packages/inspection-capture-web/test/{PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot.test.tsx => components/ZoomOutShot.test.tsx} (53%) diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts deleted file mode 100644 index d256f5ba0..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PhotoCaptureHUDButtons, type PhotoCaptureHUDButtonsProps } from './PhotoCaptureHUDButtons'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx deleted file mode 100644 index 5395c718e..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Button } from '@monkvision/common-ui-web'; -import { useTranslation } from 'react-i18next'; -import { usePhotoCaptureHUDButtonBackground } from '../hooks'; - -/** - * Props of the PhotoCaptureHUDCancelButton component. - */ -export interface PhotoCaptureHUDCancelButtonProps { - /** - * Callback called when the user clicks on the button. - */ - onCancel?: () => void; -} - -/** - * Component implementing a cancel button displayed in the PhotoCapture Camera HUD. - */ -export function PhotoCaptureHUDCancelButton({ onCancel }: PhotoCaptureHUDCancelButtonProps) { - const { t } = useTranslation(); - const primaryColor = usePhotoCaptureHUDButtonBackground(); - - return ( - - ); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts deleted file mode 100644 index aa5c1883c..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { - PhotoCaptureHUDCancelButton, - type PhotoCaptureHUDCancelButtonProps, -} from './PhotoCaptureHUDCancelButton'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx deleted file mode 100644 index 39f0561de..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { styles } from './PhotoCaptureHUDCounter.styles'; -import { usePhotoCaptureHUDButtonBackground } from '../hooks'; -import { PhotoCaptureHUDCounterProps, usePhotoCaptureHUDCounterLabel } from './hooks'; - -/** - * Component that implements an indicator of pictures taken during the PhotoCapture process. - */ -export function PhotoCaptureHUDCounter(props: PhotoCaptureHUDCounterProps) { - const label = usePhotoCaptureHUDCounterLabel(props); - const backgroundColor = usePhotoCaptureHUDButtonBackground(); - - return ( -
- {label} -
- ); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts deleted file mode 100644 index 7b2aa8a93..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { PhotoCaptureHUDCounter } from './PhotoCaptureHUDCounter'; -export { type PhotoCaptureHUDCounterProps } from './hooks'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx index c0af87444..1d2957826 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx @@ -97,7 +97,9 @@ export function PhotoCaptureHUDElements(params: PhotoCaptureHUDElementsProps) { return ( ); } diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.tsx deleted file mode 100644 index 448c1386f..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button, DynamicSVG } from '@monkvision/common-ui-web'; -import { usePhotoCaptureHUDButtonBackground } from '../hooks'; -import { styles } from './PhotoCaptureHUDElementsAddDamage1stShot.styles'; -import { PhotoCaptureHUDCounter } from '../PhotoCaptureHUDCounter'; -import { PhotoCaptureMode } from '../../hooks'; -import { PhotoCaptureHUDCancelButton } from '../PhotoCaptureHUDCancelButton'; -import { crosshairSvg } from '../../../assets'; -import { usePhotoCaptureHUDElementsAddDamage1stShotStyles } from './hooks'; - -/** - * Props of the PhotoCaptureHUDElementsAddDamage1stShot component. - */ -export interface PhotoCaptureHUDElementsAddDamage1stShotProps { - /** - * Callback called when the user cancels the Add Damage mode. - */ - onCancel?: () => void; -} - -/** - * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current - * mode is ADD_DAMAGE_1ST_SHOT. - */ -export function PhotoCaptureHUDElementsAddDamage1stShot({ - onCancel, -}: PhotoCaptureHUDElementsAddDamage1stShotProps) { - const [showInfoPopup, setShowInfoPopup] = useState(true); - const { t } = useTranslation(); - const primaryColor = usePhotoCaptureHUDButtonBackground(); - const style = usePhotoCaptureHUDElementsAddDamage1stShotStyles(); - - return ( -
- -
- - -
- {showInfoPopup && ( - - )} -
- ); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/index.ts deleted file mode 100644 index 359fef407..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { - PhotoCaptureHUDElementsAddDamage1stShot, - type PhotoCaptureHUDElementsAddDamage1stShotProps, -} from './PhotoCaptureHUDElementsAddDamage1stShot'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.tsx deleted file mode 100644 index 3b1d127ed..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { PixelDimensions } from '@monkvision/types'; -import { useTranslation } from 'react-i18next'; -import { isMobileDevice } from '@monkvision/common'; -import { PhotoCaptureMode } from '../../hooks'; -import { styles } from './PhotoCaptureHUDElementsAddDamage2ndShot.styles'; -import { PhotoCaptureHUDCounter } from '../PhotoCaptureHUDCounter'; -import { PhotoCaptureHUDCancelButton } from '../PhotoCaptureHUDCancelButton'; -import { usePhotoCaptureHUDElementsAddDamage2ndShotStyle } from './hooks'; - -/** - * Props of the PhotoCaptureHUDElementsAddDamage2ndShot component. - */ -export interface PhotoCaptureHUDElementsAddDamage2ndShotProps { - /** - * Callback called when the user cancels the Add Damage mode. - */ - onCancel?: () => void; - /** - * The dimensions of the Camera video stream. - */ - streamDimensions?: PixelDimensions | null; -} - -function getAspectRatio(streamDimensions?: PixelDimensions | null) { - if (isMobileDevice() && streamDimensions) { - return `${streamDimensions?.width}/${streamDimensions?.height}`; - } - return '16/9'; -} - -/** - * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current - * mode is ADD_DAMAGE_2ND_SHOT. - */ -export function PhotoCaptureHUDElementsAddDamage2ndShot({ - onCancel, - streamDimensions, -}: PhotoCaptureHUDElementsAddDamage2ndShotProps) { - const { t } = useTranslation(); - const style = usePhotoCaptureHUDElementsAddDamage2ndShotStyle(); - - const aspectRatio = getAspectRatio(streamDimensions); - - return ( -
-
-
-
-
- - -
-
{t('photo.hud.addDamage.infoCloseup')}
-
- ); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/index.ts deleted file mode 100644 index 84762a2e9..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { - PhotoCaptureHUDElementsAddDamage2ndShot, - type PhotoCaptureHUDElementsAddDamage2ndShotProps, -} from './PhotoCaptureHUDElementsAddDamage2ndShot'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts deleted file mode 100644 index 6952ada6b..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { PhotoCaptureHUDOverlay } from './PhotoCaptureHUDOverlay'; -export { type PhotoCaptureHUDOverlayProps } from './hooks'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts index 3153a7936..665bb1860 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts @@ -1,2 +1 @@ export * from './usePhotoCaptureHUDStyle'; -export * from './usePhotoCaptureHUDButtonBackground'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts deleted file mode 100644 index aa0ce31db..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useMonkTheme, changeAlpha } from '@monkvision/common'; -import { useMemo } from 'react'; - -/** - * Custom hook used to generate the background color for the buttons in the PhotoCaptureHUD component. - */ -export function usePhotoCaptureHUDButtonBackground() { - const { palette } = useMonkTheme(); - - return useMemo(() => changeAlpha(palette.background.base, 0.64), [palette]); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts index d5b63885b..2898d3856 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts @@ -1,11 +1,5 @@ export { PhotoCaptureHUD, type PhotoCaptureHUDProps } from './PhotoCaptureHUD'; export * from './hooks'; -export * from './PhotoCaptureHUDButtons'; -export * from './PhotoCaptureHUDCancelButton'; -export * from './PhotoCaptureHUDCounter'; -export * from './PhotoCaptureHUDOverlay'; export * from './PhotoCaptureHUDElements'; -export * from './PhotoCaptureHUDElementsAddDamage1stShot'; -export * from './PhotoCaptureHUDElementsAddDamage2ndShot'; export * from './PhotoCaptureHUDElementsSight'; export * from './PhotoCaptureHUDTutorial'; diff --git a/packages/inspection-capture-web/src/components/CancelButton/CancelButton.tsx b/packages/inspection-capture-web/src/components/CancelButton/CancelButton.tsx new file mode 100644 index 000000000..b85956056 --- /dev/null +++ b/packages/inspection-capture-web/src/components/CancelButton/CancelButton.tsx @@ -0,0 +1,27 @@ +import { Button } from '@monkvision/common-ui-web'; +import { useTranslation } from 'react-i18next'; +import { useColorBackground } from '../../hooks'; + +/** + * Props of the CancelButton component. + */ +export interface CancelButtonProps { + /** + * Callback called when the user clicks on the button. + */ + onCancel?: () => void; +} + +/** + * Component implementing a cancel button displayed in the Camera HUD. + */ +export function CancelButton({ onCancel }: CancelButtonProps) { + const { t } = useTranslation(); + const primaryColor = useColorBackground(); + + return ( + + ); +} diff --git a/packages/inspection-capture-web/src/components/CancelButton/index.ts b/packages/inspection-capture-web/src/components/CancelButton/index.ts new file mode 100644 index 000000000..fbc0346fe --- /dev/null +++ b/packages/inspection-capture-web/src/components/CancelButton/index.ts @@ -0,0 +1 @@ +export { CancelButton, type CancelButtonProps } from './CancelButton'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.styles.ts b/packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.styles.ts similarity index 93% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.styles.ts rename to packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.styles.ts index 4f69c890a..7788d0d96 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/PhotoCaptureHUDElementsAddDamage2ndShot.styles.ts +++ b/packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.styles.ts @@ -1,5 +1,5 @@ import { Styles } from '@monkvision/types'; -import { PHOTO_CAPTURE_HUD_BUTTONS_BAR_WIDTH } from '../PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles'; +import { PHOTO_CAPTURE_HUD_BUTTONS_BAR_WIDTH } from '../HUDButtons/HUDButtons.styles'; export const styles: Styles = { container: { @@ -32,6 +32,7 @@ export const styles: Styles = { frameContainer: { position: 'absolute', width: '100%', + maxHeight: '100%', }, frame: { position: 'absolute', diff --git a/packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.tsx b/packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.tsx new file mode 100644 index 000000000..4c4dc4998 --- /dev/null +++ b/packages/inspection-capture-web/src/components/CloseUpShot/CloseUpShot.tsx @@ -0,0 +1,54 @@ +import { PixelDimensions } from '@monkvision/types'; +import { useTranslation } from 'react-i18next'; +import { getAspectRatio } from '@monkvision/common'; +import { styles } from './CloseUpShot.styles'; +import { useCloseUpShotStyle } from './hooks'; +import { Counter } from '../Counter'; +import { CancelButton } from '../CancelButton'; +import { CaptureMode } from '../../types'; + +/** + * Props of the CloseUpShot component. + */ +export interface CloseUpShotProps { + /** + * Callback called when the user cancels the Add Damage mode. + */ + onCancel?: () => void; + /** + * Boolean indicating whether the counter should be displayed. + * + * @default true + */ + showCounter?: boolean; + /** + * The dimensions of the Camera video stream. + */ + streamDimensions?: PixelDimensions | null; +} + +/** + * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current + * mode is ADD_DAMAGE_2ND_SHOT | ADD_DAMAGE_PART_SELECT_SHOT. + */ +export function CloseUpShot({ onCancel, showCounter = true, streamDimensions }: CloseUpShotProps) { + const { t } = useTranslation(); + const style = useCloseUpShotStyle(); + + const aspectRatio = getAspectRatio(streamDimensions); + + return ( +
+
+
+
+
+ {showCounter && } + +
+
+ {t('photo.hud.addDamage.infoCloseup')} +
+
+ ); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/hooks.ts b/packages/inspection-capture-web/src/components/CloseUpShot/hooks.ts similarity index 67% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/hooks.ts rename to packages/inspection-capture-web/src/components/CloseUpShot/hooks.ts index eb778a717..857eece25 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot/hooks.ts +++ b/packages/inspection-capture-web/src/components/CloseUpShot/hooks.ts @@ -1,14 +1,14 @@ import { CSSProperties } from 'react'; import { useResponsiveStyle } from '@monkvision/common'; -import { styles } from './PhotoCaptureHUDElementsAddDamage2ndShot.styles'; +import { styles } from './CloseUpShot.styles'; -export interface PhotoCaptureHUDElementsAddDamage2ndShotStyle { +export interface CloseUpShotStyle { top: CSSProperties; infoCloseup: CSSProperties; frame: CSSProperties; } -export function usePhotoCaptureHUDElementsAddDamage2ndShotStyle(): PhotoCaptureHUDElementsAddDamage2ndShotStyle { +export function useCloseUpShotStyle(): CloseUpShotStyle { const { responsive } = useResponsiveStyle(); return { diff --git a/packages/inspection-capture-web/src/components/CloseUpShot/index.ts b/packages/inspection-capture-web/src/components/CloseUpShot/index.ts new file mode 100644 index 000000000..00369aecf --- /dev/null +++ b/packages/inspection-capture-web/src/components/CloseUpShot/index.ts @@ -0,0 +1,4 @@ +export { + CloseUpShot, + type CloseUpShotProps as PhotoCaptureHUDElementsAddDamage2ndShotProps, +} from './CloseUpShot'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.styles.ts b/packages/inspection-capture-web/src/components/Counter/Counter.styles.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.styles.ts rename to packages/inspection-capture-web/src/components/Counter/Counter.styles.ts diff --git a/packages/inspection-capture-web/src/components/Counter/Counter.tsx b/packages/inspection-capture-web/src/components/Counter/Counter.tsx new file mode 100644 index 000000000..0384f793b --- /dev/null +++ b/packages/inspection-capture-web/src/components/Counter/Counter.tsx @@ -0,0 +1,17 @@ +import { styles } from './Counter.styles'; +import { useColorBackground } from '../../hooks'; +import { CounterProps, useCounterLabel } from './hooks'; + +/** + * Component that implements an indicator of pictures taken during the capture process. + */ +export function Counter(props: CounterProps) { + const label = useCounterLabel(props); + const backgroundColor = useColorBackground(); + + return ( +
+ {label} +
+ ); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts b/packages/inspection-capture-web/src/components/Counter/hooks.ts similarity index 57% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts rename to packages/inspection-capture-web/src/components/Counter/hooks.ts index 942f186cf..6c5ea1e9f 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts +++ b/packages/inspection-capture-web/src/components/Counter/hooks.ts @@ -1,15 +1,15 @@ import { useTranslation } from 'react-i18next'; -import { PhotoCaptureMode } from '../../hooks'; +import { CaptureMode } from '../../types'; /** - * Props of the PhotoCaptureHUDCounter component. + * Props of the Counter component. */ -export type PhotoCaptureHUDCounterProps = +export type CounterProps = | { /** * The current mode of the PhotoCapture component. */ - mode: PhotoCaptureMode.SIGHT; + mode: CaptureMode.SIGHT; /** * The total number of sights given to the PhotoCapture component. */ @@ -23,16 +23,20 @@ export type PhotoCaptureHUDCounterProps = /** * The current mode of the PhotoCapture component. */ - mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT | PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT; + mode: + | CaptureMode.ADD_DAMAGE_1ST_SHOT + | CaptureMode.ADD_DAMAGE_2ND_SHOT + | CaptureMode.ADD_DAMAGE_PART_SELECT + | CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT; }; -export function usePhotoCaptureHUDCounterLabel(props: PhotoCaptureHUDCounterProps): string { +export function useCounterLabel(props: CounterProps): string { const { t } = useTranslation(); - if (props.mode === PhotoCaptureMode.SIGHT) { + if (props.mode === CaptureMode.SIGHT) { return `${props.sightsTaken} / ${props.totalSights}`; } - if (props.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) { + if (props.mode === CaptureMode.ADD_DAMAGE_1ST_SHOT) { return t('photo.hud.addDamage.damagedPartCounter'); } return t('photo.hud.addDamage.closeupPictureCounter'); diff --git a/packages/inspection-capture-web/src/components/Counter/index.ts b/packages/inspection-capture-web/src/components/Counter/index.ts new file mode 100644 index 000000000..908dc74ca --- /dev/null +++ b/packages/inspection-capture-web/src/components/Counter/index.ts @@ -0,0 +1,2 @@ +export { Counter } from './Counter'; +export { type CounterProps } from './hooks'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles.ts b/packages/inspection-capture-web/src/components/HUDButtons/HUDButtons.styles.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles.ts rename to packages/inspection-capture-web/src/components/HUDButtons/HUDButtons.styles.ts diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx b/packages/inspection-capture-web/src/components/HUDButtons/HUDButtons.tsx similarity index 90% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx rename to packages/inspection-capture-web/src/components/HUDButtons/HUDButtons.tsx index 23788e848..99da51790 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx +++ b/packages/inspection-capture-web/src/components/HUDButtons/HUDButtons.tsx @@ -1,11 +1,11 @@ import { Icon, TakePictureButton } from '@monkvision/common-ui-web'; import { useInteractiveStatus } from '@monkvision/common'; -import { useCaptureHUDButtonsStyles } from './hooks'; +import { useHUDButtonsStyles } from './hooks'; /** - * Props of the PhotoCaptureHUDButtons component. + * Props of the HUDButtons component. */ -export interface PhotoCaptureHUDButtonsProps { +export interface HUDButtonsProps { /** * URI of the picture displayed in the gallery button icon. Usually, this is the last picture taken by the user. If no * picture is provided, a gallery icon will be displayed instead. @@ -63,12 +63,12 @@ export interface PhotoCaptureHUDButtonsProps { } /** - * Components implementing the main buttons of the PhotoCapture Camera HUD. This component implements 3 buttons : + * Components implementing the main buttons of the capture Camera HUD. This component implements 3 buttons : * - A take picture button * - A gallery button * - A close button (only displayed if the `onClose` callback is defined) */ -export function PhotoCaptureHUDButtons({ +export function HUDButtons({ galleryPreview, onTakePicture, onOpenGallery, @@ -79,7 +79,7 @@ export function PhotoCaptureHUDButtons({ showCloseButton = false, showGalleryBadge = false, retakeCount = 0, -}: PhotoCaptureHUDButtonsProps) { +}: HUDButtonsProps) { const { status: galleryStatus, eventHandlers: galleryEventHandlers } = useInteractiveStatus({ disabled: galleryDisabled, }); @@ -87,7 +87,7 @@ export function PhotoCaptureHUDButtons({ disabled: closeDisabled, }); const { containerStyle, gallery, galleryBadgeStyle, close, backgroundCoverStyle } = - useCaptureHUDButtonsStyles({ + useHUDButtonsStyles({ galleryStatus, closeStatus, closeBtnAvailable: !!onClose, diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts b/packages/inspection-capture-web/src/components/HUDButtons/hooks.ts similarity index 91% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts rename to packages/inspection-capture-web/src/components/HUDButtons/hooks.ts index 75b04e54f..e2edf247e 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts +++ b/packages/inspection-capture-web/src/components/HUDButtons/hooks.ts @@ -10,9 +10,9 @@ import { captureButtonBackgroundColors, captureButtonForegroundColors, styles, -} from './PhotoCaptureHUDButtons.styles'; +} from './HUDButtons.styles'; -interface PhotoCaptureHUDButtonsStylesParams { +interface HUDButtonsStylesParams { galleryStatus: InteractiveStatus; closeStatus: InteractiveStatus; closeBtnAvailable: boolean; @@ -21,7 +21,7 @@ interface PhotoCaptureHUDButtonsStylesParams { showGalleryBadge?: boolean; } -interface PhotoCaptureHUDButtonsStyles { +interface HUDButtonsStyles { containerStyle: CSSProperties; gallery: { style: CSSProperties; @@ -37,9 +37,7 @@ interface PhotoCaptureHUDButtonsStyles { const ANIMATION_DELAY_MS = 50; -export function useCaptureHUDButtonsStyles( - params: PhotoCaptureHUDButtonsStylesParams, -): PhotoCaptureHUDButtonsStyles { +export function useHUDButtonsStyles(params: HUDButtonsStylesParams): HUDButtonsStyles { const [backgroundAnimationStart, setBackgroundAnimationStart] = useState(false); const { responsive } = useResponsiveStyle(); const { palette } = useMonkTheme(); diff --git a/packages/inspection-capture-web/src/components/HUDButtons/index.ts b/packages/inspection-capture-web/src/components/HUDButtons/index.ts new file mode 100644 index 000000000..cc828c459 --- /dev/null +++ b/packages/inspection-capture-web/src/components/HUDButtons/index.ts @@ -0,0 +1 @@ +export { HUDButtons, type HUDButtonsProps } from './HUDButtons'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts b/packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.styles.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts rename to packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.styles.ts diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx b/packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.tsx similarity index 76% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx rename to packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.tsx index 53485a1d6..54805e80b 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx +++ b/packages/inspection-capture-web/src/components/HUDOverlay/HUDOverlay.tsx @@ -1,23 +1,23 @@ import { useTranslation } from 'react-i18next'; import { Button, Spinner } from '@monkvision/common-ui-web'; import { useResponsiveStyle } from '@monkvision/common'; -import { styles } from './PhotoCaptureHUDOverlay.styles'; -import { PhotoCaptureHUDOverlayProps, usePhotoCaptureErrorLabel, useRetry } from './hooks'; +import { styles } from './HUDOverlay.styles'; +import { HUDOverlayProps, useErrorLabel, useRetry } from './hooks'; /** - * Component that displays an overlay on top of the PhotoCapture component that is used to display elements such as - * error messages, spinning loaders etc. + * Component that displays an overlay on top of the PhotoCapture/DamageDisclosure component that is used + * to display elements such as error messages, spinning loaders etc. */ -export function PhotoCaptureHUDOverlay({ +export function HUDOverlay({ isCaptureLoading, captureError, handle, onRetry, inspectionId, -}: PhotoCaptureHUDOverlayProps) { +}: HUDOverlayProps) { const { t } = useTranslation(); const { responsive } = useResponsiveStyle(); - const error = usePhotoCaptureErrorLabel(captureError, handle, inspectionId); + const error = useErrorLabel(captureError, handle, inspectionId); const handleRetry = useRetry({ captureError, handle, onRetry }); if (!isCaptureLoading && !error) { diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts b/packages/inspection-capture-web/src/components/HUDOverlay/hooks.ts similarity index 91% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts rename to packages/inspection-capture-web/src/components/HUDOverlay/hooks.ts index cc6f8675b..f2a408868 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts +++ b/packages/inspection-capture-web/src/components/HUDOverlay/hooks.ts @@ -5,9 +5,9 @@ import { MonkNetworkError } from '@monkvision/network'; import { PhotoCaptureErrorName } from '../../errors'; /** - * Props of the PhotoCaptureHUDOverlay component. + * Props of the HUDOverlay component. */ -export interface PhotoCaptureHUDOverlayProps { +export interface HUDOverlayProps { /** * Boolean indicating if the global loading state of the PhotoCapture component is loading or not. */ @@ -30,7 +30,7 @@ export interface PhotoCaptureHUDOverlayProps { inspectionId: string; } -export function usePhotoCaptureErrorLabel( +export function useErrorLabel( captureError: unknown | null, handle: CameraHandle, inspectionId: string, @@ -71,7 +71,7 @@ export function useRetry({ captureError, handle, onRetry, -}: Pick): (() => void) | null { +}: Pick): (() => void) | null { if (handle.error) { return handle.retry; } diff --git a/packages/inspection-capture-web/src/components/HUDOverlay/index.ts b/packages/inspection-capture-web/src/components/HUDOverlay/index.ts new file mode 100644 index 000000000..fbc390230 --- /dev/null +++ b/packages/inspection-capture-web/src/components/HUDOverlay/index.ts @@ -0,0 +1,2 @@ +export { HUDOverlay } from './HUDOverlay'; +export { type HUDOverlayProps } from './hooks'; diff --git a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.styles.ts b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.styles.ts new file mode 100644 index 000000000..531e3f5fa --- /dev/null +++ b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.styles.ts @@ -0,0 +1,53 @@ +import { Styles } from '@monkvision/types'; + +export const styles: Styles = { + container: { + position: 'fixed', + display: 'flex', + flexDirection: 'column', + justifyItems: 'center', + alignItems: 'center', + inset: 0, + }, + vehicleSelect: { + alignSelf: 'stretch', + justifySelf: 'stretch', + position: 'fixed', + height: '85%', + width: '75%', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + zIndex: 9, + }, + labelsContainer: { + position: 'fixed', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + top: '10px', + bottom: '10px', + width: '90%', + }, + partsLabel: { + display: 'flex', + justifyContent: 'center', + }, + tutoLabel: { + display: 'flex', + justifyContent: 'center', + }, + closeBtn: { + position: 'fixed', + top: '5px', + left: '5px', + }, + validateBtn: { + position: 'fixed', + top: '50%', + right: '10px', + transform: 'translate(-50%, -50%)', + zIndex: 10, + }, +}; diff --git a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx new file mode 100644 index 000000000..dcc247c36 --- /dev/null +++ b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx @@ -0,0 +1,99 @@ +import { useMonkAppState, useMonkTheme, vehiclePartLabels, getLanguage } from '@monkvision/common'; +import { VehiclePartSelection, Button } from '@monkvision/common-ui-web'; +import { VehiclePart } from '@monkvision/types'; +import { useTranslation } from 'react-i18next'; +import { styles } from './PartSelection.styles'; +import { useColorBackground } from '../../hooks'; + +export interface PartSelectionProps { + /** + * Current vehicle parts selected to take a picture of. + */ + vehicleParts: VehiclePart[]; + /** + * Boolean indicating if the vehicle part selector is currently displayed. + */ + disabled?: boolean; + /** + * Callback called when the user cancels the Add Damage mode. + */ + onCancel?: () => void; + /** + * Callback called when the user selects the parts to take a picture of. + */ + onAddDamagePartsSelected?: (parts: VehiclePart[]) => void; + /** + * Callback called when the user clicks on the "Validate" button of the Add Damage mode. + */ + onValidateVehicleParts?: () => void; + /** + * The maximum number of parts that can be selected. + */ + maxSelectableParts?: number; +} + +export function PartSelection({ + vehicleParts, + disabled = false, + onCancel = () => {}, + onAddDamagePartsSelected = () => {}, + onValidateVehicleParts = () => {}, + maxSelectableParts = 1, +}: PartSelectionProps) { + const { vehicleType } = useMonkAppState(); + const { palette } = useMonkTheme(); + const { i18n, t } = useTranslation(); + const backgroundColor = useColorBackground(0.9); + const buttonColor = useColorBackground(); + + if (!vehicleType) { + throw new Error('Vehicle type state is not found from useMonkAppState'); + } + const instruction = + maxSelectableParts === 1 ? 'photo.hud.addDamage.selectPart' : 'photo.hud.addDamage.selectParts'; + + return disabled ? null : ( +
+
+ +
+ +
+ {vehicleParts.length > 0 && ( + + + + )} +
+
+ ); +} diff --git a/packages/inspection-capture-web/src/components/PartSelection/index.ts b/packages/inspection-capture-web/src/components/PartSelection/index.ts new file mode 100644 index 000000000..f24dd5571 --- /dev/null +++ b/packages/inspection-capture-web/src/components/PartSelection/index.ts @@ -0,0 +1,2 @@ +export { PartSelection } from './PartSelection'; +export { type PartSelectionProps } from './PartSelection'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.styles.ts b/packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.styles.ts similarity index 88% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.styles.ts rename to packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.styles.ts index ecf1b2d66..651e853ba 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/PhotoCaptureHUDElementsAddDamage1stShot.styles.ts +++ b/packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.styles.ts @@ -1,5 +1,5 @@ import { Styles } from '@monkvision/types'; -import { PHOTO_CAPTURE_HUD_BUTTONS_BAR_WIDTH } from '../PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles'; +import { PHOTO_CAPTURE_HUD_BUTTONS_BAR_WIDTH } from '../HUDButtons/HUDButtons.styles'; export const styles: Styles = { container: { diff --git a/packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.tsx b/packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.tsx new file mode 100644 index 000000000..2f4fee1f7 --- /dev/null +++ b/packages/inspection-capture-web/src/components/ZoomOutShot/ZoomOutShot.tsx @@ -0,0 +1,51 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, DynamicSVG } from '@monkvision/common-ui-web'; +import { styles } from './ZoomOutShot.styles'; +import { useColorBackground } from '../../hooks'; +import { CaptureMode } from '../../types'; +import { crosshairSvg } from '../../assets'; +import { useZoomOutShotStyles } from './hooks'; +import { Counter } from '../Counter'; +import { CancelButton } from '../CancelButton'; + +/** + * Props of the ZoomOutShot component. + */ +export interface ZoomOutShotProps { + /** + * Callback called when the user cancels the Add Damage mode. + */ + onCancel?: () => void; +} + +/** + * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current + * mode is ADD_DAMAGE_1ST_SHOT. + */ +export function ZoomOutShot({ onCancel }: ZoomOutShotProps) { + const [showInfoPopup, setShowInfoPopup] = useState(true); + const { t } = useTranslation(); + const primaryColor = useColorBackground(); + const style = useZoomOutShotStyles(); + + return ( +
+ +
+ + +
+ {showInfoPopup && ( + + )} +
+ ); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/hooks.ts b/packages/inspection-capture-web/src/components/ZoomOutShot/hooks.ts similarity index 61% rename from packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/hooks.ts rename to packages/inspection-capture-web/src/components/ZoomOutShot/hooks.ts index deaa2eebb..12d171eaf 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot/hooks.ts +++ b/packages/inspection-capture-web/src/components/ZoomOutShot/hooks.ts @@ -1,13 +1,13 @@ import { CSSProperties } from 'react'; import { useResponsiveStyle } from '@monkvision/common'; -import { styles } from './PhotoCaptureHUDElementsAddDamage1stShot.styles'; +import { styles } from './ZoomOutShot.styles'; -export interface PhotoCaptureHUDElementsAddDamage1stShotStyle { +export interface ZoomOutShotStyle { top: CSSProperties; infoBtn: CSSProperties; } -export function usePhotoCaptureHUDElementsAddDamage1stShotStyles(): PhotoCaptureHUDElementsAddDamage1stShotStyle { +export function useZoomOutShotStyles(): ZoomOutShotStyle { const { responsive } = useResponsiveStyle(); return { diff --git a/packages/inspection-capture-web/src/components/ZoomOutShot/index.ts b/packages/inspection-capture-web/src/components/ZoomOutShot/index.ts new file mode 100644 index 000000000..0b495235e --- /dev/null +++ b/packages/inspection-capture-web/src/components/ZoomOutShot/index.ts @@ -0,0 +1 @@ +export { ZoomOutShot, type ZoomOutShotProps } from './ZoomOutShot'; diff --git a/packages/inspection-capture-web/src/components/index.ts b/packages/inspection-capture-web/src/components/index.ts index 4c205359f..dd4524732 100644 --- a/packages/inspection-capture-web/src/components/index.ts +++ b/packages/inspection-capture-web/src/components/index.ts @@ -1 +1,8 @@ export * from './OrientationEnforcer'; +export * from './CancelButton'; +export * from './CloseUpShot'; +export * from './HUDButtons'; +export * from './PartSelection'; +export * from './Counter'; +export * from './ZoomOutShot'; +export * from './HUDOverlay'; diff --git a/packages/inspection-capture-web/src/translations/de.json b/packages/inspection-capture-web/src/translations/de.json index fe0376ebc..56ac444e3 100644 --- a/packages/inspection-capture-web/src/translations/de.json +++ b/packages/inspection-capture-web/src/translations/de.json @@ -13,7 +13,9 @@ "damagedPartCounter": "1 / 2 • Beschädigtes Teil", "closeupPictureCounter": "2 / 2 • Nahaufnahme-Vorschau", "infoBtn": "Richte das Ziel auf den beschädigten Teil aus und tippe dann auf den Auslöserknopf", - "infoCloseup": "Ein Nahaufnahmebild von dem Schaden machen" + "infoCloseup": "Ein Nahaufnahmebild von dem Schaden machen", + "selectPart": "Wählen Sie auf den beschädigten Teil. Verwenden Sie die Pfeile, um das Fahrzeug zu drehen und die andere Seite zu sehen.", + "selectParts": "Wählen Sie auf die beschädigten Teile. Verwenden Sie die Pfeile, um das Fahrzeug zu drehen und die andere Seite zu sehen." }, "error": { "retry": "Erneut versuchen", @@ -39,6 +41,9 @@ "sight": "Richten Sie das Fahrzeug so gut wie möglich an den Linien aus, um das beste Foto zu erhalten.
Drücken Sie den Auslöser, um das Foto aufzunehmen.", "next": "Weiter" } + }, + "gallery": { + "next": "Weiter: fahrzeugfotos" } }, "video": { diff --git a/packages/inspection-capture-web/src/translations/en.json b/packages/inspection-capture-web/src/translations/en.json index 33f7c9bf1..32fa8fa26 100644 --- a/packages/inspection-capture-web/src/translations/en.json +++ b/packages/inspection-capture-web/src/translations/en.json @@ -13,7 +13,9 @@ "damagedPartCounter": "1 / 2 • Damaged part", "closeupPictureCounter": "2 / 2 • Closeup Picture", "infoBtn": "Aim the target at the damaged part then tap the shutter button", - "infoCloseup": "Take a closeup picture of the damage" + "infoCloseup": "Take a closeup picture of the damage", + "selectPart": "Tap the damaged part. Use arrows to rotate the vehicle and see the other side.", + "selectParts": "Tap the damaged parts. Use arrows to rotate the vehicle and see the other side." }, "error": { "retry": "Retry", @@ -39,6 +41,9 @@ "sight": "Align the vehicle with the lines as much as possible to get the best shot.
Press the shutter button to take the photo.", "next": "Next" } + }, + "gallery": { + "next": "Next: vehicle photos" } }, "video": { diff --git a/packages/inspection-capture-web/src/translations/fr.json b/packages/inspection-capture-web/src/translations/fr.json index 7208579fc..4ceadefe9 100644 --- a/packages/inspection-capture-web/src/translations/fr.json +++ b/packages/inspection-capture-web/src/translations/fr.json @@ -13,7 +13,9 @@ "damagedPartCounter": "1 / 2 • Pièce endommagée", "closeupPictureCounter": "2 / 2 • Dégât en gros plan", "infoBtn": "Placer le viseur sur la pièce endommagée puis enclencher le bouton capture de la photo", - "infoCloseup": "Prendre une photo en gros plan du dégât" + "infoCloseup": "Prendre une photo en gros plan du dégât", + "selectPart": "Veuillez sélectionner la pièce endommagée. Utilisez les flèches pour faire tourner le véhicule et voir l'autre côté", + "selectParts": "Veuillez sélectionner les pièces endommagées. Utilisez les flèches pour faire tourner le véhicule et voir l'autre côté." }, "error": { "retry": "Réessayer", @@ -39,6 +41,9 @@ "sight": "Alignez le véhicule avec les lignes autant que possible pour obtenir la meilleure photo.
Appuyez sur le déclencheur pour prendre la photo.", "next": "Suivant" } + }, + "gallery": { + "next": "Suivant : photos du véhicule" } }, "video": { diff --git a/packages/inspection-capture-web/src/translations/nl.json b/packages/inspection-capture-web/src/translations/nl.json index 27a5bfb34..4f61debb9 100644 --- a/packages/inspection-capture-web/src/translations/nl.json +++ b/packages/inspection-capture-web/src/translations/nl.json @@ -13,7 +13,9 @@ "damagedPartCounter": "1 / 2 • Beschadigd onderdeel", "closeupPictureCounter": "2 / 2 • Closeup foto", "infoBtn": "Richt de markering op het beschadigde onderdeel en tik vervolgens op de sluitertknop", - "infoCloseup": "Neem een closeup foto van de schade" + "infoCloseup": "Neem een closeup foto van de schade", + "selectPart": "Selecteer het beschadigde onderdeel. Gebruik de pijlen om het voertuig te draaien en de andere kant te bekijken.", + "selectParts": "Selecteer de beschadigde onderdelen. Gebruik de pijlen om het voertuig te draaien en de andere kant te bekijken." }, "error": { "retry": "Opnieuw proberen", @@ -39,6 +41,9 @@ "sight": "Richt het voertuig zo goed mogelijk uit op de lijnen om de beste foto te krijgen.
Druk op de knop om de foto te nemen.", "next": "Volgende" } + }, + "gallery": { + "next": "Volgende: voertuigfoto's" } }, "video": { diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx b/packages/inspection-capture-web/test/components/CancelButton.test.tsx similarity index 75% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx rename to packages/inspection-capture-web/test/components/CancelButton.test.tsx index 60f6d36f3..aa92158eb 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx +++ b/packages/inspection-capture-web/test/components/CancelButton.test.tsx @@ -2,9 +2,9 @@ import { render } from '@testing-library/react'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { Button } from '@monkvision/common-ui-web'; import { useTranslation } from 'react-i18next'; -import { PhotoCaptureHUDCancelButton } from '../../../src'; +import { CancelButton } from '../../src/components'; -describe('PhotoCaptureHUDCancelButton component', () => { +describe('CancelButton component', () => { afterEach(() => { jest.clearAllMocks(); }); @@ -13,7 +13,7 @@ describe('PhotoCaptureHUDCancelButton component', () => { const label = 'wow-test-label-yes'; const tMock = jest.fn(() => label); (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock })); - const { unmount } = render(); + const { unmount } = render(); expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.cancelBtn'); expectPropsOnChildMock(Button, { children: label }); @@ -23,7 +23,7 @@ describe('PhotoCaptureHUDCancelButton component', () => { it('should pass the onClick event to the Button', () => { const onCancel = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(Button, { onClick: onCancel }); diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot.test.tsx b/packages/inspection-capture-web/test/components/CloseUpShot.test.tsx similarity index 53% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot.test.tsx rename to packages/inspection-capture-web/test/components/CloseUpShot.test.tsx index 6fef9bb6a..729c75009 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage2ndShot.test.tsx +++ b/packages/inspection-capture-web/test/components/CloseUpShot.test.tsx @@ -1,32 +1,29 @@ -jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({ - PhotoCaptureHUDCounter: jest.fn(() => <>), +const streamDimensions = { width: 5436, height: 1231 }; + +jest.mock('../../src/components/Counter', () => ({ + Counter: jest.fn(() => <>), +})); +jest.mock('../../src/components/CancelButton', () => ({ + CancelButton: jest.fn(() => <>), })); -jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton', () => ({ - PhotoCaptureHUDCancelButton: jest.fn(() => <>), +jest.mock('@monkvision/common', () => ({ + ...jest.requireActual('@monkvision/common'), + getAspectRatio: jest.fn(() => `${streamDimensions?.width}/${streamDimensions?.height}`), })); import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { useTranslation } from 'react-i18next'; -import { - PhotoCaptureHUDCancelButton, - PhotoCaptureHUDCounter, - PhotoCaptureHUDElementsAddDamage2ndShot, -} from '../../../src'; -import { isMobileDevice } from '@monkvision/common'; -import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks'; +import { CancelButton, Counter, CloseUpShot } from '../../src/components'; +import { getAspectRatio } from '@monkvision/common'; +import { CaptureMode } from '../../src/types'; const FRAME_CONTAINER_TEST_ID = 'frame-container'; -describe('PhotoCaptureHUDElementsAddDamage2ndShot component', () => { +describe('CloseUpShot component', () => { it('should display a frame on the center of the screen', () => { - const isMobileDeviceMock = isMobileDevice as jest.Mock; - isMobileDeviceMock.mockReturnValue(true); - const streamDimensions = { width: 5436, height: 1231 }; - const { unmount } = render( - , - ); + const { unmount } = render(); const frameContainerEl = screen.getByTestId(FRAME_CONTAINER_TEST_ID); expect(frameContainerEl.style.aspectRatio).toEqual( @@ -52,27 +49,29 @@ describe('PhotoCaptureHUDElementsAddDamage2ndShot component', () => { }); it('should give the frame container a 16:9 ratio by default if the stream dimensions are not defined', () => { - const { unmount } = render(); + (getAspectRatio as jest.Mock).mockImplementation(() => '16/9'); + const { unmount } = render(); const frameContainerEl = screen.getByTestId(FRAME_CONTAINER_TEST_ID); + expect(frameContainerEl.style.aspectRatio).toEqual('16/9'); unmount(); }); - it('should display the PhotoCaptureHUDCounter in AD 2nd Shot mode', () => { - const { unmount } = render(); + it('should display the Counter in AD 2nd Shot mode', () => { + const { unmount } = render(); - expectPropsOnChildMock(PhotoCaptureHUDCounter, { mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT }); + expectPropsOnChildMock(Counter, { mode: CaptureMode.ADD_DAMAGE_2ND_SHOT }); unmount(); }); it('should display the a cancel button component and pass it the onCancel prop', () => { const onCancel = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); - expectPropsOnChildMock(PhotoCaptureHUDCancelButton, { onCancel }); + expectPropsOnChildMock(CancelButton, { onCancel }); unmount(); }); @@ -81,10 +80,9 @@ describe('PhotoCaptureHUDElementsAddDamage2ndShot component', () => { const label = 'test'; const tMock = jest.fn(() => label); (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock })); - const { unmount } = render(); + const { unmount } = render(); expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.infoCloseup'); - expect(screen.queryByText(label)).not.toBeNull(); unmount(); }); diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx b/packages/inspection-capture-web/test/components/Counter.test.tsx similarity index 65% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx rename to packages/inspection-capture-web/test/components/Counter.test.tsx index 1113addd3..d364d86b7 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx +++ b/packages/inspection-capture-web/test/components/Counter.test.tsx @@ -1,9 +1,9 @@ import { render, screen } from '@testing-library/react'; -import { PhotoCaptureHUDCounter } from '../../../src'; -import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks'; import { useTranslation } from 'react-i18next'; +import { Counter } from '../../src/components'; +import { CaptureMode } from '../../src/types'; -describe('PhotoCaptureHUDCounter component', () => { +describe('Counter component', () => { afterEach(() => { jest.clearAllMocks(); }); @@ -12,11 +12,7 @@ describe('PhotoCaptureHUDCounter component', () => { const totalSights = 51; const sightsTaken = 12; const { unmount } = render( - , + , ); expect(screen.queryByText(`${sightsTaken} / ${totalSights}`)).not.toBeNull(); @@ -28,14 +24,12 @@ describe('PhotoCaptureHUDCounter component', () => { const label = 'fake-label'; const tMock = jest.fn(() => label); (useTranslation as jest.Mock).mockImplementation(() => ({ t: tMock })); - const { unmount, rerender } = render( - , - ); + const { unmount, rerender } = render(); expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.damagedPartCounter'); expect(screen.queryByText(label)).not.toBeNull(); - rerender(); + rerender(); expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.closeupPictureCounter'); expect(screen.queryByText(label)).not.toBeNull(); diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx b/packages/inspection-capture-web/test/components/HUDButtons.test.tsx similarity index 75% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx rename to packages/inspection-capture-web/test/components/HUDButtons.test.tsx index 7b1955f1f..42c29fc03 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx +++ b/packages/inspection-capture-web/test/components/HUDButtons.test.tsx @@ -3,8 +3,8 @@ import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { InteractiveStatus } from '@monkvision/types'; import { TakePictureButton, Icon } from '@monkvision/common-ui-web'; import { fireEvent, render, screen } from '@testing-library/react'; -import { PhotoCaptureHUDButtons } from '../../../src'; -import { captureButtonForegroundColors } from '../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles'; +import { HUDButtons } from '../../src/components'; +import { captureButtonForegroundColors } from '../../src/components/HUDButtons/HUDButtons.styles'; const GALLERY_BTN_TEST_ID = 'monk-gallery-btn'; const GALLERY_BADGE_TEST_ID = 'monk-gallery-badge'; @@ -16,7 +16,7 @@ describe('CaptureHUDButtons component', () => { }); it('should display 3 buttons : open gallery, take picture and close', () => { - const { unmount } = render(); + const { unmount } = render(); expect(Icon).toHaveBeenCalledTimes(2); expect(TakePictureButton).toHaveBeenCalledTimes(1); @@ -26,7 +26,7 @@ describe('CaptureHUDButtons component', () => { describe('Gallery button', () => { it('should not be disabled by default', () => { - const { unmount } = render(); + const { unmount } = render(); const galleryBtnEl = screen.getByTestId(GALLERY_BTN_TEST_ID); expect(galleryBtnEl.getAttribute('disabled')).toBeNull(); @@ -35,7 +35,7 @@ describe('CaptureHUDButtons component', () => { }); it('should be disabled when the galleryDisabled prop is true', () => { - const { unmount } = render(); + const { unmount } = render(); const galleryBtnEl = screen.getByTestId(GALLERY_BTN_TEST_ID); expect(galleryBtnEl.getAttribute('disabled')).toBeDefined(); @@ -45,7 +45,7 @@ describe('CaptureHUDButtons component', () => { it('should get passed the onOpenGallery callback', () => { const onOpenGallery = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); const galleryBtnEl = screen.getByTestId(GALLERY_BTN_TEST_ID); fireEvent.click(galleryBtnEl); @@ -56,7 +56,7 @@ describe('CaptureHUDButtons component', () => { it('should display an image icon when no galleryPreview is provided', () => { const expectedIcon = 'gallery'; - const { unmount } = render(); + const { unmount } = render(); expect((Icon as jest.Mock).mock.calls).toContainEqual([ { @@ -72,7 +72,7 @@ describe('CaptureHUDButtons component', () => { it('should display background image the galleryPreview prop is provided', () => { const uri = 'test-uri'; - const { unmount } = render(); + const { unmount } = render(); const galleryBtnEl = screen.getByTestId(GALLERY_BTN_TEST_ID); const backgroundDiv = galleryBtnEl.querySelector('div'); @@ -83,7 +83,7 @@ describe('CaptureHUDButtons component', () => { }); it('should not display the notification badge if not asked to', () => { - const { unmount } = render(); + const { unmount } = render(); const galleryBadgeEl = screen.getByTestId(GALLERY_BADGE_TEST_ID); expect(galleryBadgeEl).toHaveStyle({ visibility: 'hidden' }); @@ -92,7 +92,7 @@ describe('CaptureHUDButtons component', () => { }); it('should display the notification badge if asked to', () => { - const { unmount } = render(); + const { unmount } = render(); const galleryBadgeEl = screen.getByTestId(GALLERY_BADGE_TEST_ID); expect(galleryBadgeEl).toHaveStyle({ visibility: 'visible' }); @@ -101,9 +101,7 @@ describe('CaptureHUDButtons component', () => { }); it('should display the notification badge with the number of pictures to retake', () => { - const { unmount } = render( - , - ); + const { unmount } = render(); const galleryBadgeEl = screen.getByTestId(GALLERY_BADGE_TEST_ID); expect(galleryBadgeEl.textContent).toBe('10'); @@ -116,7 +114,7 @@ describe('CaptureHUDButtons component', () => { const takePictureButtonMock = TakePictureButton as unknown as jest.Mock; it('should not be disabled by default', () => { - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(takePictureButtonMock, { disabled: false }); @@ -124,7 +122,7 @@ describe('CaptureHUDButtons component', () => { }); it('should be disabled when the takePictureDisabled prop is true', () => { - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(takePictureButtonMock, { disabled: true }); @@ -132,7 +130,7 @@ describe('CaptureHUDButtons component', () => { }); it('should have a size of 85px', () => { - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(takePictureButtonMock, { size: 85 }); @@ -141,7 +139,7 @@ describe('CaptureHUDButtons component', () => { it('should get passed the onTakePicture callback', () => { const onTakePicture = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(takePictureButtonMock, { onClick: onTakePicture }); @@ -151,7 +149,7 @@ describe('CaptureHUDButtons component', () => { describe('Close button', () => { it('should not be displayed by default', () => { - const { unmount } = render(); + const { unmount } = render(); const closeBtn = screen.getByTestId(CLOSE_BTN_TEST_ID); expect(closeBtn).toHaveStyle({ visibility: 'hidden' }); @@ -160,7 +158,7 @@ describe('CaptureHUDButtons component', () => { }); it('should displayed when showCloseButton is true', () => { - const { unmount } = render(); + const { unmount } = render(); const closeBtn = screen.getByTestId(CLOSE_BTN_TEST_ID); expect(closeBtn).toHaveStyle({ visibility: 'visible' }); @@ -169,7 +167,7 @@ describe('CaptureHUDButtons component', () => { }); it('should not be disabled by default', () => { - const { unmount } = render(); + const { unmount } = render(); const closeBtn = screen.getByTestId(CLOSE_BTN_TEST_ID); expect(closeBtn.getAttribute('disabled')).toBeNull(); @@ -178,7 +176,7 @@ describe('CaptureHUDButtons component', () => { }); it('should be disabled when the closeDisabled prop is true', () => { - const { unmount } = render(); + const { unmount } = render(); const closeBtn = screen.getByTestId(CLOSE_BTN_TEST_ID); expect(closeBtn.getAttribute('disabled')).toBeDefined(); @@ -188,7 +186,7 @@ describe('CaptureHUDButtons component', () => { it('should get passed the onClose callback', () => { const onClose = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); const closeBtn = screen.getByTestId(CLOSE_BTN_TEST_ID); fireEvent.click(closeBtn); @@ -199,7 +197,7 @@ describe('CaptureHUDButtons component', () => { it('should display an image icon', () => { const expectedIcon = 'close'; - const { unmount } = render(); + const { unmount } = render(); expect((Icon as jest.Mock).mock.calls).toContainEqual([ { diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx b/packages/inspection-capture-web/test/components/HUDOverlay.test.tsx similarity index 84% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx rename to packages/inspection-capture-web/test/components/HUDOverlay.test.tsx index fc6473d47..8c5edd42b 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx +++ b/packages/inspection-capture-web/test/components/HUDOverlay.test.tsx @@ -11,12 +11,12 @@ import { useTranslation } from 'react-i18next'; import { useObjectTranslation } from '@monkvision/common'; import { MonkNetworkError } from '@monkvision/network'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; -import { PhotoCaptureHUDOverlay, PhotoCaptureHUDOverlayProps } from '../../../src'; -import { PhotoCaptureErrorName } from '../../../src/PhotoCapture/errors'; +import { HUDOverlay, HUDOverlayProps } from '../../src/components'; +import { PhotoCaptureErrorName } from '../../src/errors'; const OVERLAY_TEST_ID = 'overlay'; -function createProps(): PhotoCaptureHUDOverlayProps { +function createProps(): HUDOverlayProps { return { isCaptureLoading: false, captureError: null, @@ -40,14 +40,14 @@ function mockTranslationFunction(returnValue: string, mockTObj = false): jest.Mo return mock; } -describe('PhotoCaptureHUDOverlay component', () => { +describe('HUDOverlay component', () => { afterEach(() => { jest.clearAllMocks(); }); it('should return null there is no loading or error', () => { const props = createProps(); - const { container, unmount } = render(); + const { container, unmount } = render(); expect(container).toBeEmptyDOMElement(); @@ -57,7 +57,7 @@ describe('PhotoCaptureHUDOverlay component', () => { it('should display a fixed overlay on top of the screen', () => { const props = createProps(); props.isCaptureLoading = true; - const { unmount } = render(); + const { unmount } = render(); expect(screen.getByTestId(OVERLAY_TEST_ID)).toHaveStyle({ position: 'absolute', @@ -75,7 +75,7 @@ describe('PhotoCaptureHUDOverlay component', () => { it('should NOT display a spinner on the screen if the camera is loading', () => { const props = createProps(); props.handle.isLoading = true; - const { unmount } = render(); + const { unmount } = render(); expect(Spinner).not.toHaveBeenCalled(); @@ -85,7 +85,7 @@ describe('PhotoCaptureHUDOverlay component', () => { it('should display a spinner on the screen if the capture is loading', () => { const props = createProps(); props.isCaptureLoading = true; - const { unmount } = render(); + const { unmount } = render(); expect(Spinner).toHaveBeenCalled(); @@ -110,14 +110,14 @@ describe('PhotoCaptureHUDOverlay component', () => { ].forEach(({ errors, label }) => { it(`should display the proper error label for ${errors ?? 'unknown capture'} errors`, () => { const props = createProps(); - const { unmount, rerender } = render(); + const { unmount, rerender } = render(); errors.forEach((err) => { const translationLabel = `test-${err}`; const tMock = mockTranslationFunction(translationLabel); props.captureError = new Error(); (props.captureError as Error).name = err ?? 'unknown'; - rerender(); + rerender(); expect(tMock).toHaveBeenCalledWith(label); expect( @@ -137,7 +137,7 @@ describe('PhotoCaptureHUDOverlay component', () => { const tObjMock = mockTranslationFunction(label, true); const props = createProps(); props.handle.error = { type: UserMediaErrorType.OTHER } as UserMediaError; - const { unmount } = render(); + const { unmount } = render(); expect(getCameraErrorLabel).toHaveBeenCalledWith(props.handle.error.type); expect(tObjMock).toHaveBeenCalledWith(obj); @@ -150,7 +150,7 @@ describe('PhotoCaptureHUDOverlay component', () => { it('should display a retry button for camera errors', () => { const props = createProps(); props.handle.error = { type: UserMediaErrorType.OTHER } as UserMediaError; - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(Button, { onClick: props.handle.retry, @@ -170,7 +170,7 @@ describe('PhotoCaptureHUDOverlay component', () => { const props = createProps(); props.captureError = new Error(); (props.captureError as Error).name = error; - const { unmount } = render(); + const { unmount } = render(); expect(Button).not.toHaveBeenCalled(); @@ -182,7 +182,7 @@ describe('PhotoCaptureHUDOverlay component', () => { const props = createProps(); props.captureError = new Error(); (props.captureError as Error).name = 'unknown'; - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(Button, { onClick: props.onRetry, diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot.test.tsx b/packages/inspection-capture-web/test/components/ZoomOutShot.test.tsx similarity index 53% rename from packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot.test.tsx rename to packages/inspection-capture-web/test/components/ZoomOutShot.test.tsx index 8b24952be..a7e456ca7 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsAddDamage1stShot.test.tsx +++ b/packages/inspection-capture-web/test/components/ZoomOutShot.test.tsx @@ -1,48 +1,44 @@ -jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({ - PhotoCaptureHUDCounter: jest.fn(() => <>), +jest.mock('../../src/components/Counter', () => ({ + Counter: jest.fn(() => <>), })); -jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton', () => ({ - PhotoCaptureHUDCancelButton: jest.fn(() => <>), +jest.mock('../../src/components/CancelButton', () => ({ + CancelButton: jest.fn(() => <>), })); import { act, render, screen } from '@testing-library/react'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { Button, DynamicSVG } from '@monkvision/common-ui-web'; import { useTranslation } from 'react-i18next'; -import { - PhotoCaptureHUDCancelButton, - PhotoCaptureHUDCounter, - PhotoCaptureHUDElementsAddDamage1stShot, -} from '../../../src'; -import { crosshairSvg } from '../../../src/assets'; -import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks'; - -describe('PhotoCaptureHUDElementsAddDamage1stShot component', () => { +import { CancelButton, Counter, ZoomOutShot } from '../../src/components'; +import { crosshairSvg } from '../../src/assets'; +import { CaptureMode } from '../../src/types'; + +describe('ZoomOutShot component', () => { afterEach(() => { jest.clearAllMocks(); }); it('should display a crosshair SVG on the screen', () => { - const { unmount } = render(); + const { unmount } = render(); expectPropsOnChildMock(DynamicSVG, { svg: crosshairSvg }); unmount(); }); - it('should display the PhotoCaptureHUDCounter component on AD 1st Shot mode', () => { - const { unmount } = render(); + it('should display the Counter component on AD 1st Shot mode', () => { + const { unmount } = render(); - expectPropsOnChildMock(PhotoCaptureHUDCounter, { mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT }); + expectPropsOnChildMock(Counter, { mode: CaptureMode.ADD_DAMAGE_1ST_SHOT }); unmount(); }); it('should display the a cancel button component and pass it the onCancel prop', () => { const onCancel = jest.fn(); - const { unmount } = render(); + const { unmount } = render(); - expectPropsOnChildMock(PhotoCaptureHUDCancelButton, { onCancel }); + expectPropsOnChildMock(CancelButton, { onCancel }); unmount(); }); @@ -51,7 +47,7 @@ describe('PhotoCaptureHUDElementsAddDamage1stShot component', () => { const label = 'test-label'; const tMock = jest.fn(() => label); (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock })); - const { unmount } = render(); + const { unmount } = render(); expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.infoBtn'); expectPropsOnChildMock(Button, { children: label }); @@ -62,7 +58,7 @@ describe('PhotoCaptureHUDElementsAddDamage1stShot component', () => { it('should remove the info popup when the user clicks on it', () => { const testId = 'button-test-id'; (Button as unknown as jest.Mock).mockImplementation(() =>
); - const { unmount } = render(); + const { unmount } = render(); expect(screen.queryByTestId(testId)).not.toBeNull(); act(() => { From 71653965616ab240a2bd5eb4377e41d4997e8278 Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:14:21 +0100 Subject: [PATCH 07/12] Shared hooks logic between 'PhotoCapture' and 'DamageDisclosure' --- .../PhotoCaptureHUDElements.tsx | 1 + .../src/PhotoCapture/hooks/index.ts | 8 +- .../PhotoCapture/hooks/useAddDamageMode.ts | 80 ---------- .../hooks/usePhotoCaptureSightState.ts | 5 +- packages/inspection-capture-web/src/errors.ts | 3 + .../inspection-capture-web/src/hooks/index.ts | 8 + .../hooks/useAdaptiveCameraConfig.ts | 0 .../src/hooks/useAddDamageMode.ts | 137 ++++++++++++++++++ .../hooks/useBadConnectionWarning.ts | 0 .../src/hooks/useColorBackground.ts | 13 ++ .../hooks/usePhotoCaptureImages.ts | 2 +- .../hooks/usePictureTaken.ts | 49 +++++-- .../{PhotoCapture => }/hooks/useTracking.ts | 0 .../hooks/useUploadQueue.ts | 53 +++++-- packages/inspection-capture-web/src/types.ts | 106 ++++++++++++++ .../hooks/useAddDamageMode.test.ts | 47 ------ .../hooks/useAdaptiveCameraConfig.test.ts | 5 +- .../test/hooks/useAddDamageMode.test.ts | 91 ++++++++++++ .../hooks/useBadConnectionWarning.test.tsx | 2 +- .../hooks/usePhotoCaptureImages.test.ts | 9 +- .../hooks/usePictureTaken.test.ts | 33 +++-- .../hooks/useTracking.test.ts | 2 +- .../hooks/useUploadQueue.test.ts | 12 +- 23 files changed, 476 insertions(+), 190 deletions(-) delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts create mode 100644 packages/inspection-capture-web/src/errors.ts rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/useAdaptiveCameraConfig.ts (100%) create mode 100644 packages/inspection-capture-web/src/hooks/useAddDamageMode.ts rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/useBadConnectionWarning.ts (100%) create mode 100644 packages/inspection-capture-web/src/hooks/useColorBackground.ts rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/usePhotoCaptureImages.ts (84%) rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/usePictureTaken.ts (60%) rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/useTracking.ts (100%) rename packages/inspection-capture-web/src/{PhotoCapture => }/hooks/useUploadQueue.ts (78%) create mode 100644 packages/inspection-capture-web/src/types.ts delete mode 100644 packages/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/useAdaptiveCameraConfig.test.ts (97%) create mode 100644 packages/inspection-capture-web/test/hooks/useAddDamageMode.test.ts rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/useBadConnectionWarning.test.tsx (98%) rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/usePhotoCaptureImages.test.ts (93%) rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/usePictureTaken.test.ts (82%) rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/useTracking.test.ts (96%) rename packages/inspection-capture-web/test/{PhotoCapture => }/hooks/useUploadQueue.test.ts (97%) diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx index 1d2957826..fe320c995 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx @@ -62,6 +62,7 @@ export interface PhotoCaptureHUDElementsProps error?: unknown | null; /** * The current images taken by the user (ignoring retaken pictures etc.). + props.disableSightPicture, */ images: Image[]; } diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts index b6937e82b..f0c83d6a0 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts @@ -1,10 +1,4 @@ export * from './usePhotoCaptureSightState'; -export * from './useAddDamageMode'; -export * from './useUploadQueue'; -export * from './usePictureTaken'; -export * from './usePhotoCaptureImages'; +export * from './useStartTasksOnComplete'; export * from './useComplianceAnalytics'; -export * from './useBadConnectionWarning'; -export * from './useAdaptiveCameraConfig'; -export * from './useTracking'; export * from './usePhotoCaptureTutorial'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts deleted file mode 100644 index 952fd0db9..000000000 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { useCallback, useState } from 'react'; -import { useObjectMemo } from '@monkvision/common'; -import { useAnalytics } from '@monkvision/analytics'; - -/** - * Enum of the different picture taking modes that the PhotoCapture component can be in. - */ -export enum PhotoCaptureMode { - /** - * SIGHT mode : user is asked to take a picture of its vehicle following a given Sight. - */ - SIGHT = 'sight', - /** - * ADD_DAMAGE_1ST_SHOT mode : user is asked to take a picture centered on a damage, far away from the vehicle. - */ - ADD_DAMAGE_1ST_SHOT = 'add_damage_1st_shot', - /** - * ADD_DAMAGE_2ND_SHOT mode : user is asked to take a zoomed picture of a damage on the car. - */ - ADD_DAMAGE_2ND_SHOT = 'add_damage_2nd_shot', -} - -/** - * Handle used to modify the current PhotoCaptureMode of the PhotoCaptureComponent. - */ -export interface AddDamageHandle { - /** - * The current mode of the component. - */ - mode: PhotoCaptureMode; - /** - * Callback to be called when the user clicks on the "Add Damage" button. - */ - handleAddDamage: () => void; - /** - * Callback to be called everytime the user takes a picture to update the mode after it. - */ - updatePhotoCaptureModeAfterPictureTaken: () => void; - /** - * Callback to be called when the user clicks on the "Cancel" button of the Add Damage mode. - */ - handleCancelAddDamage: () => void; -} - -/** - * Custom hook used to switch between sight picture taking and add damage picture taking. - */ -export function useAddDamageMode(): AddDamageHandle { - const [mode, setMode] = useState(PhotoCaptureMode.SIGHT); - const { trackEvent } = useAnalytics(); - - const handleAddDamage = useCallback(() => { - setMode(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT); - trackEvent('AddDamage Selected', { - mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT, - }); - }, []); - - const updatePhotoCaptureModeAfterPictureTaken = useCallback(() => { - setMode((currentMode) => - currentMode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT - ? PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT - : PhotoCaptureMode.SIGHT, - ); - }, []); - - const handleCancelAddDamage = useCallback(() => { - trackEvent('AddDamage Canceled', { - mode, - }); - setMode(PhotoCaptureMode.SIGHT); - }, []); - - return useObjectMemo({ - mode, - handleAddDamage, - updatePhotoCaptureModeAfterPictureTaken, - handleCancelAddDamage, - }); -} diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts index 6a77c5281..43c5c4143 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts @@ -100,6 +100,9 @@ export interface PhotoCaptureSightsParams { * The options for the compliance conf */ complianceOptions: ComplianceOptions; + /** + * Callback used to manually update the completion state of the inspection. + */ setIsInitialInspectionFetched: (state: boolean) => void; /** * Record associating each sight with a list of tasks to execute for it. If not provided, the default tasks of the @@ -223,7 +226,7 @@ export function usePhotoCaptureSightState({ const notCompliantSights = captureSights .map((s) => ({ sight: s, - image: getInspectionImages(inspectionId, state.images, true).find( + image: getInspectionImages(inspectionId, state.images, undefined, true).find( (i) => i.inspectionId === inspectionId && i.sightId === s.id, ), })) diff --git a/packages/inspection-capture-web/src/errors.ts b/packages/inspection-capture-web/src/errors.ts new file mode 100644 index 000000000..ddd92ec89 --- /dev/null +++ b/packages/inspection-capture-web/src/errors.ts @@ -0,0 +1,3 @@ +export enum PhotoCaptureErrorName { + MISSING_TASK_IN_INSPECTION = 'PhotoCaptureMissingTaskInInspection', +} diff --git a/packages/inspection-capture-web/src/hooks/index.ts b/packages/inspection-capture-web/src/hooks/index.ts index 9be9ee168..178a39fbe 100644 --- a/packages/inspection-capture-web/src/hooks/index.ts +++ b/packages/inspection-capture-web/src/hooks/index.ts @@ -1,2 +1,10 @@ export * from './useStartTasksOnComplete'; export * from './useEnforceOrientation'; +export * from './useColorBackground'; +export * from './useAddDamageMode'; +export * from './useUploadQueue'; +export * from './usePictureTaken'; +export * from './usePhotoCaptureImages'; +export * from './useBadConnectionWarning'; +export * from './useAdaptiveCameraConfig'; +export * from './useTracking'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAdaptiveCameraConfig.ts b/packages/inspection-capture-web/src/hooks/useAdaptiveCameraConfig.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/useAdaptiveCameraConfig.ts rename to packages/inspection-capture-web/src/hooks/useAdaptiveCameraConfig.ts diff --git a/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts new file mode 100644 index 000000000..cd346b4ef --- /dev/null +++ b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts @@ -0,0 +1,137 @@ +import { useCallback, useState } from 'react'; +import { useObjectMemo } from '@monkvision/common'; +import { useAnalytics } from '@monkvision/analytics'; +import { AddDamage, CaptureAppConfig, VehiclePart } from '@monkvision/types'; +import { CaptureMode, CaptureScreen } from '../types'; + +interface AddDamageModeParams extends Pick { + /** + * The current screen of the Capture component. + */ + currentScreen?: CaptureScreen; + /** + * Boolean indicating if the only close damage will be taken first then sights picutures. + */ + damageDisclosure?: boolean; + /** + * Callback called when the user clicks on the 'close` button in vehicleDiclosure mode. + */ + handleOpenGallery: () => void; +} + +/** + * Handle used to modify the current PhotoCaptureMode of the PhotoCaptureComponent. + */ +export interface AddDamageHandle { + /** + * The current mode of the component. + */ + mode: CaptureMode; + /** + * Parts selected to take a picture of. + */ + vehicleParts: VehiclePart[]; + /** + * Callback to be called when the user clicks on the "Add Damage" button. + */ + handleAddDamage: () => void; + /** + * Callback to be called when the user selects the parts to take a picture of. + */ + handleAddDamagePartsSelected: (parts: VehiclePart[]) => void; + /** + * Callback to be called everytime the user takes a picture to update the mode after it. + */ + updatePhotoCaptureModeAfterPictureTaken: () => void; + /** + * Callback to be called when the user clicks on the "Cancel" button of the Add Damage mode. + */ + handleCancelAddDamage: () => void; + /** + * Callback called when the user clicks on the "Validate" button of the Add Damage mode. + */ + handleValidateVehicleParts: () => void; +} + +function getInitialMode(addDamage?: AddDamage, damageDisclosure?: boolean) { + if (damageDisclosure && addDamage === AddDamage.PART_SELECT) { + return CaptureMode.ADD_DAMAGE_PART_SELECT; + } + if (damageDisclosure && addDamage === AddDamage.TWO_SHOT) { + return CaptureMode.ADD_DAMAGE_1ST_SHOT; + } + return CaptureMode.SIGHT; +} + +/** + * Custom hook used to switch between sight picture taking and add damage picture taking. + */ +export function useAddDamageMode({ + addDamage, + currentScreen, + handleOpenGallery = () => {}, + damageDisclosure = false, +}: AddDamageModeParams): AddDamageHandle { + const [mode, setMode] = useState(getInitialMode(addDamage, damageDisclosure)); + const [vehicleParts, setVehicleParts] = useState([]); + const { trackEvent } = useAnalytics(); + + const handleAddDamage = useCallback(() => { + if (addDamage === AddDamage.TWO_SHOT) { + setMode(CaptureMode.ADD_DAMAGE_1ST_SHOT); + trackEvent('AddDamage Selected', { + mode: CaptureMode.ADD_DAMAGE_1ST_SHOT, + }); + } + if (addDamage === AddDamage.PART_SELECT) { + setMode(CaptureMode.ADD_DAMAGE_PART_SELECT); + trackEvent('AddDamage Selected', { + mode: CaptureMode.ADD_DAMAGE_PART_SELECT, + }); + } + }, []); + + const updatePhotoCaptureModeAfterPictureTaken = useCallback(() => { + setVehicleParts([]); + setMode( + mode === CaptureMode.ADD_DAMAGE_1ST_SHOT + ? CaptureMode.ADD_DAMAGE_2ND_SHOT + : getInitialMode(addDamage, damageDisclosure), + ); + if (damageDisclosure) { + handleOpenGallery(); + } + }, [mode]); + + const handleCancelAddDamage = useCallback(() => { + trackEvent('AddDamage Canceled', { + mode, + }); + if ( + [CaptureMode.ADD_DAMAGE_PART_SELECT, CaptureMode.ADD_DAMAGE_1ST_SHOT].includes(mode) && + damageDisclosure + ) { + handleOpenGallery(); + } + setVehicleParts([]); + setMode(getInitialMode(addDamage, damageDisclosure)); + }, [mode, currentScreen]); + + const handleAddDamagePartsSelected = useCallback((parts: VehiclePart[]) => { + setVehicleParts(parts); + }, []); + + const handleValidateVehicleParts = useCallback(() => { + setMode(CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT); + }, []); + + return useObjectMemo({ + mode, + vehicleParts, + handleAddDamage, + updatePhotoCaptureModeAfterPictureTaken, + handleCancelAddDamage, + handleAddDamagePartsSelected, + handleValidateVehicleParts, + }); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useBadConnectionWarning.ts b/packages/inspection-capture-web/src/hooks/useBadConnectionWarning.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/useBadConnectionWarning.ts rename to packages/inspection-capture-web/src/hooks/useBadConnectionWarning.ts diff --git a/packages/inspection-capture-web/src/hooks/useColorBackground.ts b/packages/inspection-capture-web/src/hooks/useColorBackground.ts new file mode 100644 index 000000000..502cbc52b --- /dev/null +++ b/packages/inspection-capture-web/src/hooks/useColorBackground.ts @@ -0,0 +1,13 @@ +import { useMonkTheme, changeAlpha } from '@monkvision/common'; +import { useMemo } from 'react'; + +/** + * Custom hook used to generate the background color in the inspection capture web components. + */ +export function useColorBackground(opacity = 0.64) { + const { palette } = useMonkTheme(); + + const clampedOpacity = Math.max(0, Math.min(opacity, 1)); + + return useMemo(() => changeAlpha(palette.background.base, opacity), [palette, clampedOpacity]); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts b/packages/inspection-capture-web/src/hooks/usePhotoCaptureImages.ts similarity index 84% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts rename to packages/inspection-capture-web/src/hooks/usePhotoCaptureImages.ts index 24de30bb2..bc355cbc6 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts +++ b/packages/inspection-capture-web/src/hooks/usePhotoCaptureImages.ts @@ -9,7 +9,7 @@ export function usePhotoCaptureImages(inspectionId: string): Image[] { const { state } = useMonkState(); return useMemo( - () => getInspectionImages(inspectionId, state.images, true), + () => getInspectionImages(inspectionId, state.images, undefined, true), [state.images, inspectionId], ); } diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts b/packages/inspection-capture-web/src/hooks/usePictureTaken.ts similarity index 60% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts rename to packages/inspection-capture-web/src/hooks/usePictureTaken.ts index 6ef3b8f25..ba4a3b2b5 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts +++ b/packages/inspection-capture-web/src/hooks/usePictureTaken.ts @@ -3,8 +3,8 @@ import { Queue } from '@monkvision/common'; import { useCallback } from 'react'; import { useAnalytics } from '@monkvision/analytics'; import { PictureUpload } from './useUploadQueue'; -import { AddDamageHandle, PhotoCaptureMode } from './useAddDamageMode'; -import { PhotoCaptureSightState } from './usePhotoCaptureSightState'; +import { AddDamageHandle } from './useAddDamageMode'; +import { PhotoCaptureSightState, DamageDisclosureState, CaptureMode } from '../types'; /** * Parameters of the usePictureTaken hook. @@ -13,7 +13,7 @@ export interface UseTakePictureParams { /** * The PhotoCapture sight state, created using the usePhotoCaptureSightState hook. */ - sightState: PhotoCaptureSightState; + sightState: PhotoCaptureSightState | DamageDisclosureState; /** * The PhotoCapture add damage handle, created using the useAddDamageMode hook. */ @@ -49,21 +49,38 @@ export function usePictureTaken({ onPictureTaken, }: UseTakePictureParams): HandleTakePictureFunction { const { trackEvent } = useAnalytics(); + + const selectedSightId = 'selectedSight' in sightState ? sightState.selectedSight.id : undefined; + + const takeSelectedSight = + 'takeSelectedSight' in sightState ? sightState.takeSelectedSight : undefined; + return useCallback( (picture: MonkPicture) => { onPictureTaken?.(picture); sightState.setLastPictureTakenUri(picture.uri); - const upload: PictureUpload = - addDamageHandle.mode === PhotoCaptureMode.SIGHT - ? { - mode: addDamageHandle.mode, - picture, - sightId: sightState.selectedSight.id, - tasks: tasksBySight?.[sightState.selectedSight.id] ?? sightState.selectedSight.tasks, - } - : { mode: addDamageHandle.mode, picture }; - uploadQueue.push(upload); - if (addDamageHandle.mode === PhotoCaptureMode.SIGHT) { + if (addDamageHandle.mode === CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT) { + uploadQueue.push({ + mode: addDamageHandle.mode, + picture, + vehicleParts: addDamageHandle.vehicleParts, + }); + } + if (addDamageHandle.mode === CaptureMode.SIGHT && 'selectedSight' in sightState) { + uploadQueue.push({ + mode: addDamageHandle.mode, + picture, + sightId: sightState.selectedSight.id, + tasks: tasksBySight?.[sightState.selectedSight.id] ?? sightState.selectedSight.tasks, + }); + } + if ( + addDamageHandle.mode === CaptureMode.ADD_DAMAGE_1ST_SHOT || + addDamageHandle.mode === CaptureMode.ADD_DAMAGE_2ND_SHOT + ) { + uploadQueue.push({ mode: addDamageHandle.mode, picture }); + } + if (addDamageHandle.mode === CaptureMode.SIGHT && 'takeSelectedSight' in sightState) { sightState.takeSelectedSight(); } else { trackEvent('AddDamage Captured', { @@ -75,10 +92,10 @@ export function usePictureTaken({ [ sightState.setLastPictureTakenUri, addDamageHandle.mode, - sightState.selectedSight.id, + selectedSightId, tasksBySight, uploadQueue.push, - sightState.takeSelectedSight, + takeSelectedSight, addDamageHandle.updatePhotoCaptureModeAfterPictureTaken, onPictureTaken, ], diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useTracking.ts b/packages/inspection-capture-web/src/hooks/useTracking.ts similarity index 100% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/useTracking.ts rename to packages/inspection-capture-web/src/hooks/useTracking.ts diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts b/packages/inspection-capture-web/src/hooks/useUploadQueue.ts similarity index 78% rename from packages/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts rename to packages/inspection-capture-web/src/hooks/useUploadQueue.ts index de9f06ddd..524866778 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts +++ b/packages/inspection-capture-web/src/hooks/useUploadQueue.ts @@ -1,9 +1,15 @@ import { Queue, uniq, useQueue } from '@monkvision/common'; import { AddImageOptions, ImageUploadType, MonkApiConfig, useMonkApi } from '@monkvision/network'; -import { PhotoCaptureAppConfig, ComplianceOptions, MonkPicture, TaskName } from '@monkvision/types'; +import { + CaptureAppConfig, + ComplianceOptions, + MonkPicture, + TaskName, + VehiclePart, +} from '@monkvision/types'; import { useRef } from 'react'; import { useMonitoring } from '@monkvision/monitoring'; -import { PhotoCaptureMode } from './useAddDamageMode'; +import { CaptureMode } from '../types'; /** * Type definition for upload event handlers. @@ -50,7 +56,7 @@ export interface SightPictureUpload { /** * Upload mode : `PhotoCaptureMode.SIGHT`. */ - mode: PhotoCaptureMode.SIGHT; + mode: CaptureMode.SIGHT; /** * The picture to upload. */ @@ -72,7 +78,7 @@ export interface AddDamage1stShotPictureUpload { /** * Upload mode : `PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT`. */ - mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT; + mode: CaptureMode.ADD_DAMAGE_1ST_SHOT; /** * The picture to upload. */ @@ -86,20 +92,39 @@ export interface AddDamage2ndShotPictureUpload { /** * Upload mode : `PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT`. */ - mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT; + mode: CaptureMode.ADD_DAMAGE_2ND_SHOT; /** * The picture to upload. */ picture: MonkPicture; } +/** + * Upload options for a part select picture in the add damage process. + */ +export interface AddDamagePartSelectShotPictureUpload { + /** + * Upload mode : `PhotoCaptureMode.ADD_DAMAGE_PART_SELECTION`. + */ + mode: CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT; + /** + * The picture to upload. + */ + picture: MonkPicture; + /** + * Selected damaged parts. + */ + vehicleParts: VehiclePart[]; +} + /** * Union type describing every possible upload configurations for a picture taken. */ export type PictureUpload = | SightPictureUpload | AddDamage1stShotPictureUpload - | AddDamage2ndShotPictureUpload; + | AddDamage2ndShotPictureUpload + | AddDamagePartSelectShotPictureUpload; function createAddImageOptions( upload: PictureUpload, @@ -109,7 +134,7 @@ function createAddImageOptions( additionalTasks?: PhotoCaptureAppConfig['additionalTasks'], compliance?: ComplianceOptions, ): AddImageOptions { - if (upload.mode === PhotoCaptureMode.SIGHT) { + if (upload.mode === CaptureMode.SIGHT) { return { uploadType: ImageUploadType.BEAUTY_SHOT, picture: upload.picture, @@ -120,11 +145,21 @@ function createAddImageOptions( useThumbnailCaching: enableThumbnail, }; } + if (upload.mode === CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT) { + return { + uploadType: ImageUploadType.PART_SELECT_SHOT, + picture: upload.picture, + inspectionId, + vehicleParts: upload.vehicleParts, + compliance, + useThumbnailCaching: enableThumbnail, + }; + } return { uploadType: ImageUploadType.CLOSE_UP_2_SHOT, picture: upload.picture, siblingKey: `closeup-sibling-key-${siblingId}`, - firstShot: upload.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT, + firstShot: upload.mode === CaptureMode.ADD_DAMAGE_1ST_SHOT, inspectionId, compliance, useThumbnailCaching: enableThumbnail, @@ -146,7 +181,7 @@ export function useUploadQueue({ const { addImage } = useMonkApi(apiConfig); return useQueue(async (upload: PictureUpload) => { - if (upload.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) { + if (upload.mode === CaptureMode.ADD_DAMAGE_1ST_SHOT) { siblingIdRef.current += 1; } try { diff --git a/packages/inspection-capture-web/src/types.ts b/packages/inspection-capture-web/src/types.ts new file mode 100644 index 000000000..da3ec5051 --- /dev/null +++ b/packages/inspection-capture-web/src/types.ts @@ -0,0 +1,106 @@ +import { Sight } from '@monkvision/types'; +import { Dispatch, SetStateAction } from 'react'; + +/** + * Enum of the different picture taking modes that the PhotoCapture or DamageDisclosure component can be in. + */ +export enum CaptureMode { + /** + * SIGHT mode : user is asked to take a picture of its vehicle following a given Sight. + */ + SIGHT = 'sight', + /** + * ADD_DAMAGE_1ST_SHOT mode : user is asked to take a picture centered on a damage, far away from the vehicle. + */ + ADD_DAMAGE_1ST_SHOT = 'add_damage_1st_shot', + /** + * ADD_DAMAGE_2ND_SHOT mode : user is asked to take a zoomed picture of a damage on the car. + */ + ADD_DAMAGE_2ND_SHOT = 'add_damage_2nd_shot', + /** + * ADD_DAMAGE_PART_SELECT mode : user is asked to select car parts to take a picture of. + */ + ADD_DAMAGE_PART_SELECT = 'add_damage_part_select', + /** + * ADD_DAMAGE_PART_SELECT_SHOT mode : user is asked to take a close-up picture of a damage on the car part. + */ + ADD_DAMAGE_PART_SELECT_SHOT = 'add_damage_part_select_shot', +} + +/** + * Enum of the different capture screen available in the capture process. + */ +export enum CaptureScreen { + /** + * Camera scren. + */ + CAMERA = 'camera', + /** + * Gallery screen. + */ + GALLERY = 'gallery', +} + +/** + * Object containing state management utilities for the PhotoCapture sights. + */ +export interface PhotoCaptureSightState { + /** + * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture. + */ + selectedSight: Sight; + /** + * Array containing the list of sights that the user has already captured. + */ + sightsTaken: Sight[]; + /** + * Callback called when the user manually select a new sight. + */ + selectSight: (s: Sight) => void; + /** + * Callback called when the user has taken a picture of a sight. + */ + takeSelectedSight: () => void; + /** + * Callback called when a sight needs to be retaken. + */ + retakeSight: (id: string) => void; + /** + * Value storing the last picture taken by the user. If no picture has been taken yet, this value is null. + */ + lastPictureTakenUri: string | null; + /** + * Callback used to manually update the last picture taken by the user after they take a picture. + */ + setLastPictureTakenUri: (uri: string) => void; + /** + * Callback that can be used to retry fetching this state object from the API in case the previous fetch failed. + */ + retryLoadingInspection: () => void; + /** + * Boolean indicating if the inspection is completed or not. + */ + isInspectionCompleted: boolean; + /** + * Callback used to manually update the completion state of the inspection. + */ + setIsInspectionCompleted: Dispatch>; +} + +/** + * Object containing state management utilities for the PhotoCapture sights. + */ +export interface DamageDisclosureState { + /** + * Value storing the last picture taken by the user. If no picture has been taken yet, this value is null. + */ + lastPictureTakenUri: string | null; + /** + * Callback used to manually update the last picture taken by the user after they take a picture. + */ + setLastPictureTakenUri: (uri: string) => void; + /** + * Callback that can be used to retry fetching this state object from the API in case the previous fetch failed. + */ + retryLoadingInspection: () => void; +} diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts b/packages/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts deleted file mode 100644 index d819ec617..000000000 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { PhotoCaptureMode, useAddDamageMode } from '../../../src/PhotoCapture/hooks'; -import { act } from '@testing-library/react'; - -describe('useAddDamageMode hook', () => { - it('should be in the SIGHT mode by default', () => { - const { result, unmount } = renderHook(useAddDamageMode); - expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT); - unmount(); - }); - - it('should switch to ADD_DAMAGE_1ST_SHOT', () => { - const { result, unmount } = renderHook(useAddDamageMode); - act(() => result.current.handleAddDamage()); - expect(result.current.mode).toEqual(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT); - unmount(); - }); - - it('should switch to ADD_DAMAGE_2ND_SHOT', () => { - const { result, unmount } = renderHook(useAddDamageMode); - act(() => result.current.handleAddDamage()); - act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); - expect(result.current.mode).toEqual(PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT); - unmount(); - }); - - it('should go back to the SIGHT mode', () => { - const { result, unmount } = renderHook(useAddDamageMode); - act(() => result.current.handleAddDamage()); - act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); - act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); - expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT); - unmount(); - }); - - it('should allow to cancel add damage at any time', () => { - const { result, unmount } = renderHook(useAddDamageMode); - act(() => result.current.handleAddDamage()); - act(() => result.current.handleCancelAddDamage()); - expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT); - act(() => result.current.handleAddDamage()); - act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); - act(() => result.current.handleCancelAddDamage()); - expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT); - unmount(); - }); -}); diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useAdaptiveCameraConfig.test.ts b/packages/inspection-capture-web/test/hooks/useAdaptiveCameraConfig.test.ts similarity index 97% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/useAdaptiveCameraConfig.test.ts rename to packages/inspection-capture-web/test/hooks/useAdaptiveCameraConfig.test.ts index 43720db9f..957daa203 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/useAdaptiveCameraConfig.test.ts +++ b/packages/inspection-capture-web/test/hooks/useAdaptiveCameraConfig.test.ts @@ -1,8 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; -import { - useAdaptiveCameraConfig, - UseAdaptiveCameraConfigOptions, -} from '../../../src/PhotoCapture/hooks'; +import { useAdaptiveCameraConfig, UseAdaptiveCameraConfigOptions } from '../../src/hooks'; import { CameraResolution, CompressionFormat } from '@monkvision/types'; import { act } from '@testing-library/react'; diff --git a/packages/inspection-capture-web/test/hooks/useAddDamageMode.test.ts b/packages/inspection-capture-web/test/hooks/useAddDamageMode.test.ts new file mode 100644 index 000000000..10d8fec12 --- /dev/null +++ b/packages/inspection-capture-web/test/hooks/useAddDamageMode.test.ts @@ -0,0 +1,91 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useAddDamageMode } from '../../src/hooks'; +import { CaptureMode } from '../../src/types'; +import { act } from '@testing-library/react'; +import { AddDamage } from '@monkvision/types'; + +describe('useAddDamageMode hook', () => { + it('should be in the SIGHT mode by default if damageDisclosure is false', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.PART_SELECT, + handleOpenGallery: jest.fn(), + damageDisclosure: false, + }, + }); + expect(result.current.mode).toEqual(CaptureMode.SIGHT); + unmount(); + }); + + it('should be in the PART SELECT mode by default if damageDisclosure is true', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.PART_SELECT, + handleOpenGallery: jest.fn(), + damageDisclosure: true, + }, + }); + expect(result.current.mode).toEqual(CaptureMode.ADD_DAMAGE_PART_SELECT); + unmount(); + }); + + it('should switch to ADD_DAMAGE_1ST_SHOT', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.TWO_SHOT, + handleOpenGallery: jest.fn(), + damageDisclosure: false, + }, + }); + act(() => result.current.handleAddDamage()); + expect(result.current.mode).toEqual(CaptureMode.ADD_DAMAGE_1ST_SHOT); + unmount(); + }); + + it('should switch to ADD_DAMAGE_2ND_SHOT', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.TWO_SHOT, + handleOpenGallery: jest.fn(), + damageDisclosure: false, + }, + }); + act(() => result.current.handleAddDamage()); + act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); + expect(result.current.mode).toEqual(CaptureMode.ADD_DAMAGE_2ND_SHOT); + unmount(); + }); + + it('should go back to the SIGHT mode', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.TWO_SHOT, + handleOpenGallery: jest.fn(), + damageDisclosure: false, + }, + }); + act(() => result.current.handleAddDamage()); + act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); + act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); + expect(result.current.mode).toEqual(CaptureMode.SIGHT); + unmount(); + }); + + it('should allow to cancel add damage at any time', () => { + const { result, unmount } = renderHook(useAddDamageMode, { + initialProps: { + addDamage: AddDamage.TWO_SHOT, + handleOpenGallery: jest.fn(), + damageDisclosure: false, + }, + }); + act(() => result.current.handleAddDamage()); + act(() => result.current.handleCancelAddDamage()); + expect(result.current.mode).toEqual(CaptureMode.SIGHT); + act(() => result.current.handleAddDamage()); + act(() => result.current.updatePhotoCaptureModeAfterPictureTaken()); + act(() => result.current.handleCancelAddDamage()); + expect(result.current.mode).toEqual(CaptureMode.SIGHT); + unmount(); + }); +}); diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useBadConnectionWarning.test.tsx b/packages/inspection-capture-web/test/hooks/useBadConnectionWarning.test.tsx similarity index 98% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/useBadConnectionWarning.test.tsx rename to packages/inspection-capture-web/test/hooks/useBadConnectionWarning.test.tsx index 2eebf935c..aa0c2d121 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/useBadConnectionWarning.test.tsx +++ b/packages/inspection-capture-web/test/hooks/useBadConnectionWarning.test.tsx @@ -1,5 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useBadConnectionWarning } from '../../../src/PhotoCapture/hooks'; +import { useBadConnectionWarning } from '../../src/hooks'; import { act } from '@testing-library/react'; import { createFakePromise } from '@monkvision/test-utils'; diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts b/packages/inspection-capture-web/test/hooks/usePhotoCaptureImages.test.ts similarity index 93% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts rename to packages/inspection-capture-web/test/hooks/usePhotoCaptureImages.test.ts index 6542682b7..41aa2250a 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts +++ b/packages/inspection-capture-web/test/hooks/usePhotoCaptureImages.test.ts @@ -6,7 +6,7 @@ jest.mock('@monkvision/common', () => ({ getInspectionImages: jest.fn(() => inspectionImagesMock), })); -import { usePhotoCaptureImages } from '../../../src/PhotoCapture/hooks'; +import { usePhotoCaptureImages } from '../../src/hooks'; import { renderHook } from '@testing-library/react-hooks'; import { getInspectionImages, useMonkState } from '@monkvision/common'; @@ -20,7 +20,12 @@ describe('usePhotoCaptureImages hook', () => { const { result, unmount } = renderHook(usePhotoCaptureImages, { initialProps: inspectionId }); expect(useMonkState).toHaveBeenCalled(); - expect(getInspectionImages).toHaveBeenCalledWith(inspectionId, stateMock.images, true); + expect(getInspectionImages).toHaveBeenCalledWith( + inspectionId, + stateMock.images, + undefined, + true, + ); expect(result.current).toEqual(inspectionImagesMock); unmount(); diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts b/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts similarity index 82% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts rename to packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts index 9f673d0cd..b6b2f7b30 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts +++ b/packages/inspection-capture-web/test/hooks/usePictureTaken.test.ts @@ -1,11 +1,8 @@ import { TaskName, MonkPicture } from '@monkvision/types'; -import { - AddDamageHandle, - PhotoCaptureMode, - usePictureTaken, - UseTakePictureParams, -} from '../../../src/PhotoCapture/hooks'; +import { AddDamageHandle, usePictureTaken, UseTakePictureParams } from '../../src/hooks'; +import { CaptureMode } from '../../src/types'; import { renderHook } from '@testing-library/react-hooks'; +import { PhotoCaptureSightState } from '../../src/PhotoCapture/hooks'; function createParams(): UseTakePictureParams { return { @@ -15,7 +12,7 @@ function createParams(): UseTakePictureParams { selectedSight: { id: 'test-selected-sight', tasks: [TaskName.WHEEL_ANALYSIS] }, }, addDamageHandle: { - mode: PhotoCaptureMode.SIGHT, + mode: CaptureMode.SIGHT, updatePhotoCaptureModeAfterPictureTaken: jest.fn(), }, uploadQueue: { @@ -62,8 +59,8 @@ describe('usePictureTaken hook', () => { expect(initialProps.uploadQueue.push).toHaveBeenCalledWith({ mode: initialProps.addDamageHandle.mode, picture, - sightId: initialProps.sightState.selectedSight.id, - tasks: initialProps.sightState.selectedSight.tasks, + sightId: (initialProps.sightState as PhotoCaptureSightState).selectedSight.id, + tasks: (initialProps.sightState as PhotoCaptureSightState).selectedSight.tasks, }); unmount(); @@ -74,7 +71,7 @@ describe('usePictureTaken hook', () => { const initialProps = { ...createParams(), tasksBySight: { - [createParams().sightState.selectedSight.id]: tasks, + [(createParams().sightState as PhotoCaptureSightState).selectedSight.id]: tasks, }, }; const { result, unmount } = renderHook(usePictureTaken, { initialProps }); @@ -93,7 +90,7 @@ describe('usePictureTaken hook', () => { ...defaultParams, addDamageHandle: { ...defaultParams.addDamageHandle, - mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT, + mode: CaptureMode.ADD_DAMAGE_2ND_SHOT, } as unknown as AddDamageHandle, }; const { result, unmount } = renderHook(usePictureTaken, { initialProps }); @@ -113,9 +110,13 @@ describe('usePictureTaken hook', () => { const initialProps = createParams(); const { result, unmount } = renderHook(usePictureTaken, { initialProps }); - expect(initialProps.sightState.takeSelectedSight).not.toHaveBeenCalled(); + expect( + (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + ).not.toHaveBeenCalled(); result.current(createMonkPicture()); - expect(initialProps.sightState.takeSelectedSight).toHaveBeenCalled(); + expect( + (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + ).toHaveBeenCalled(); unmount(); }); @@ -126,13 +127,15 @@ describe('usePictureTaken hook', () => { ...defaultParams, addDamageHandle: { ...defaultParams.addDamageHandle, - mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT, + mode: CaptureMode.ADD_DAMAGE_2ND_SHOT, } as unknown as AddDamageHandle, }; const { result, unmount } = renderHook(usePictureTaken, { initialProps }); result.current(createMonkPicture()); - expect(initialProps.sightState.takeSelectedSight).not.toHaveBeenCalled(); + expect( + (initialProps.sightState as PhotoCaptureSightState).takeSelectedSight, + ).not.toHaveBeenCalled(); unmount(); }); diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useTracking.test.ts b/packages/inspection-capture-web/test/hooks/useTracking.test.ts similarity index 96% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/useTracking.test.ts rename to packages/inspection-capture-web/test/hooks/useTracking.test.ts index 706f005c4..690639dcd 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/useTracking.test.ts +++ b/packages/inspection-capture-web/test/hooks/useTracking.test.ts @@ -1,5 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useTracking, TrackingParams } from '../../../src/PhotoCapture/hooks'; +import { useTracking, TrackingParams } from '../../src/hooks'; import { useAnalytics } from '@monkvision/analytics'; import { useMonitoring } from '@monkvision/monitoring'; import { decodeMonkJwt } from '@monkvision/network'; diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts b/packages/inspection-capture-web/test/hooks/useUploadQueue.test.ts similarity index 97% rename from packages/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts rename to packages/inspection-capture-web/test/hooks/useUploadQueue.test.ts index dd4b91421..68cc81c80 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts +++ b/packages/inspection-capture-web/test/hooks/useUploadQueue.test.ts @@ -3,10 +3,10 @@ import { renderHook } from '@testing-library/react-hooks'; import { AddDamage1stShotPictureUpload, AddDamage2ndShotPictureUpload, - PhotoCaptureMode, UploadQueueParams, useUploadQueue, -} from '../../../src/PhotoCapture/hooks'; +} from '../../src/hooks'; +import { CaptureMode } from '../../src/types'; import { ComplianceIssue, TaskName } from '@monkvision/types'; import { ImageUploadType, useMonkApi } from '@monkvision/network'; import { useMonitoring } from '@monkvision/monitoring'; @@ -41,7 +41,7 @@ function createParams(): UploadQueueParams { } const defaultUploadOptions = { - mode: PhotoCaptureMode.SIGHT, + mode: CaptureMode.SIGHT, picture: { uri: 'test-monk-uri', mimetype: 'test-mimetype', @@ -132,7 +132,7 @@ describe('useUploadQueue hook', () => { const process = (useQueue as jest.Mock).mock.calls[0][0]; const upload1: AddDamage1stShotPictureUpload = { - mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT, + mode: CaptureMode.ADD_DAMAGE_1ST_SHOT, picture: { blob: { size: 42 } as Blob, uri: 'test-monk-uri-1', @@ -157,7 +157,7 @@ describe('useUploadQueue hook', () => { addImageMock.mockClear(); const upload2: AddDamage2ndShotPictureUpload = { - mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT, + mode: CaptureMode.ADD_DAMAGE_2ND_SHOT, picture: { blob: { size: 42 } as Blob, uri: 'test-monk-uri-2', @@ -181,7 +181,7 @@ describe('useUploadQueue hook', () => { addImageMock.mockClear(); await act(async () => { await process({ - mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT, + mode: CaptureMode.ADD_DAMAGE_1ST_SHOT, picture: { uri: 'test-monk-uri-3', mimetype: 'test-mimetype-3', From 18f47e4003ffedab4f622cacab61b1fde4ecf46e Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 2 Jan 2025 16:16:47 +0100 Subject: [PATCH 08/12] Added 'part_select' addDamage feature + fixed import from common hooks and components --- documentation/src/utils/schemas.ts | 328 ++++++++++++++++++ .../src/PhotoCapture/PhotoCapture.tsx | 40 ++- .../PhotoCaptureHUD/PhotoCaptureHUD.tsx | 72 ++-- .../PhotoCaptureHUDElements.tsx | 48 ++- .../AddDamageButton/AddDamageButton.tsx | 11 +- .../PhotoCaptureHUDElementsSight.tsx | 15 +- .../SightGuideline/SightGuideline.styles.ts | 2 +- .../SightGuideline/SightGuideline.tsx | 16 +- .../SightSlider/SightSlider.styles.ts | 2 +- .../PhotoCaptureHUDElementsSight/hooks.ts | 5 +- .../PhotoCaptureHUDTutorial.styles.ts | 2 +- .../PhotoCaptureHUDTutorial.tsx | 9 +- .../test/PhotoCapture/PhotoCapture.test.tsx | 42 ++- .../PhotoCaptureHUD/PhotoCaptureHUD.test.tsx | 35 +- .../PhotoCaptureHUDElements.test.tsx | 48 ++- .../AddDamageButton.test.tsx | 9 +- .../PhotoCaptureHUDElementsSight.test.tsx | 13 +- .../PhotoCaptureHUDTutorial.test.tsx | 4 +- 18 files changed, 546 insertions(+), 155 deletions(-) create mode 100644 documentation/src/utils/schemas.ts diff --git a/documentation/src/utils/schemas.ts b/documentation/src/utils/schemas.ts new file mode 100644 index 000000000..356fae305 --- /dev/null +++ b/documentation/src/utils/schemas.ts @@ -0,0 +1,328 @@ +import { z, CustomErrorParams } from 'zod'; +import { + AddDamage, + CameraResolution, + ComplianceIssue, + CompressionFormat, + CurrencyCode, + DeviceOrientation, + MileageUnit, + MonkApiPermission, + PhotoCaptureTutorialOption, + SteeringWheelPosition, + TaskName, + VehicleType, +} from '@monkvision/types'; +import { sights } from '@monkvision/sights'; +import { flatten } from '@monkvision/common'; + +function isValidSightId(sightId: string): boolean { + return !!sights[sightId]; +} + +function validateSightIds(value?: string[] | Record): boolean { + if (!value) { + return true; + } + const sightIds = Array.isArray(value) ? value : Object.keys(value); + return sightIds.every(isValidSightId); +} + +function getInvalidSightIdsMessage(value?: string[] | Record): CustomErrorParams { + if (!value) { + return {}; + } + const sightIds = Array.isArray(value) ? value : Object.keys(value); + const invalidIds = sightIds.filter((sightId) => !isValidSightId(sightId)).join(', '); + const plural = invalidIds.length > 1 ? 's' : ''; + return { message: `Invalid sight ID${plural} : ${invalidIds}` }; +} + +function getAllSightsByVehicleType( + vehicleSights?: Partial>, +): string[] | undefined { + return vehicleSights ? flatten(Object.values(vehicleSights)) : undefined; +} + +export const CompressionOptionsSchema = z.object({ + format: z.nativeEnum(CompressionFormat), + quality: z.number().gte(0).lte(1), +}); + +export const CameraConfigSchema = z + .object({ + resolution: z.nativeEnum(CameraResolution).optional(), + allowImageUpscaling: z.boolean().optional(), + }) + .and(CompressionOptionsSchema.partial()); + +export const CustomComplianceThresholdsSchema = z + .object({ + blurriness: z.number().gte(0).lte(1).optional(), + overexposure: z.number().gte(0).lte(1).optional(), + underexposure: z.number().gte(0).lte(1).optional(), + lensFlare: z.number().gte(0).lte(1).optional(), + wetness: z.number().gte(0).lte(1).optional(), + snowness: z.number().gte(0).lte(1).optional(), + dirtiness: z.number().gte(0).lte(1).optional(), + reflections: z.number().gte(0).lte(1).optional(), + zoom: z + .object({ + min: z.number().gte(0).lte(1), + max: z.number().gte(0).lte(1), + }) + .optional(), + }) + .refine((thresholds) => !thresholds.zoom || thresholds.zoom.min < thresholds.zoom.max, { + message: 'Min zoom threshold must be smaller than max zoom threshold', + }); + +export const ComplianceOptionsSchema = z.object({ + enableCompliance: z.boolean().optional(), + enableCompliancePerSight: z + .array(z.string()) + .optional() + .refine(validateSightIds, getInvalidSightIdsMessage), + complianceIssues: z.array(z.nativeEnum(ComplianceIssue)).optional(), + complianceIssuesPerSight: z + .record(z.string(), z.array(z.nativeEnum(ComplianceIssue))) + .optional() + .refine(validateSightIds, getInvalidSightIdsMessage), + useLiveCompliance: z.boolean().optional(), + customComplianceThresholds: CustomComplianceThresholdsSchema.optional(), + customComplianceThresholdsPerSight: z + .record(z.string(), CustomComplianceThresholdsSchema) + .optional() + .refine(validateSightIds, getInvalidSightIdsMessage), +}); + +export const SightGuidelineSchema = z.object({ + sightIds: z.array(z.string()), + en: z.string(), + fr: z.string(), + de: z.string(), + nl: z.string(), +}); + +export const AccentColorVariantsSchema = z.object({ + xdark: z.string(), + dark: z.string(), + base: z.string(), + light: z.string(), + xlight: z.string(), +}); + +export const TextColorVariantsSchema = z.object({ + primary: z.string(), + secondary: z.string(), + disabled: z.string(), + white: z.string(), + black: z.string(), + link: z.string(), + linkInverted: z.string(), +}); + +export const BackgroundColorVariantsSchema = z.object({ + dark: z.string(), + base: z.string(), + light: z.string(), +}); + +export const SurfaceColorVariantsSchema = z.object({ + dark: z.string(), + light: z.string(), +}); + +export const OutlineColorVariantsSchema = z.object({ + base: z.string(), +}); + +export const MonkPaletteSchema = z.object({ + primary: AccentColorVariantsSchema, + secondary: AccentColorVariantsSchema, + alert: AccentColorVariantsSchema, + caution: AccentColorVariantsSchema, + success: AccentColorVariantsSchema, + information: AccentColorVariantsSchema, + text: TextColorVariantsSchema, + background: BackgroundColorVariantsSchema, + surface: SurfaceColorVariantsSchema, + outline: OutlineColorVariantsSchema, +}); + +export const SightsByVehicleTypeSchema = z + .record(z.nativeEnum(VehicleType), z.array(z.string())) + .refine( + (vehicleSights) => validateSightIds(getAllSightsByVehicleType(vehicleSights)), + (vehicleSights) => getInvalidSightIdsMessage(getAllSightsByVehicleType(vehicleSights)), + ); + +export const SteeringWheelDiscriminatedUnionSchema = z.discriminatedUnion( + 'enableSteeringWheelPosition', + [ + z.object({ + enableSteeringWheelPosition: z.literal(false), + sights: SightsByVehicleTypeSchema, + }), + z.object({ + enableSteeringWheelPosition: z.literal(true), + defaultSteeringWheelPosition: z.nativeEnum(SteeringWheelPosition), + sights: z.record(z.nativeEnum(SteeringWheelPosition), SightsByVehicleTypeSchema), + }), + ], +); + +export const TaskCallbackOptionsSchema = z.object({ + url: z.string(), + headers: z.record(z.string(), z.string()), + params: z.record(z.string(), z.unknown()).optional(), + event: z.string().optional(), +}); + +export const CreateDamageDetectionTaskOptionsSchema = z.object({ + name: z.literal(TaskName.DAMAGE_DETECTION), + damageScoreThreshold: z.number().gte(0).lte(1).optional(), + generateDamageVisualOutput: z.boolean().optional(), + generateSubimageDamages: z.boolean().optional(), + generateSubimageParts: z.boolean().optional(), +}); + +export const CreateHinlTaskOptionsSchema = z.object({ + name: z.literal(TaskName.HUMAN_IN_THE_LOOP), + callbacks: z.array(TaskCallbackOptionsSchema).optional(), +}); + +export const CreatePricingTaskOptionsSchema = z.object({ + name: z.literal(TaskName.PRICING), + outputFormat: z.string().optional(), + config: z.string().optional(), + methodology: z.string().optional(), +}); + +export const InspectionCreateTaskSchema = z + .nativeEnum(TaskName) + .or(CreateDamageDetectionTaskOptionsSchema) + .or(CreateHinlTaskOptionsSchema) + .or(CreatePricingTaskOptionsSchema); + +export const AdditionalDataSchema = z.record(z.string(), z.unknown()); + +export const InspectionCreateVehicleSchema = z.object({ + brand: z.string().optional(), + model: z.string().optional(), + plate: z.string().optional(), + type: z.string().optional(), + mileageUnit: z.nativeEnum(MileageUnit).optional(), + mileageValue: z.number().optional(), + marketValueUnit: z.nativeEnum(CurrencyCode).optional(), + marketValue: z.number().optional(), + vin: z.string().optional(), + color: z.string().optional(), + exteriorCleanliness: z.string().optional(), + interiorCleanliness: z.string().optional(), + dateOfCirculation: z.string().optional(), + duplicateKeys: z.boolean().optional(), + expertiseRequested: z.boolean().optional(), + carRegistration: z.boolean().optional(), + vehicleQuotation: z.number().optional(), + tradeInOffer: z.number().optional(), + ownerInfo: z.record(z.string().optional(), z.unknown()).optional(), + additionalData: AdditionalDataSchema.optional(), +}); + +export const CreateInspectionOptionsSchema = z.object({ + tasks: z.array(InspectionCreateTaskSchema), + vehicle: InspectionCreateVehicleSchema.optional(), + useDynamicCrops: z.boolean().optional(), + enablePricingV1: z.boolean().optional(), + additionalData: AdditionalDataSchema.optional(), +}); + +export const CreateInspectionDiscriminatedUnionSchema = z.discriminatedUnion( + 'allowCreateInspection', + [ + z.object({ + allowCreateInspection: z.literal(false), + }), + z.object({ + allowCreateInspection: z.literal(true), + createInspectionOptions: CreateInspectionOptionsSchema, + }), + ], +); + +const domainsByEnv = { + staging: { + api: 'api.staging.monk.ai/v1', + thumbnail: 'europe-west1-monk-staging-321715.cloudfunctions.net/image_resize', + }, + preview: { + api: 'api.preview.monk.ai/v1', + thumbnail: 'europe-west1-monk-preview-321715.cloudfunctions.net/image_resize', + }, + production: { + api: 'api.monk.ai/v1', + thumbnail: 'europe-west1-monk-prod.cloudfunctions.net/image_resize', + }, +}; + +const apiDomains = Object.values(domainsByEnv).map((env) => env.api) as [string, ...string[]]; +const thumbnailDomains = Object.values(domainsByEnv).map((env) => env.thumbnail) as [ + string, + ...string[], +]; + +export const DomainsSchema = z + .object({ + apiDomain: z.enum(apiDomains), + thumbnailDomain: z.enum(thumbnailDomains), + }) + .refine( + (data) => { + const apiEnv = Object.values(domainsByEnv).find((env) => env.api === data.apiDomain); + const thumbnailEnv = Object.values(domainsByEnv).find( + (env) => env.thumbnail === data.thumbnailDomain, + ); + return !!apiEnv && apiEnv === thumbnailEnv; + }, + (data) => ({ + message: `The selected thumbnailDomain must correspond to the selected apiDomain. Please use the corresponding thumbnailDomain: ${ + thumbnailDomains[apiDomains.indexOf(data.apiDomain)] + }`, + path: ['thumbnailDomain'], + }), + ); + +export const LiveConfigSchema = z + .object({ + id: z.string(), + description: z.string(), + additionalTasks: z.array(z.nativeEnum(TaskName)).optional(), + tasksBySight: z.record(z.string(), z.array(z.nativeEnum(TaskName))).optional(), + startTasksOnComplete: z + .boolean() + .or(z.array(z.nativeEnum(TaskName))) + .optional(), + showCloseButton: z.boolean().optional(), + enforceOrientation: z.nativeEnum(DeviceOrientation).optional(), + maxUploadDurationWarning: z.number().positive().or(z.literal(-1)).optional(), + useAdaptiveImageQuality: z.boolean().optional(), + allowSkipRetake: z.boolean().optional(), + addDamage: z.nativeEnum(AddDamage).optional(), + enableSightGuidelines: z.boolean().optional(), + sightGuidelines: z.array(SightGuidelineSchema).optional(), + enableTutorial: z.nativeEnum(PhotoCaptureTutorialOption).optional(), + allowSkipTutorial: z.boolean().optional(), + enableSightTutorial: z.boolean().optional(), + defaultVehicleType: z.nativeEnum(VehicleType), + allowManualLogin: z.boolean(), + allowVehicleTypeSelection: z.boolean(), + fetchFromSearchParams: z.boolean(), + requiredApiPermissions: z.array(z.nativeEnum(MonkApiPermission)).optional(), + palette: MonkPaletteSchema.partial().optional(), + }) + .and(DomainsSchema) + .and(SteeringWheelDiscriminatedUnionSchema) + .and(CreateInspectionDiscriminatedUnionSchema) + .and(CameraConfigSchema) + .and(ComplianceOptionsSchema); diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx index 7f156294f..af0adba4d 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx @@ -10,6 +10,7 @@ import { import { useMonitoring } from '@monkvision/monitoring'; import { MonkApiConfig } from '@monkvision/network'; import { + AddDamage, CameraConfig, ComplianceOptions, MonkPicture, @@ -23,17 +24,20 @@ import { styles } from './PhotoCapture.styles'; import { PhotoCaptureHUD, PhotoCaptureHUDProps } from './PhotoCaptureHUD'; import { useStartTasksOnComplete } from '../hooks'; import { - useAdaptiveCameraConfig, - useAddDamageMode, - useBadConnectionWarning, useComplianceAnalytics, - usePhotoCaptureImages, usePhotoCaptureSightState, usePhotoCaptureTutorial, + useStartTasksOnComplete, +} from './hooks'; +import { usePictureTaken, - useTracking, + useAddDamageMode, useUploadQueue, -} from './hooks'; + usePhotoCaptureImages, + useAdaptiveCameraConfig, + useBadConnectionWarning, + useTracking, +} from '../hooks'; /** * Props of the PhotoCapture component. @@ -50,7 +54,7 @@ export interface PhotoCaptureProps | 'showCloseButton' | 'enforceOrientation' | 'allowSkipRetake' - | 'enableAddDamage' + | 'addDamage' | 'sightGuidelines' | 'enableSightGuidelines' | 'enableTutorial' @@ -124,7 +128,7 @@ export function PhotoCapture({ customComplianceThresholdsPerSight, useLiveCompliance = false, allowSkipRetake = false, - enableAddDamage = true, + addDamage = AddDamage.PART_SELECT, sightGuidelines, enableTutorial = PhotoCaptureTutorialOption.FIRST_TIME_ONLY, allowSkipTutorial = true, @@ -151,7 +155,14 @@ export function PhotoCapture({ const [currentScreen, setCurrentScreen] = useState(PhotoCaptureScreen.CAMERA); const analytics = useAnalytics(); const loading = useLoadingState(); - const addDamageHandle = useAddDamageMode(); + const handleOpenGallery = () => { + setCurrentScreen(PhotoCaptureScreen.GALLERY); + analytics.trackEvent('Gallery Opened'); + }; + const addDamageHandle = useAddDamageMode({ + addDamage, + handleOpenGallery, + }); useTracking({ inspectionId, authToken: apiConfig.authToken }); const { setIsInitialInspectionFetched } = useComplianceAnalytics({ inspectionId, sights }); const { adaptiveCameraConfig, uploadEventHandlers: adaptiveUploadEventHandlers } = @@ -206,10 +217,6 @@ export function PhotoCapture({ tasksBySight, onPictureTaken, }); - const handleOpenGallery = () => { - setCurrentScreen(PhotoCaptureScreen.GALLERY); - analytics.trackEvent('Gallery Opened'); - }; const handleGalleryBack = () => setCurrentScreen(PhotoCaptureScreen.CAMERA); const handleNavigateToCapture = (options: NavigateToCaptureOptions) => { if (options.reason === NavigateToCaptureReason.ADD_DAMAGE) { @@ -249,10 +256,12 @@ export function PhotoCapture({ sightsTaken: sightState.sightsTaken, lastPictureTakenUri: sightState.lastPictureTakenUri, mode: addDamageHandle.mode, + vehicleParts: addDamageHandle.vehicleParts, onOpenGallery: handleOpenGallery, onSelectSight: sightState.selectSight, onRetakeSight: sightState.retakeSight, onAddDamage: addDamageHandle.handleAddDamage, + onAddDamagePartsSelected: addDamageHandle.handleAddDamagePartsSelected, onCancelAddDamage: addDamageHandle.handleCancelAddDamage, onRetry: sightState.retryLoadingInspection, loading, @@ -260,7 +269,8 @@ export function PhotoCapture({ inspectionId, showCloseButton, images, - enableAddDamage, + addDamage, + onValidateVehicleParts: addDamageHandle.handleValidateVehicleParts, sightGuidelines, enableSightGuidelines, currentTutorialStep, @@ -292,7 +302,7 @@ export function PhotoCapture({ onBack={handleGalleryBack} onNavigateToCapture={handleNavigateToCapture} onValidate={handleGalleryValidate} - enableAddDamage={enableAddDamage} + addDamage={addDamage} validateButtonLabel={validateButtonLabel} isInspectionCompleted={sightState.isInspectionCompleted} {...complianceOptions} diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx index 7ace867c3..9db3d30b2 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx @@ -1,17 +1,16 @@ import { useMemo, useState } from 'react'; -import { PhotoCaptureAppConfig, Image, ImageStatus, Sight } from '@monkvision/types'; +import { CaptureAppConfig, Image, ImageStatus, Sight, VehiclePart } from '@monkvision/types'; import { useTranslation } from 'react-i18next'; import { BackdropDialog } from '@monkvision/common-ui-web'; import { CameraHUDProps } from '@monkvision/camera-web'; import { LoadingState } from '@monkvision/common'; import { useAnalytics } from '@monkvision/analytics'; -import { PhotoCaptureHUDButtons } from './PhotoCaptureHUDButtons'; import { usePhotoCaptureHUDStyle } from './hooks'; -import { PhotoCaptureMode, TutorialSteps } from '../hooks'; -import { PhotoCaptureHUDOverlay } from './PhotoCaptureHUDOverlay'; +import { TutorialSteps } from '../hooks'; import { PhotoCaptureHUDElements } from './PhotoCaptureHUDElements'; import { PhotoCaptureHUDTutorial } from './PhotoCaptureHUDTutorial'; -import { OrientationEnforcer } from '../../components'; +import { CaptureMode } from '../../types'; +import { HUDButtons, HUDOverlay } from '../../components'; /** * Props of the PhotoCaptureHUD component. @@ -22,7 +21,7 @@ export interface PhotoCaptureHUDProps PhotoCaptureAppConfig, | 'enableSightGuidelines' | 'sightGuidelines' - | 'enableAddDamage' + | 'addDamage' | 'showCloseButton' | 'allowSkipTutorial' | 'enforceOrientation' @@ -50,7 +49,7 @@ export interface PhotoCaptureHUDProps /** * The current mode of the component. */ - mode: PhotoCaptureMode; + mode: CaptureMode; /** * Global loading state of the PhotoCapture component. */ @@ -59,6 +58,10 @@ export interface PhotoCaptureHUDProps * The current tutorial step in PhotoCapture component. */ currentTutorialStep: TutorialSteps | null; + /** + * Current vehicle parts selected to take a picture of. + */ + vehicleParts: VehiclePart[]; /** * Callback called when the user clicks on "Next" button in PhotoCapture tutorial. */ @@ -76,11 +79,15 @@ export interface PhotoCaptureHUDProps */ onRetakeSight: (sight: string) => void; /** - * Callback to be called when the user clicks on the "Add Damage" button. + * Callback called when the user clicks on the "Add Damage" button. */ onAddDamage: () => void; /** - * Callback to be called when the user clicks on the "Cancel" button of the Add Damage mode. + * Callback called when the user selects the parts to take a picture of. + */ + onAddDamagePartsSelected?: (parts: VehiclePart[]) => void; + /** + * Callback called when the user clicks on the "Cancel" button of the Add Damage mode. */ onCancelAddDamage: () => void; /** @@ -91,6 +98,10 @@ export interface PhotoCaptureHUDProps * Callback called when the user clicks on the gallery icon. */ onOpenGallery: () => void; + /** + * Callback called when the user clicks on the "Validate" button of the Add Damage mode. + */ + onValidateVehicleParts: () => void; /** * Callback called when the user clicks on the close button. If this callback is not provided, the close button is not * displayed. @@ -114,9 +125,12 @@ export function PhotoCaptureHUD({ sightsTaken, lastPictureTakenUri, mode, + vehicleParts, onSelectSight, onRetakeSight, onAddDamage, + onAddDamagePartsSelected, + onValidateVehicleParts, onCancelAddDamage, onOpenGallery, onRetry, @@ -126,7 +140,7 @@ export function PhotoCaptureHUD({ handle, cameraPreview, images, - enableAddDamage, + addDamage, sightGuidelines, enableSightGuidelines, currentTutorialStep, @@ -164,35 +178,40 @@ export function PhotoCaptureHUD({ sights={sights} sightsTaken={sightsTaken} mode={mode} + vehicleParts={vehicleParts} onAddDamage={onAddDamage} onCancelAddDamage={onCancelAddDamage} + onAddDamagePartsSelected={onAddDamagePartsSelected} onSelectSight={onSelectSight} onRetakeSight={onRetakeSight} + onValidateVehicleParts={onValidateVehicleParts} isLoading={loading.isLoading} error={loading.error ?? handle.error} previewDimensions={handle.previewDimensions} images={images} - enableAddDamage={enableAddDamage} + addDamage={addDamage} sightGuidelines={sightGuidelines} enableSightGuidelines={enableSightGuidelines} tutorialStep={currentTutorialStep} /> - setShowCloseModal(true)} - galleryPreview={lastPictureTakenUri ?? undefined} - closeDisabled={!!loading.error || !!handle.error} - galleryDisabled={!!loading.error || !!handle.error} - takePictureDisabled={ - !!loading.error || !!handle.error || handle.isLoading || loading.isLoading - } - showCloseButton={showCloseButton} - showGalleryBadge={retakeCount > 0} - retakeCount={retakeCount} - /> - setShowCloseModal(true)} + galleryPreview={lastPictureTakenUri ?? undefined} + closeDisabled={!!loading.error || !!handle.error} + galleryDisabled={!!loading.error || !!handle.error} + takePictureDisabled={ + !!loading.error || !!handle.error || handle.isLoading || loading.isLoading + } + showCloseButton={showCloseButton} + showGalleryBadge={retakeCount > 0} + retakeCount={retakeCount} + /> + )} + diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx index fe320c995..cedd5ff8c 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx @@ -1,17 +1,14 @@ -import { PhotoCaptureAppConfig, Image, PixelDimensions, Sight } from '@monkvision/types'; -import { PhotoCaptureMode, TutorialSteps } from '../../hooks'; +import { CaptureAppConfig, Image, PixelDimensions, Sight, VehiclePart } from '@monkvision/types'; +import { TutorialSteps } from '../../hooks'; import { PhotoCaptureHUDElementsSight } from '../PhotoCaptureHUDElementsSight'; -import { PhotoCaptureHUDElementsAddDamage1stShot } from '../PhotoCaptureHUDElementsAddDamage1stShot'; -import { PhotoCaptureHUDElementsAddDamage2ndShot } from '../PhotoCaptureHUDElementsAddDamage2ndShot'; +import { CloseUpShot, ZoomOutShot, PartSelection } from '../../../components'; +import { CaptureMode } from '../../../types'; /** * Props of the PhotoCaptureHUDElements component. */ export interface PhotoCaptureHUDElementsProps - extends Pick< - PhotoCaptureAppConfig, - 'enableSightGuidelines' | 'sightGuidelines' | 'enableAddDamage' - > { + extends Pick { /** * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture. */ @@ -27,15 +24,23 @@ export interface PhotoCaptureHUDElementsProps /** * The current mode of the component. */ - mode: PhotoCaptureMode; + mode: CaptureMode; /** * The current tutorial step in PhotoCapture component. */ tutorialStep: TutorialSteps | null; + /** + * Current vehicle parts selected to take a picture of. + */ + vehicleParts: VehiclePart[]; /** * Callback called when the user presses the Add Damage button. */ onAddDamage: () => void; + /** + * Callback called when the user selects the parts to take a picture of. + */ + onAddDamagePartsSelected?: (parts: VehiclePart[]) => void; /** * Callback called when the user cancels the Add Damage mode. */ @@ -48,6 +53,10 @@ export interface PhotoCaptureHUDElementsProps * Callback called when the user manually selects a sight to retake. */ onRetakeSight: (sight: string) => void; + /** + * Callback called when the user clicks on the "Validate" button of the Add Damage mode. + */ + onValidateVehicleParts: () => void; /** * The effective pixel dimensions of the Camera video stream on the screen. */ @@ -74,7 +83,7 @@ export function PhotoCaptureHUDElements(params: PhotoCaptureHUDElementsProps) { if (params.isLoading || !!params.error) { return null; } - if (params.mode === PhotoCaptureMode.SIGHT) { + if (params.mode === CaptureMode.SIGHT) { return ( ); } - if (params.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) { - return ; + if (params.mode === CaptureMode.ADD_DAMAGE_1ST_SHOT) { + return ; + } + if ( + [CaptureMode.ADD_DAMAGE_2ND_SHOT, CaptureMode.ADD_DAMAGE_PART_SELECT_SHOT].includes(params.mode) + ) { + return ( + + ); } return ( - - {enableAddDamage && ( + {addDamage && addDamage !== AddDamage.DISABLED && ( - )} - - ); + return [AddDamage.TWO_SHOT, AddDamage.PART_SELECT].includes(addDamage as AddDamage) ? ( + + ) : null; } diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx index 3b72430cc..4cac72042 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx @@ -37,8 +37,7 @@ export function SightGuideline({ const primaryColor = useColorBackground(); const { i18n, t } = useTranslation(); - const style = - addDamage && addDamage === AddDamage.DISABLED ? styles['containerWide'] : styles['container']; + const style = addDamage === AddDamage.DISABLED ? styles['containerWide'] : styles['container']; const guidelineFound = sightGuidelines?.find((value) => value.sightIds.includes(sightId)); diff --git a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx index dcc247c36..ca10391ee 100644 --- a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx +++ b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx @@ -5,6 +5,9 @@ import { useTranslation } from 'react-i18next'; import { styles } from './PartSelection.styles'; import { useColorBackground } from '../../hooks'; +/** + * Props of PartSelection component. + */ export interface PartSelectionProps { /** * Current vehicle parts selected to take a picture of. @@ -32,6 +35,10 @@ export interface PartSelectionProps { maxSelectableParts?: number; } +/** + * Component that displays a vehicle wireframe on top of the Camera Preview that is used + * to select the parts of the vehicle that the user wants to take a picture of. + */ export function PartSelection({ vehicleParts, disabled = false, diff --git a/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts index cd346b4ef..d0d38b8a2 100644 --- a/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts +++ b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts @@ -4,23 +4,26 @@ import { useAnalytics } from '@monkvision/analytics'; import { AddDamage, CaptureAppConfig, VehiclePart } from '@monkvision/types'; import { CaptureMode, CaptureScreen } from '../types'; -interface AddDamageModeParams extends Pick { +/** + * Parameters of the useAddDamageMode hook. + */ +export interface AddDamageModeParams extends Pick { /** * The current screen of the Capture component. */ currentScreen?: CaptureScreen; /** - * Boolean indicating if the only close damage will be taken first then sights picutures. + * Boolean indicating if the capture is in Damage Disclosure mode. */ damageDisclosure?: boolean; /** - * Callback called when the user clicks on the 'close` button in vehicleDiclosure mode. + * Callback called when the user clicks on the 'close` button in Damage Diclosure mode. */ handleOpenGallery: () => void; } /** - * Handle used to modify the current PhotoCaptureMode of the PhotoCaptureComponent. + * Handle used to modify the current CaptureMode of the PhotoCapture or DamageDisclosure component. */ export interface AddDamageHandle { /** @@ -53,7 +56,7 @@ export interface AddDamageHandle { handleValidateVehicleParts: () => void; } -function getInitialMode(addDamage?: AddDamage, damageDisclosure?: boolean) { +function getInitialMode(addDamage?: AddDamage, damageDisclosure?: boolean): CaptureMode { if (damageDisclosure && addDamage === AddDamage.PART_SELECT) { return CaptureMode.ADD_DAMAGE_PART_SELECT; } 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/src/types.ts b/packages/inspection-capture-web/src/types.ts index da3ec5051..66b57d369 100644 --- a/packages/inspection-capture-web/src/types.ts +++ b/packages/inspection-capture-web/src/types.ts @@ -88,7 +88,7 @@ export interface PhotoCaptureSightState { } /** - * Object containing state management utilities for the PhotoCapture sights. + * Object containing state management utilities for the DamageDisclosure. */ export interface DamageDisclosureState { /** 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/DamageDisclosure/hooks/useDamageDisclosureState.test.ts b/packages/inspection-capture-web/test/DamageDisclosure/hooks/useDamageDisclosureState.test.ts index db50dce75..60cafd64a 100644 --- a/packages/inspection-capture-web/test/DamageDisclosure/hooks/useDamageDisclosureState.test.ts +++ b/packages/inspection-capture-web/test/DamageDisclosure/hooks/useDamageDisclosureState.test.ts @@ -8,7 +8,6 @@ import { DamageDisclosureParams, useDamageDisclosureState, } from '../../../src/DamageDisclosure/hooks'; -// import { DamageDisclosureErrorName } from '../../../src/PhotoCapture/errors'; function createParams(): DamageDisclosureParams { return { diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx index 8a977f068..8ff3dda0b 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx @@ -270,12 +270,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(); From 1328989e885667288318665afea9fcd0479099e3 Mon Sep 17 00:00:00 2001 From: David Ly Date: Mon, 13 Jan 2025 14:50:29 +0100 Subject: [PATCH 12/12] Fixed conflits with VideoCapture feature --- .../DamageDisclosurePage.tsx | 5 ++- .../PhotoCapturePage/PhotoCapturePage.tsx | 3 +- packages/inspection-capture-web/README.md | 40 ++++++++++--------- .../src/DamageDisclosure/DamageDisclosure.tsx | 11 ++++- .../DamageDisclosureHUD.tsx | 19 ++++++++- .../DamageDisclosureHUDElements.tsx | 14 ++++++- .../src/PhotoCapture/PhotoCapture.tsx | 18 ++++++--- .../PhotoCaptureHUD/PhotoCaptureHUD.tsx | 17 +++++++- .../PhotoCaptureHUDElements.tsx | 16 +++++++- .../SightGuideline/SightGuideline.tsx | 4 +- .../PhotoCaptureHUDElementsSight/hooks.ts | 2 +- .../PhotoCaptureHUDTutorial.tsx | 2 +- .../src/PhotoCapture/hooks/index.ts | 1 - .../PartSelection/PartSelection.tsx | 10 +++-- .../src/hooks/useAddDamageMode.ts | 4 +- .../src/hooks/useUploadQueue.ts | 2 +- .../DamageDisclosure.test.tsx | 3 ++ .../DamageDisclosureHUDElements.test.tsx | 4 +- .../DamageDislosureHUD.test.tsx | 3 +- .../test/PhotoCapture/PhotoCapture.test.tsx | 16 ++++---- .../PhotoCaptureHUD/PhotoCaptureHUD.test.tsx | 26 ++---------- .../PhotoCaptureHUDElements.test.tsx | 3 +- packages/network/src/api/image/requests.ts | 6 ++- 23 files changed, 144 insertions(+), 85 deletions(-) diff --git a/apps/demo-app/src/pages/DamageDisclosurePage/DamageDisclosurePage.tsx b/apps/demo-app/src/pages/DamageDisclosurePage/DamageDisclosurePage.tsx index b9913121a..327ac2e3b 100644 --- a/apps/demo-app/src/pages/DamageDisclosurePage/DamageDisclosurePage.tsx +++ b/apps/demo-app/src/pages/DamageDisclosurePage/DamageDisclosurePage.tsx @@ -2,14 +2,16 @@ import { useTranslation } from 'react-i18next'; import { useMonkAppState } from '@monkvision/common'; import { DamageDisclosure } from '@monkvision/inspection-capture-web'; import { useNavigate } from 'react-router-dom'; +import { CaptureWorkflow, VehicleType } from '@monkvision/types'; import styles from './DamageDisclosurePage.module.css'; import { Page } from '../pages'; export function DamageDisclosurePage() { const navigate = useNavigate(); const { i18n } = useTranslation(); - const { config, authToken, inspectionId } = useMonkAppState({ + const { config, authToken, inspectionId, vehicleType } = useMonkAppState({ requireInspection: true, + requireWorkflow: CaptureWorkflow.PHOTO, }); return ( @@ -24,6 +26,7 @@ export function DamageDisclosurePage() { inspectionId={inspectionId} onComplete={() => navigate(Page.PHOTO_CAPTURE)} lang={i18n.language} + vehicleType={vehicleType ?? VehicleType.SEDAN} /> ); diff --git a/apps/demo-app/src/pages/PhotoCapturePage/PhotoCapturePage.tsx b/apps/demo-app/src/pages/PhotoCapturePage/PhotoCapturePage.tsx index 10248471e..244df29b5 100644 --- a/apps/demo-app/src/pages/PhotoCapturePage/PhotoCapturePage.tsx +++ b/apps/demo-app/src/pages/PhotoCapturePage/PhotoCapturePage.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useMonkAppState } from '@monkvision/common'; import { PhotoCapture } from '@monkvision/inspection-capture-web'; -import { CaptureWorkflow } from '@monkvision/types'; +import { CaptureWorkflow, VehicleType } from '@monkvision/types'; import styles from './PhotoCapturePage.module.css'; import { createInspectionReportLink } from './inspectionReport'; @@ -36,6 +36,7 @@ export function PhotoCapturePage() { sights={currentSights} onComplete={handleComplete} lang={i18n.language} + vehicleType={vehicleType ?? VehicleType.SEDAN} /> ); diff --git a/packages/inspection-capture-web/README.md b/packages/inspection-capture-web/README.md index b3460b8a9..b037d1cf7 100644 --- a/packages/inspection-capture-web/README.md +++ b/packages/inspection-capture-web/README.md @@ -98,6 +98,7 @@ export function MonkPhotoCapturePage({ authToken }) { | validateButtonLabel | `string` | Custom label for validate button in gallery view. | | | | 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` | +| vehicleType | `VehicleType` | The vehicle type of the inspection. | | `VehicleType.SEDAN` | # VideoCapture The VideoCapture workflow is aimed at asking users to record a walkaround video of their vehicle (around ~1min per @@ -189,22 +190,23 @@ export function MonkDamageDisclosurePage({ authToken }) { 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 | string | null | 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. | ✔️ | | +| 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 | string | null | 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. | ✔️ | | +| vehicleType | `VehicleType` | The vehicle type of the inspection. | | `VehicleType.SEDAN` | diff --git a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx index e6ffcf16d..4b22c8b32 100644 --- a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx +++ b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosure.tsx @@ -11,12 +11,13 @@ import { MonkApiConfig } from '@monkvision/network'; import { AddDamage, CameraConfig, - CaptureAppConfig, + PhotoCaptureAppConfig, ComplianceOptions, CompressionOptions, DeviceOrientation, ImageType, MonkPicture, + VehicleType, } from '@monkvision/types'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -40,7 +41,7 @@ import { CaptureScreen } from '../types'; export interface DamageDisclosureProps extends Pick, 'resolution' | 'allowImageUpscaling'>, Pick< - CaptureAppConfig, + PhotoCaptureAppConfig, | keyof CameraConfig | 'maxUploadDurationWarning' | 'useAdaptiveImageQuality' @@ -60,6 +61,10 @@ export interface DamageDisclosureProps * one as the one that created the inspection provided in the `inspectionId` prop. */ apiConfig: MonkApiConfig; + /** + * The vehicle type of the inspection. + */ + vehicleType?: VehicleType; /** * 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. @@ -98,6 +103,7 @@ export function DamageDisclosure({ useAdaptiveImageQuality = true, lang, enforceOrientation, + vehicleType = VehicleType.SEDAN, ...initialCameraConfig }: DamageDisclosureProps) { useI18nSync(lang); @@ -174,6 +180,7 @@ export function DamageDisclosure({ images, addDamage, onValidateVehicleParts: addDamageHandle.handleValidateVehicleParts, + vehicleType, }; return ( diff --git a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUD.tsx b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUD.tsx index 211e20fc6..8c5373440 100644 --- a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUD.tsx +++ b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUD.tsx @@ -1,5 +1,12 @@ import { useMemo, useState } from 'react'; -import { CaptureAppConfig, Image, ImageStatus, ImageType, VehiclePart } from '@monkvision/types'; +import { + PhotoCaptureAppConfig, + Image, + ImageStatus, + ImageType, + VehiclePart, + VehicleType, +} from '@monkvision/types'; import { useTranslation } from 'react-i18next'; import { BackdropDialog } from '@monkvision/common-ui-web'; import { CameraHUDProps } from '@monkvision/camera-web'; @@ -16,7 +23,7 @@ import { HUDOverlay } from '../../components/HUDOverlay'; */ export interface DamageDisclosureHUDProps extends CameraHUDProps, - Pick { + Pick { /** * The inspection ID. */ @@ -70,6 +77,10 @@ export interface DamageDisclosureHUDProps * The current images taken by the user (ignoring retaken pictures etc.). */ images: Image[]; + /** + * The vehicle type of the inspection. + */ + vehicleType: VehicleType; } /** @@ -82,6 +93,7 @@ export function DamageDisclosureHUD({ lastPictureTakenUri, mode, vehicleParts, + addDamage, onAddDamage, onAddDamagePartsSelected, onValidateVehicleParts, @@ -94,6 +106,7 @@ export function DamageDisclosureHUD({ handle, cameraPreview, images, + vehicleType, }: DamageDisclosureHUDProps) { const { t } = useTranslation(); const [showCloseModal, setShowCloseModal] = useState(false); @@ -122,6 +135,7 @@ export function DamageDisclosureHUD({ { } diff --git a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements/DamageDisclosureHUDElements.tsx b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements/DamageDisclosureHUDElements.tsx index 19c78ee9c..d41b4b1b1 100644 --- a/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements/DamageDisclosureHUDElements.tsx +++ b/packages/inspection-capture-web/src/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements/DamageDisclosureHUDElements.tsx @@ -1,10 +1,15 @@ -import { CaptureAppConfig, PixelDimensions, VehiclePart } from '@monkvision/types'; +import { + PhotoCaptureAppConfig, + PixelDimensions, + VehiclePart, + VehicleType, +} from '@monkvision/types'; import { CaptureMode } from '../../../types'; import { CloseUpShot, PartSelection, ZoomOutShot } from '../../../components'; /** * Props of the DamageDisclosureHUDElements component. */ -export interface DamageDisclosureHUDElementsProps extends Pick { +export interface DamageDisclosureHUDElementsProps extends Pick { /** * The current mode of the component. */ @@ -41,6 +46,10 @@ export interface DamageDisclosureHUDElementsProps extends Pick {mode !== CaptureMode.ADD_DAMAGE_PART_SELECT && ( diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx index 9cadc2689..11c2608d0 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements/PhotoCaptureHUDElements.tsx @@ -1,4 +1,11 @@ -import { CaptureAppConfig, Image, PixelDimensions, Sight, VehiclePart } from '@monkvision/types'; +import { + PhotoCaptureAppConfig, + Image, + PixelDimensions, + Sight, + VehiclePart, + VehicleType, +} from '@monkvision/types'; import { TutorialSteps } from '../../hooks'; import { PhotoCaptureHUDElementsSight } from '../PhotoCaptureHUDElementsSight'; import { CloseUpShot, ZoomOutShot, PartSelection } from '../../../components'; @@ -8,7 +15,7 @@ import { CaptureMode } from '../../../types'; * Props of the PhotoCaptureHUDElements component. */ export interface PhotoCaptureHUDElementsProps - extends Pick { + extends Pick { /** * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture. */ @@ -73,6 +80,10 @@ export interface PhotoCaptureHUDElementsProps * The current images taken by the user (ignoring retaken pictures etc.). */ images: Image[]; + /** + * The vehicle type of the inspection. + */ + vehicleType: VehicleType; } /** @@ -118,6 +129,7 @@ export function PhotoCaptureHUDElements(params: PhotoCaptureHUDElementsProps) { diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx index 4cac72042..0772c716d 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/SightGuideline/SightGuideline.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Button } from '@monkvision/common-ui-web'; -import { AddDamage, CaptureAppConfig } from '@monkvision/types'; +import { AddDamage, PhotoCaptureAppConfig } from '@monkvision/types'; import { useTranslation } from 'react-i18next'; import { getLanguage } from '@monkvision/common'; import { styles } from './SightGuideline.styles'; @@ -10,7 +10,7 @@ import { useColorBackground } from '../../../../hooks'; * Props of the SightGuideline component. */ export interface SightGuidelineProps - extends Pick { + extends Pick { /** * The id of the sight. */ diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/hooks.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/hooks.ts index 7e36d3889..cf4960f9a 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/hooks.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight/hooks.ts @@ -8,7 +8,7 @@ import { TutorialSteps } from '../../hooks'; * Props of the PhotoCaptureHUDElementsSight component. */ export interface PhotoCaptureHUDElementsSightProps - extends Pick { + extends Pick { /** * The list of sights provided to the PhotoCapture component. */ diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDTutorial/PhotoCaptureHUDTutorial.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDTutorial/PhotoCaptureHUDTutorial.tsx index 090f75001..d10d97d23 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDTutorial/PhotoCaptureHUDTutorial.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDTutorial/PhotoCaptureHUDTutorial.tsx @@ -13,7 +13,7 @@ import { useColorBackground } from '../../../hooks'; * Props of the PhotoCaptureHUDTutorial component. */ export interface PhotoCaptureHUDTutorialProps - extends Pick { + extends Pick { /** * The id of the sight. */ diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts index f0c83d6a0..11517f16a 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts @@ -1,4 +1,3 @@ export * from './usePhotoCaptureSightState'; -export * from './useStartTasksOnComplete'; export * from './useComplianceAnalytics'; export * from './usePhotoCaptureTutorial'; diff --git a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx index ca10391ee..4907d0ee5 100644 --- a/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx +++ b/packages/inspection-capture-web/src/components/PartSelection/PartSelection.tsx @@ -1,6 +1,6 @@ -import { useMonkAppState, useMonkTheme, vehiclePartLabels, getLanguage } from '@monkvision/common'; +import { useMonkTheme, vehiclePartLabels, getLanguage } from '@monkvision/common'; import { VehiclePartSelection, Button } from '@monkvision/common-ui-web'; -import { VehiclePart } from '@monkvision/types'; +import { VehiclePart, VehicleType } from '@monkvision/types'; import { useTranslation } from 'react-i18next'; import { styles } from './PartSelection.styles'; import { useColorBackground } from '../../hooks'; @@ -9,6 +9,10 @@ import { useColorBackground } from '../../hooks'; * Props of PartSelection component. */ export interface PartSelectionProps { + /** + * Vehicle type of the wireframe to display. + */ + vehicleType: VehicleType; /** * Current vehicle parts selected to take a picture of. */ @@ -40,6 +44,7 @@ export interface PartSelectionProps { * to select the parts of the vehicle that the user wants to take a picture of. */ export function PartSelection({ + vehicleType, vehicleParts, disabled = false, onCancel = () => {}, @@ -47,7 +52,6 @@ export function PartSelection({ onValidateVehicleParts = () => {}, maxSelectableParts = 1, }: PartSelectionProps) { - const { vehicleType } = useMonkAppState(); const { palette } = useMonkTheme(); const { i18n, t } = useTranslation(); const backgroundColor = useColorBackground(0.9); diff --git a/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts index d0d38b8a2..e876f104a 100644 --- a/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts +++ b/packages/inspection-capture-web/src/hooks/useAddDamageMode.ts @@ -1,13 +1,13 @@ import { useCallback, useState } from 'react'; import { useObjectMemo } from '@monkvision/common'; import { useAnalytics } from '@monkvision/analytics'; -import { AddDamage, CaptureAppConfig, VehiclePart } from '@monkvision/types'; +import { AddDamage, PhotoCaptureAppConfig, VehiclePart } from '@monkvision/types'; import { CaptureMode, CaptureScreen } from '../types'; /** * Parameters of the useAddDamageMode hook. */ -export interface AddDamageModeParams extends Pick { +export interface AddDamageModeParams extends Pick { /** * The current screen of the Capture component. */ diff --git a/packages/inspection-capture-web/src/hooks/useUploadQueue.ts b/packages/inspection-capture-web/src/hooks/useUploadQueue.ts index 524866778..26a58d1d0 100644 --- a/packages/inspection-capture-web/src/hooks/useUploadQueue.ts +++ b/packages/inspection-capture-web/src/hooks/useUploadQueue.ts @@ -1,7 +1,7 @@ import { Queue, uniq, useQueue } from '@monkvision/common'; import { AddImageOptions, ImageUploadType, MonkApiConfig, useMonkApi } from '@monkvision/network'; import { - CaptureAppConfig, + PhotoCaptureAppConfig, ComplianceOptions, MonkPicture, TaskName, diff --git a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx index 46ec966cd..6f3d3b073 100644 --- a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx +++ b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosure.test.tsx @@ -5,6 +5,7 @@ import { CompressionFormat, ImageType, TaskName, + VehicleType, } from '@monkvision/types'; const { CaptureMode } = jest.requireActual('../../src/types'); @@ -91,6 +92,7 @@ function createProps(): DamageDisclosureProps { useAdaptiveImageQuality: false, addDamage: AddDamage.PART_SELECT, maxUploadDurationWarning: 456, + vehicleType: VehicleType.SEDAN, }; } @@ -272,6 +274,7 @@ describe('DamageDisclosure component', () => { addDamage: props.addDamage, onRetry: disclosureState.retryLoadingInspection, onValidateVehicleParts: addDamageHandle.handleValidateVehicleParts, + vehicleType: props.vehicleType, }, }); diff --git a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements.test.tsx b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements.test.tsx index 5a4fd8cea..1f0184879 100644 --- a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements.test.tsx +++ b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDisclosureHUDElements.test.tsx @@ -1,5 +1,3 @@ -import { AddDamage } from '@monkvision/types'; - jest.mock('../../../src/components', () => ({ ZoomOutShot: jest.fn(() => <>), CloseUpShot: jest.fn(() => <>), @@ -9,6 +7,7 @@ jest.mock('../../../src/components', () => ({ import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; +import { AddDamage, VehicleType } from '@monkvision/types'; import { CaptureMode } from '../../../src/types'; import { DamageDisclosureHUDElements, DamageDisclosureHUDElementsProps } from '../../../src'; import { ZoomOutShot, CloseUpShot, PartSelection } from '../../../src/components'; @@ -24,6 +23,7 @@ function createProps(): DamageDisclosureHUDElementsProps { onValidateVehicleParts: jest.fn(), vehicleParts: [], addDamage: AddDamage.PART_SELECT, + vehicleType: VehicleType.SEDAN, }; } diff --git a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDislosureHUD.test.tsx b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDislosureHUD.test.tsx index 43bcbd7ea..9ccfcf079 100644 --- a/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDislosureHUD.test.tsx +++ b/packages/inspection-capture-web/test/DamageDisclosure/DamageDisclosureHUD/DamageDislosureHUD.test.tsx @@ -1,4 +1,4 @@ -import { Image, ImageStatus } from '@monkvision/types'; +import { Image, ImageStatus, VehicleType } from '@monkvision/types'; jest.mock('../../../src/components/HUDButtons', () => ({ HUDButtons: jest.fn(() => <>), @@ -45,6 +45,7 @@ function createProps(): DamageDisclosureHUDProps { images: [{ sightId: 'test-sight-1', status: ImageStatus.NOT_COMPLIANT }] as Image[], onValidateVehicleParts: jest.fn(), vehicleParts: [], + vehicleType: VehicleType.SEDAN, }; } diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx index 8ff3dda0b..c92b23ac1 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx @@ -7,6 +7,7 @@ import { DeviceOrientation, PhotoCaptureTutorialOption, TaskName, + VehicleType, } from '@monkvision/types'; import { Camera } from '@monkvision/camera-web'; import { useI18nSync, useLoadingState, usePreventExit } from '@monkvision/common'; @@ -15,17 +16,16 @@ import { useMonitoring } from '@monkvision/monitoring'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { act, render, waitFor } from '@testing-library/react'; import { PhotoCapture, PhotoCaptureHUD, PhotoCaptureProps } from '../../src'; +import { usePhotoCaptureSightState, usePhotoCaptureTutorial } from '../../src/PhotoCapture/hooks'; import { + useStartTasksOnComplete, useAdaptiveCameraConfig, useAddDamageMode, useBadConnectionWarning, usePhotoCaptureImages, - usePhotoCaptureSightState, - usePhotoCaptureTutorial, usePictureTaken, useUploadQueue, -} from '../../src/PhotoCapture/hooks'; -import { useStartTasksOnComplete } from '../../src/hooks'; +} from '../../src/hooks'; const { CaptureMode } = jest.requireActual('../../src/types'); @@ -39,7 +39,6 @@ jest.mock('../../src/PhotoCapture/hooks', () => ({ setLastPictureTakenUri: jest.fn(), retryLoadingInspection: jest.fn(), })), - useStartTasksOnComplete: jest.fn(() => jest.fn()), useComplianceAnalytics: jest.fn(() => ({ isInitialInspectionFetched: jest.fn(), })), @@ -51,6 +50,7 @@ jest.mock('../../src/PhotoCapture/hooks', () => ({ })); jest.mock('../../src/hooks', () => ({ + useStartTasksOnComplete: jest.fn(() => jest.fn()), useAddDamageMode: jest.fn(() => ({ mode: CaptureMode.SIGHT, handleAddDamage: jest.fn(), @@ -85,10 +85,6 @@ jest.mock('../../src/hooks', () => ({ useTracking: jest.fn(), })); -jest.mock('../../src/hooks', () => ({ - useStartTasksOnComplete: jest.fn(() => jest.fn()), -})); - function createProps(): PhotoCaptureProps { return { sights: [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']], @@ -135,6 +131,7 @@ function createProps(): PhotoCaptureProps { enableTutorial: PhotoCaptureTutorialOption.ENABLED, allowSkipTutorial: true, enableSightTutorial: true, + vehicleType: VehicleType.SEDAN, }; } @@ -353,6 +350,7 @@ describe('PhotoCapture component', () => { onCloseTutorial: tutorial.closeTutorial, allowSkipTutorial: props.allowSkipRetake, enforceOrientation: props.enforceOrientation, + vehicleType: props.vehicleType, }, }); diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx index d40f6f479..9cf632914 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx @@ -1,21 +1,3 @@ -import { DeviceOrientation, Image, ImageStatus } from '@monkvision/types'; -import { useTranslation } from 'react-i18next'; -import { act, render, screen } from '@testing-library/react'; -import { sights } from '@monkvision/sights'; -import { LoadingState } from '@monkvision/common'; -import { CameraHandle } from '@monkvision/camera-web'; -import { expectPropsOnChildMock } from '@monkvision/test-utils'; -import { BackdropDialog } from '@monkvision/common-ui-web'; -import { - PhotoCaptureHUD, - PhotoCaptureHUDButtons, - PhotoCaptureHUDElements, - PhotoCaptureHUDOverlay, - PhotoCaptureHUDProps, -} from '../../../src'; -import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks'; -import { OrientationEnforcer } from '../../../src/components'; - jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/hooks', () => ({ ...jest.requireActual('../../../src/PhotoCapture/PhotoCaptureHUD/hooks'), useComplianceNotification: jest.fn(() => false), @@ -23,13 +5,11 @@ jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/hooks', () => ({ jest.mock('../../../src/components', () => ({ HUDButtons: jest.fn(() => <>), HUDOverlay: jest.fn(() => <>), + OrientationEnforcer: jest.fn(() => <>), })); jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements', () => ({ PhotoCaptureHUDElements: jest.fn(() => <>), })); -jest.mock('../../../src/components', () => ({ - OrientationEnforcer: jest.fn(() => <>), -})); import { useTranslation } from 'react-i18next'; import { act, render, screen } from '@testing-library/react'; @@ -39,8 +19,9 @@ import { CameraHandle } from '@monkvision/camera-web'; import { expectPropsOnChildMock } from '@monkvision/test-utils'; import { BackdropDialog } from '@monkvision/common-ui-web'; import { PhotoCaptureHUD, PhotoCaptureHUDElements, PhotoCaptureHUDProps } from '../../../src'; -import { HUDButtons, HUDOverlay } from '../../../src/components'; +import { HUDButtons, HUDOverlay, OrientationEnforcer } from '../../../src/components'; import { CaptureMode } from '../../../src/types'; +import { ImageStatus, Image, DeviceOrientation, VehicleType } from '@monkvision/types'; const cameraTestId = 'camera-test-id'; @@ -81,6 +62,7 @@ function createProps(): PhotoCaptureHUDProps { enforceOrientation: DeviceOrientation.PORTRAIT, onValidateVehicleParts: jest.fn(), vehicleParts: [], + vehicleType: VehicleType.SEDAN, }; } diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements.test.tsx index e23700a87..dbd196be7 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElements.test.tsx @@ -1,4 +1,4 @@ -import { AddDamage, Image, ImageStatus } from '@monkvision/types'; +import { AddDamage, Image, ImageStatus, VehicleType } from '@monkvision/types'; jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDElementsSight', () => ({ PhotoCaptureHUDElementsSight: jest.fn(() => <>), @@ -41,6 +41,7 @@ function createProps(): PhotoCaptureHUDElementsProps { onValidateVehicleParts: jest.fn(), vehicleParts: [], addDamage: AddDamage.PART_SELECT, + vehicleType: VehicleType.SEDAN, }; } diff --git a/packages/network/src/api/image/requests.ts b/packages/network/src/api/image/requests.ts index 2cdc907c8..ab91e45bd 100644 --- a/packages/network/src/api/image/requests.ts +++ b/packages/network/src/api/image/requests.ts @@ -206,8 +206,8 @@ export type AddImageOptions = | AddBeautyShotImageOptions | Add2ShotCloseUpImageOptions | AddVideoFrameOptions - | AddVideoManualPhotoOptions; - | AddPartSelectCloseUpImageOptions + | AddVideoManualPhotoOptions + | AddPartSelectCloseUpImageOptions; interface AddImageData { filename: string; @@ -241,6 +241,8 @@ function getImageLabel(options: AddImageOptions): TranslationObject | undefined fr: `Photo Manuelle Vidéo`, de: `Foto Manuell Video`, nl: `Foto-handleiding Video`, + }; + } if (options.uploadType === ImageUploadType.PART_SELECT_SHOT) { const partsTranslation = options.vehicleParts.map((part) => vehiclePartLabels[part]); return {