Skip to content

Commit

Permalink
Fix Refresh in PhotoCapture (#738)
Browse files Browse the repository at this point in the history
* Fix compliance status on refresh and allow retake of compliant pictures

* Added sightId and createdAt in image object
  • Loading branch information
souyahia-monk authored May 7, 2024
1 parent cf6a6c3 commit b771311
Show file tree
Hide file tree
Showing 30 changed files with 460 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { useTranslation } from 'react-i18next';
import { useObjectTranslation } from '@monkvision/common';
import {
ImageDetailedViewOverlayProps,
isComplianceContainerDisplayed,
useComplianceLabels,
useRetakeOverlay,
useImageDetailedViewOverlayStyles,
useImageLabelIcon,
} from './hooks';
Expand All @@ -14,7 +13,7 @@ export function ImageDetailedViewOverlay(props: ImageDetailedViewOverlayProps) {
const { t } = useTranslation();
const { tObj } = useObjectTranslation();
const labelIcon = useImageLabelIcon(props);
const complianceLabels = useComplianceLabels(props);
const retakeOverlay = useRetakeOverlay(props);
const {
mainContainerStyle,
overlayDisplayStyle,
Expand All @@ -32,25 +31,27 @@ export function ImageDetailedViewOverlay(props: ImageDetailedViewOverlayProps) {
return (
<div style={mainContainerStyle}>
<div style={overlayDisplayStyle}>
{isComplianceContainerDisplayed(props) && (
<div style={complianceContainerStyle}>
<div style={complianceMessageContainerStyle}>
<Icon icon='error' primaryColor='alert' size={complianceIcon.size} />
<div style={complianceMessageStyle}>
<div style={complianceTitleStyle}>{complianceLabels?.title}</div>
<div style={complianceDescriptionStyle}>{complianceLabels?.description}</div>
</div>
<div style={complianceContainerStyle}>
<div style={complianceMessageContainerStyle}>
<Icon
icon={retakeOverlay.icon}
primaryColor={retakeOverlay.iconColor}
size={complianceIcon.size}
/>
<div style={complianceMessageStyle}>
<div style={complianceTitleStyle}>{retakeOverlay.title}</div>
<div style={complianceDescriptionStyle}>{retakeOverlay.description}</div>
</div>
<Button
style={complianceRetakeButton.style}
size={complianceRetakeButton.size}
primaryColor='alert'
onClick={props.onRetake}
>
{t('retake')}
</Button>
</div>
)}
<Button
style={complianceRetakeButton.style}
size={complianceRetakeButton.size}
primaryColor={retakeOverlay.buttonColor}
onClick={props.onRetake}
>
{t('retake')}
</Button>
</div>
{props.image.label && (
<div style={imageLabelStyle}>
{labelIcon && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,51 @@ export interface ImageLabelIcon {
primaryColor: ColorProp;
}

export function isComplianceContainerDisplayed(props: ImageDetailedViewOverlayProps): boolean {
export function isImageValid(props: ImageDetailedViewOverlayProps): boolean {
return (
props.captureMode &&
[ImageStatus.UPLOAD_FAILED, ImageStatus.NOT_COMPLIANT].includes(props.image.status)
!props.captureMode ||
![ImageStatus.UPLOAD_FAILED, ImageStatus.NOT_COMPLIANT].includes(props.image.status)
);
}

export function useComplianceLabels(
props: ImageDetailedViewOverlayProps,
): { title: string; description: string } | null {
export function useRetakeOverlay(props: ImageDetailedViewOverlayProps): {
title: string;
description: string;
icon: IconName;
iconColor: ColorProp;
buttonColor: ColorProp;
} {
const { tObj } = useObjectTranslation();

if (!isComplianceContainerDisplayed(props)) {
return null;
const success = {
title: tObj(imageStatusLabels[ImageStatus.SUCCESS].title),
description: tObj(imageStatusLabels[ImageStatus.SUCCESS].description),
iconColor: 'text-secondary',
icon: 'check-circle' as IconName,
buttonColor: 'primary',
};

if (isImageValid(props)) {
return success;
}
if (props.image.status === ImageStatus.UPLOAD_FAILED) {
return {
title: tObj(imageStatusLabels[ImageStatus.UPLOAD_FAILED].title),
description: tObj(imageStatusLabels[ImageStatus.UPLOAD_FAILED].description),
iconColor: 'alert',
icon: 'error',
buttonColor: 'alert',
};
}
if (!props.image.complianceIssues || props.image.complianceIssues.length === 0) {
return null;
return success;
}
return {
title: tObj(complianceIssueLabels[props.image.complianceIssues[0]].title),
description: tObj(complianceIssueLabels[props.image.complianceIssues[0]].description),
iconColor: 'alert',
icon: 'error',
buttonColor: 'alert',
};
}

Expand Down Expand Up @@ -88,25 +106,17 @@ export function useImageDetailedViewOverlayStyles(props: ImageDetailedViewOverla
const { responsive } = useResponsiveStyle();
const { palette } = useMonkTheme();

let overlayDisplayJustifyContent = 'space-between';
if (!isComplianceContainerDisplayed(props)) {
overlayDisplayJustifyContent = 'end';
}
if (!props.image.label) {
overlayDisplayJustifyContent = 'start';
}

return {
mainContainerStyle: {
...styles['mainContainer'],
...responsive(styles['mainContainerSmall']),
background: isComplianceContainerDisplayed(props)
background: isImageValid(props)
? 'linear-gradient(rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0) 50%)'
: 'transparent',
},
overlayDisplayStyle: {
...styles['overlayDisplay'],
justifyContent: overlayDisplayJustifyContent,
justifyContent: props.image.label ? 'space-between' : 'start',
},
complianceContainerStyle: {
...styles['complianceContainer'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export const InspectionGallery = i18nWrap((props: InspectionGalleryProps) => {
};

const handleRetakeImage = (image: Image | null) => {
if (props.captureMode && image?.additionalData?.sight_id) {
if (props.captureMode && image?.sightId) {
props.onNavigateToCapture?.({
reason: NavigateToCaptureReason.RETAKE_PICTURE,
sightId: image?.additionalData.sight_id,
sightId: image.sightId,
});
} else if (props.captureMode && image?.type === ImageType.CLOSE_UP) {
props.onNavigateToCapture?.({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function getSightSortIndex(item: InspectionGalleryItem, inspectionSights?: Sight
if (item.isAddDamage) {
return defaultIndex + 1;
}
const sightId = item.isTaken ? item.image.additionalData?.sight_id : item.sightId;
const sightId = item.isTaken ? item.image.sightId : item.sightId;
if (!sightId) {
return defaultIndex;
}
Expand Down Expand Up @@ -66,10 +66,7 @@ function getItems(
inspectionSights?.forEach((sight) => {
if (
captureMode &&
!items.find(
(item) =>
!item.isAddDamage && item.isTaken && item.image.additionalData?.sight_id === sight.id,
)
!items.find((item) => !item.isAddDamage && item.isTaken && item.image.sightId === sight.id)
) {
items.push({ isTaken: false, isAddDamage: false, sightId: sight.id });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useTranslation } from 'react-i18next';

jest.mock('../../../../src/components/Button', () => ({
Button: jest.fn(() => <></>),
}));
Expand All @@ -11,17 +9,24 @@ jest.mock('../../../../src/components/ImageDetailedView/ImageDetailedViewOverlay
'../../../../src/components/ImageDetailedView/ImageDetailedViewOverlay/hooks',
),
useImageLabelIcon: jest.fn(() => null),
useComplianceLabels: jest.fn(() => null),
isComplianceContainerDisplayed: jest.fn(() => false),
useRetakeOverlay: jest.fn(() => ({
title: '',
description: '',
iconColor: '',
icon: '',
buttonColor: '',
})),
isImageValid: jest.fn(() => true),
}));

import { useTranslation } from 'react-i18next';
import { expectPropsOnChildMock } from '@monkvision/test-utils';
import { render, screen } from '@testing-library/react';
import { ImageDetailedViewOverlay } from '../../../../src/components/ImageDetailedView/ImageDetailedViewOverlay';
import {
ImageDetailedViewOverlayProps,
isComplianceContainerDisplayed,
useComplianceLabels,
isImageValid,
useRetakeOverlay,
useImageLabelIcon,
} from '../../../../src/components/ImageDetailedView/ImageDetailedViewOverlay/hooks';
import { Image, ImageStatus } from '@monkvision/types';
Expand All @@ -41,46 +46,35 @@ describe('ImageDetailedViewOverlay component', () => {
jest.clearAllMocks();
});

it('should display a retake button if the compliance container is displayed', () => {
(isComplianceContainerDisplayed as jest.Mock).mockImplementationOnce(() => true);
const props = createProps();
const { unmount } = render(<ImageDetailedViewOverlay {...props} />);

expect(useTranslation).toHaveBeenCalled();
const { t } = (useTranslation as jest.Mock).mock.results[0].value;
expect(t).toHaveBeenCalledWith('retake');
expectPropsOnChildMock(Button, {
children: 'retake',
onClick: expect.any(Function),
[true, false].forEach((isValid) => {
it(`should display a retake button if isImageValid is ${isValid}`, () => {
(isImageValid as jest.Mock).mockImplementationOnce(() => isValid);
const props = createProps();
const { unmount } = render(<ImageDetailedViewOverlay {...props} />);

expect(useTranslation).toHaveBeenCalled();
const { t } = (useTranslation as jest.Mock).mock.results[0].value;
expect(t).toHaveBeenCalledWith('retake');
expectPropsOnChildMock(Button, {
children: 'retake',
onClick: expect.any(Function),
});
const { onClick } = (Button as unknown as jest.Mock).mock.calls.find(
(args) => args[0].children === 'retake',
)[0];
expect(props.onRetake).not.toHaveBeenCalled();
onClick();
expect(props.onRetake).toHaveBeenCalled();

unmount();
});
const { onClick } = (Button as unknown as jest.Mock).mock.calls.find(
(args) => args[0].children === 'retake',
)[0];
expect(props.onRetake).not.toHaveBeenCalled();
onClick();
expect(props.onRetake).toHaveBeenCalled();

unmount();
});

it('should not display a retake button if the compliance container is not displayed', () => {
(isComplianceContainerDisplayed as jest.Mock).mockImplementationOnce(() => false);
const props = createProps();
const { unmount } = render(<ImageDetailedViewOverlay {...props} />);

expect(Button).not.toHaveBeenCalledWith(
expect.objectContaining({ children: 'retake' }),
expect.anything(),
);

unmount();
});

it('should contain the compliance labels if the compliance container is displayed', () => {
it('should contain the retake labels', () => {
const title = 'test-title-test';
const description = 'test-description-test';
(isComplianceContainerDisplayed as jest.Mock).mockImplementationOnce(() => true);
(useComplianceLabels as jest.Mock).mockImplementationOnce(() => ({ title, description }));
(isImageValid as jest.Mock).mockImplementationOnce(() => true);
(useRetakeOverlay as jest.Mock).mockImplementationOnce(() => ({ title, description }));
const props = createProps();
const { unmount } = render(<ImageDetailedViewOverlay {...props} />);

Expand All @@ -90,20 +84,6 @@ describe('ImageDetailedViewOverlay component', () => {
unmount();
});

it('should not contain the compliance labels if the compliance container is not displayed', () => {
const title = 'test-title-test';
const description = 'test-description-test';
(isComplianceContainerDisplayed as jest.Mock).mockImplementationOnce(() => false);
(useComplianceLabels as jest.Mock).mockImplementationOnce(() => ({ title, description }));
const props = createProps();
const { unmount } = render(<ImageDetailedViewOverlay {...props} />);

expect(screen.queryByText(title)).toBeNull();
expect(screen.queryByText(description)).toBeNull();

unmount();
});

it('should display the image label with the proper icon', () => {
const props = createProps();
props.image.label = { en: 'test', fr: 'fr', de: 'test-de', nl: 'test-nl' };
Expand Down
Loading

0 comments on commit b771311

Please sign in to comment.