diff --git a/frontend/package.json b/frontend/package.json
index a9c5a698858..b7199e50727 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -94,7 +94,7 @@
"react-google-recaptcha": "2.1.0",
"react-gtm-module": "2.0.11",
"react-highlight-words": "0.17.0",
- "react-hook-form": "7.12.2",
+ "react-hook-form": "7.61.1",
"react-intl": "5.21.0",
"react-is": "17.0.2",
"react-konva": "17.0.2-5",
diff --git a/frontend/src/v5/store/drawings/revisions/drawingRevisions.types.ts b/frontend/src/v5/store/drawings/revisions/drawingRevisions.types.ts
index 246fa69334e..a372c65ba7c 100644
--- a/frontend/src/v5/store/drawings/revisions/drawingRevisions.types.ts
+++ b/frontend/src/v5/store/drawings/revisions/drawingRevisions.types.ts
@@ -74,3 +74,5 @@ export type UploadItemFields = CreateDrawingRevisionBody & {
extension: string;
isMultiPagePdf?: boolean;
};
+
+export type UploadDrawingFormType = { uploads: UploadItemFields[] };
\ No newline at end of file
diff --git a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfileModal.component.tsx b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfileModal.component.tsx
index 06878a1332a..a4b57c3e0a1 100644
--- a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfileModal.component.tsx
+++ b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfileModal.component.tsx
@@ -58,7 +58,7 @@ export const EditProfileModal = ({ open, onClickClose, initialTab }: EditProfile
const defaultPersonalValues = defaults(
pick(omitBy(user, isNull), ['firstName', 'lastName', 'email', 'company', 'countryCode']),
- { company: '', avatarFile: '' },
+ { company: '', avatarFile: undefined },
);
const onTabChange = (_, selectedTab) => setActiveTab(selectedTab);
diff --git a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfileAvatar/editProfileAvatar.component.tsx b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfileAvatar/editProfileAvatar.component.tsx
index b29017e33f1..c2fc8174a8a 100644
--- a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfileAvatar/editProfileAvatar.component.tsx
+++ b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfileAvatar/editProfileAvatar.component.tsx
@@ -110,7 +110,7 @@ export const EditProfileAvatar = ({ user }: EditProfilePersonalTabProps) => {
/>
- {(fileSizeError || error) && }
+ {(fileSizeError || error) && }
>
);
};
diff --git a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfilePersonalTab.component.tsx b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfilePersonalTab.component.tsx
index e8c4b027bb0..45b8303795a 100644
--- a/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfilePersonalTab.component.tsx
+++ b/frontend/src/v5/ui/components/shared/userMenu/editProfileModal/editProfilePersonalTab/editProfilePersonalTab.component.tsx
@@ -61,7 +61,7 @@ export const EditProfilePersonalTab = ({
reset,
setError: setFormError,
control,
- formState: { errors: formErrors, isDirty, touchedFields, isSubmitting },
+ formState: { errors: formErrors, isSubmitting },
} = useFormContext();
const userIsMissingRequiredData = userHasMissingRequiredData(user);
@@ -107,10 +107,9 @@ export const EditProfilePersonalTab = ({
const canSubmit = isEmpty(formErrors) && fieldsAreDirty;
useEffect(() => {
- if (submitWasSuccessful) {
- setSubmitWasSuccessful(false);
- }
- }, [JSON.stringify(isDirty), touchedFields]);
+ if (!fieldsAreDirty || !submitWasSuccessful) return
+ setSubmitWasSuccessful(false);
+ }, [fieldsAreDirty]);
useEffect(() => {
if (userIsMissingRequiredData) {
diff --git a/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/createDrawingDialog.component.tsx b/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/createDrawingDialog.component.tsx
index 02cf188bbdd..4803ca4796d 100644
--- a/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/createDrawingDialog.component.tsx
+++ b/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/createDrawingDialog.component.tsx
@@ -49,7 +49,11 @@ export const CreateDrawingDialog = ({ open, onClickClose }) => {
onSubmit={handleSubmit(onSubmit)}
confirmLabel={formatMessage({ id: 'drawings.creation.ok', defaultMessage: 'Create Drawing' })}
maxWidth="sm"
- {...formState}
+ // {...formState} Some properties in formState where
+ // converted from enumerable to non-enumerable isValid one of them so {...formstate} will no longer
+ // pass the correct values.
+ isValid={formState.isValid}
+ isSubmitting={formState.isSubmitting}
>
diff --git a/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/editDrawingDialog.component.tsx b/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/editDrawingDialog.component.tsx
index 2a0706a60d4..35f7044a9d3 100644
--- a/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/editDrawingDialog.component.tsx
+++ b/frontend/src/v5/ui/routes/dashboard/projects/drawings/drawingDialogs/editDrawingDialog.component.tsx
@@ -72,7 +72,7 @@ export const EditDrawingDialog = ({ open, onClickClose, drawingId }:Props) => {
onSubmit={handleSubmit(onSubmit)}
confirmLabel={formatMessage({ id: 'drawings.edit.ok', defaultMessage: 'Save Drawing' })}
maxWidth="sm"
- {...formState}
+ isSubmitting={formState.isSubmitting}
isValid={dirtyValuesChanged(formData, drawingId) && formState.isValid}
>
{drawing.calibration.units
diff --git a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/sidebarForm/sidebarForm.component.tsx b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/sidebarForm/sidebarForm.component.tsx
index 9f8fe7baff2..a18817b177c 100644
--- a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/sidebarForm/sidebarForm.component.tsx
+++ b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/sidebarForm/sidebarForm.component.tsx
@@ -17,7 +17,7 @@
import { formatMessage } from '@/v5/services/intl';
import { FormattedMessage } from 'react-intl';
-import { useFormContext } from 'react-hook-form';
+import { FieldError, useFormContext } from 'react-hook-form';
import { MenuItem } from '@mui/material';
import { FormNumberField, FormSelect, FormTextField } from '@controls/inputs/formInputs.component';
import { get, isNumber } from 'lodash';
@@ -30,16 +30,17 @@ import { DoubleInputLineContainer } from '../../drawingDialogs/drawingForm.style
import { Loader } from '@/v4/routes/components/loader/loader.component';
import { DrawingRevisionsActionsDispatchers, DrawingsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { CALIBRATION_INVALID_RANGE_ERROR } from '@/v5/validation/drawingSchemes/drawingSchemes';
+import { UploadDrawingFormType } from '@/v5/store/drawings/revisions/drawingRevisions.types';
export const SidebarForm = () => {
const teamspace = TeamspacesHooksSelectors.selectCurrentTeamspace();
const project = ProjectsHooksSelectors.selectCurrentProject();
const types = DrawingsHooksSelectors.selectTypes();
- const { getValues, formState: { errors, dirtyFields }, trigger, watch } = useFormContext();
+ const { getValues, formState: { errors, dirtyFields }, trigger, watch } = useFormContext();
const { fields, selectedId } = useContext(UploadFilesContext);
// @ts-ignore
- const selectedIndex = fields.findIndex(({ uploadId }) => uploadId === selectedId);
- const revisionPrefix = `uploads.${selectedIndex}`;
+ const selectedIndex: number = fields.findIndex(({ uploadId }) => uploadId === selectedId);
+ const revisionPrefix = `uploads.${selectedIndex}` as `uploads.${number}`;
const [drawingId, drawingName] = getValues([`${revisionPrefix}.drawingId`, `${revisionPrefix}.drawingName`]);
const disableDrawingFields = !(drawingName && !drawingId);
const getError = (field: string) => get(errors, `${revisionPrefix}.${field}`);
@@ -48,7 +49,8 @@ export const SidebarForm = () => {
const hasPendingRevisions = !!DrawingRevisionsHooksSelectors.selectRevisionsPending(drawingId);
const drawingRevisionsArePending = !!DrawingRevisionsHooksSelectors.selectIsPending(drawingId);
const needsFetchingCalibration = hasActiveRevisions && (hasPendingRevisions || drawingRevisionsArePending) && !isNumber(verticalRange[0]);
- const hideBottomExtentError = (errors.calibration?.verticalRange || []).some((e) => e.message === CALIBRATION_INVALID_RANGE_ERROR);
+ const hideBottomExtentError = (errors.uploads?.[selectedIndex]?.calibration?.verticalRange || [])
+ .some((e: FieldError) => e.message === CALIBRATION_INVALID_RANGE_ERROR);
useEffect(() => {
if (get(dirtyFields, `${revisionPrefix}.calibration.verticalRange`)?.some((v) => v)) {
diff --git a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadDrawingRevisionForm.component.tsx b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadDrawingRevisionForm.component.tsx
index cb9e559df99..e75cfef7397 100644
--- a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadDrawingRevisionForm.component.tsx
+++ b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadDrawingRevisionForm.component.tsx
@@ -29,14 +29,14 @@ import {
} from '@/v5/services/selectorsHooks';
import { getSupportedDrawingRevisionsFileExtensions } from '@controls/fileUploader/uploadFile';
import { UploadFiles } from '@components/shared/uploadFiles/uploadFiles.component';
-import { UploadFieldArray, UploadFilesContextComponent } from '@components/shared/uploadFiles/uploadFilesContext';
+import { UploadFilesContextComponent } from '@components/shared/uploadFiles/uploadFilesContext';
import { SidebarForm } from './sidebarForm/sidebarForm.component';
import { IDrawing } from '@/v5/store/drawings/drawings.types';
import { DrawingRevisionsActionsDispatchers, DrawingsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { UploadList } from './uploadList/uploadList.component';
import { parseFileName, reduceFileData, isPdf, getPdfFirstPage, fileToPdf, pdfToFile } from '@components/shared/uploadFiles/uploadFiles.helpers';
-import { UploadItemFields } from '@/v5/store/drawings/revisions/drawingRevisions.types';
import { formatInfoUnit } from '@/v5/helpers/intl.helper';
+import { UploadDrawingFormType } from '@/v5/store/drawings/revisions/drawingRevisions.types';
const REVISION_NAME_MAX_LENGTH = 50;
@@ -44,8 +44,6 @@ type UploadModalLabelTypes = {
isUploading: boolean;
fileCount: number;
};
-type FormType = UploadFieldArray;
-
const uploadModalLabels = ({ isUploading, fileCount }: UploadModalLabelTypes) => (isUploading
? {
title: formatMessage({
@@ -94,7 +92,7 @@ export const UploadDrawingRevisionForm = ({
const [isPresetLoading, setIsPresetLoading] = useState(!!presetFile);
const [isUploading, setIsUploading] = useState(false);
- const formData = useForm({
+ const formData = useForm({
mode: 'onChange',
resolver: !isUploading ? yupResolver(UploadsSchema) : undefined,
context: {
diff --git a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadList/uploadListItem/uploadListItem.component.tsx b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadList/uploadListItem/uploadListItem.component.tsx
index 13dea8f667c..027c90573cd 100644
--- a/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadList/uploadListItem/uploadListItem.component.tsx
+++ b/frontend/src/v5/ui/routes/dashboard/projects/drawings/uploadDrawingRevisionForm/uploadList/uploadListItem/uploadListItem.component.tsx
@@ -137,9 +137,12 @@ export const UploadListItem = ({
}, [drawingId, revCodeError]);
useEffect(() => {
- setValue(revisionPrefix, sanitiseDrawing(selectedDrawing));
- }, [JSON.stringify(selectedDrawing)]);
+ for (const [key, val] of Object.entries(sanitiseDrawing(selectedDrawing))) {
+ setValue(`${revisionPrefix}.${key}`, val);
+ }
+ }, [JSON.stringify(selectedDrawing)]);
+
useEffect(() => {
if (selectedDrawing?._id) {
if (selectRevisionsPending(getState(), selectedDrawing._id)) {
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/newTicket/newTicket.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/newTicket/newTicket.component.tsx
index 0ac81781b6c..1086de07d05 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/newTicket/newTicket.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/newTicket/newTicket.component.tsx
@@ -57,7 +57,7 @@ export const NewTicketCard = () => {
const formData = useForm({
resolver: yupResolver(isLoading ? null : getTicketValidator(template)),
- mode: 'onChange',
+ mode: 'all',
defaultValues: defaultTicket,
});
@@ -92,7 +92,7 @@ export const NewTicketCard = () => {
await promiseToResolve;
};
- const updateUnsavedTicket = () => TicketsCardActionsDispatchers.setUnsavedTicket(formData.getValues());
+ const updateUnsavedTicket = () => TicketsCardActionsDispatchers.setUnsavedTicket(cloneDeep(formData.getValues()));
useEffect(() => {
if (!templateAlreadyFetched(template)) {
@@ -162,7 +162,6 @@ export const NewTicketCard = () => {
// Im not sure this is still needed here, because we are already depending on react-hook-form to fill the form
ticket={defaultTicket}
focusOnTitle
- onPropertyBlur={updateUnsavedTicket}
/>
)}
>
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/commentsPanel/commentBox/commentBox.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/commentsPanel/commentBox/commentBox.component.tsx
index f1389910ce2..0c91537dd8a 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/commentsPanel/commentBox/commentBox.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/commentsPanel/commentBox/commentBox.component.tsx
@@ -100,7 +100,7 @@ export const CommentBox = ({ commentId, onCancelEdit, message = '', images = [],
return { ...image, src: getTicketResourceUrl(teamspace, project, containerOrFederation, ticketId, image.id, isFederation) };
});
- const { watch, reset, control } = useForm<{ message: string, images: File[] }>({
+ const { watch, reset, control } = useForm<{ message: string, images: string[] }>({
mode: 'all',
defaultValues: {
message: desanitiseMessage(message),
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/properties.hooks.ts b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/properties.hooks.ts
new file mode 100644
index 00000000000..83aff83288c
--- /dev/null
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/properties.hooks.ts
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2025 3D Repo Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useRef, useEffect } from "react";
+
+export const useChangeAndBlur = (value, onChange, onBlur) => {
+ const shouldBlur = useRef(false);
+
+ useEffect(() => {
+ if (!shouldBlur.current) return;
+ shouldBlur.current = false;
+ onBlur?.()
+ }, [value, onBlur]);
+
+ return (val) => {
+ shouldBlur.current = true;
+ onChange?.(val);
+ }
+};
\ No newline at end of file
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageContent/ticketImage/ticketImage.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageContent/ticketImage/ticketImage.component.tsx
index 57e665eba6c..ef17e2df7b5 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageContent/ticketImage/ticketImage.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageContent/ticketImage/ticketImage.component.tsx
@@ -26,33 +26,34 @@ import { InputContainer, Label } from './ticketImage.styles';
import { ImagesModal } from '@components/shared/modalsDispatcher/templates/imagesModal/imagesModal.component';
import { DialogsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { useSyncProps } from '@/v5/helpers/syncProps.hooks';
+import { useChangeAndBlur as useChangeAndBlur } from '../../properties.hooks';
type TicketImageProps = FormInputProps & { onImageClick: () => void; inputRef? };
export const TicketImage = ({ value, onChange, onBlur, disabled, label, helperText, onImageClick, inputRef = undefined, ...props }: TicketImageProps) => {
const imgSrc = getImgSrc(value);
const [imgInModal, setImgInModal] = useState(imgSrc);
const syncProps = useSyncProps({ images: [imgInModal] });
+ const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);
const handleImageClick = () => DialogsActionsDispatchers.open(ImagesModal, {
onAddMarkup: disabled
? null
- : (newValue) => onChange(newValue ? stripBase64Prefix(newValue) : null),
+ : (newValue) => onChangeAndBlur(newValue ? stripBase64Prefix(newValue) : null),
}, syncProps);
const onUploadNewImage = (newValue) => {
if (!newValue) {
- onChange(newValue);
+ onChangeAndBlur(newValue);
return;
}
setImgInModal(newValue);
DialogsActionsDispatchers.open(ImagesModal, {
- onClose: (newImages) => onChange(stripBase64Prefix(newImages[0])),
+ onClose: (newImages) => onChangeAndBlur(stripBase64Prefix(newImages[0])),
onAddMarkup: setImgInModal,
}, syncProps);
};
- useEffect(() => onBlur?.(), [value]);
useEffect(() => { setImgInModal(imgSrc); }, [imgSrc]);
return (
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageList/ticketImageList.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageList/ticketImageList.component.tsx
index 6eab2db22ec..07dfec6f53f 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageList/ticketImageList.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketImageList/ticketImageList.component.tsx
@@ -41,6 +41,7 @@ import EmptyImageIcon from '@assets/icons/outlined/add_image_thin-outlined.svg';
import { uploadImages } from '@controls/fileUploader/uploadImages';
import { DrawingViewerService } from '@components/viewer/drawingViewer/drawingViewer.service';
import { ViewerCanvasesContext } from '../../../../viewerCanvases.context';
+import { useChangeAndBlur } from '../properties.hooks';
const EmptyImage = ({ disabled, onClick }) => (
@@ -72,9 +73,10 @@ export const TicketImageList = ({ value, onChange, onBlur, disabled, label, help
const isProjectAdmin = ProjectsHooksSelectors.selectIsProjectAdmin();
const imgsSrcs = (value || []).map((img) => getImgSrc(img, imgContext));
const [imgsInModal, setImgsInModal] = useState(imgsSrcs);
-
- const onClose = () => onChange(imgsInModal?.length ? imgsInModal.map(getImgIdFromSrc) : null);
- const onDeleteImages = () => onChange(null);
+ const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);
+
+ const onClose = () => onChangeAndBlur(imgsInModal?.length ? imgsInModal.map(getImgIdFromSrc) : null);
+ const onDeleteImages = () => onChangeAndBlur(null);
const onDeleteImage = (index) => setImgsInModal(imgsInModal.filter((img, i) => index !== i));
const onEditImage = (newValue, index) => {
if (newValue) {
@@ -108,15 +110,10 @@ export const TicketImageList = ({ value, onChange, onBlur, disabled, label, help
setImgsInModal(imgsInModal.concat(screenshot));
openImagesModal(displayImageIndex);
};
+
const upload3DScreenshot = async () => uploadScreenshot(await ViewerService.getScreenshot());
const upload2DScreenshot = async () => uploadScreenshot(await DrawingViewerService.getScreenshot());
-
- useEffect(() => {
- setImgsInModal(imgsSrcs);
- onBlur?.();
- }, [value]);
-
return (
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketView/ticketView.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketView/ticketView.component.tsx
index fcd8ae19436..3f412f7c2d0 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketView/ticketView.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/ticketView/ticketView.component.tsx
@@ -19,7 +19,7 @@ import { Viewer as ViewerService } from '@/v4/services/viewer/viewer';
import ViewpointIcon from '@assets/icons/outlined/aim-outlined.svg';
import TickIcon from '@assets/icons/outlined/tick-outlined.svg';
import { stripBase64Prefix } from '@controls/fileUploader/imageFile.helper';
-import { useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { cloneDeep, isEmpty } from 'lodash';
import { getImgSrc } from '@/v5/store/tickets/tickets.helpers';
@@ -40,6 +40,7 @@ import { GroupsActionMenu } from './viewActionMenu/menus/groupsActionMenu.compon
import { ViewerInputContainer } from '../viewerInputContainer/viewerInputContainer.component';
import { useSyncProps } from '@/v5/helpers/syncProps.hooks';
import { DialogsActionsDispatchers } from '@/v5/services/actionsDispatchers';
+import { useChangeAndBlur } from '../properties.hooks';
type ITicketView = {
value: Viewpoint | undefined;
@@ -67,12 +68,13 @@ export const TicketView = ({
const imgSrc = getImgSrc(value?.screenshot);
const [imgInModal, setImgInModal] = useState(imgSrc);
const syncProps = useSyncProps({ images: [imgInModal] });
+ const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);
// Image
const handleImageClick = () => DialogsActionsDispatchers.open(ImagesModal, {
onAddMarkup: disabled
? null
- : (newValue) => onChange({ ...value, screenshot: stripBase64Prefix(newValue) }),
+ : (newValue) => onChangeAndBlur({ ...value, screenshot: stripBase64Prefix(newValue) }),
}, syncProps);
const handleNewImageUpload = (newImage, onSave) => {
@@ -85,11 +87,11 @@ export const TicketView = ({
const onUpdateImage = (newValue) => {
if (!newValue) {
- onChange({ ...value, screenshot: null });
+ onChangeAndBlur({ ...value, screenshot: null });
return;
}
- handleNewImageUpload(newValue, (newImage) => onChange({ ...value, screenshot: newImage }));
+ handleNewImageUpload(newValue, (newImage) => onChangeAndBlur({ ...value, screenshot: newImage }));
};
// Viewpoint
@@ -98,18 +100,18 @@ export const TicketView = ({
const screenshot = await ViewerService.getScreenshot();
const state = await getViewerState();
- handleNewImageUpload(screenshot, (newImage) => onChange({ screenshot: newImage, ...currentCameraAndClipping, state }));
+ handleNewImageUpload(screenshot, (newImage) => onChangeAndBlur({ screenshot: newImage, ...currentCameraAndClipping, state }));
};
// Camera
const onUpdateCamera = async () => {
const currentCameraAndClipping = await ViewerService.getViewpoint();
- onChange?.({ ...value, ...currentCameraAndClipping });
+ onChangeAndBlur?.({ ...value, ...currentCameraAndClipping });
};
const onDeleteCamera = async () => {
const { camera, ...view } = value || {};
- onChange?.(isEmpty(view) ? null : view);
+ onChangeAndBlur?.(isEmpty(view) ? null : view);
};
const onGoToCamera = async () => {
@@ -126,10 +128,9 @@ export const TicketView = ({
state.colored = [];
state.hidden = [];
state.transformed = [];
- onChange?.({ state, ...view });
+ onChangeAndBlur?.({ state, ...view });
};
- useEffect(() => onBlur?.(), [value]);
useEffect(() => { setImgInModal(imgSrc); }, [imgSrc]);
const onGroupsClick = () => {
diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/ticketGroups/groups/groupActionMenu/groupSettingsForm/groupSettingsForm.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/ticketGroups/groups/groupActionMenu/groupSettingsForm/groupSettingsForm.component.tsx
index 6c9cb3ec2f4..7e38e269b29 100644
--- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/ticketGroups/groups/groupActionMenu/groupSettingsForm/groupSettingsForm.component.tsx
+++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/ticketGroups/groups/groupActionMenu/groupSettingsForm/groupSettingsForm.component.tsx
@@ -133,6 +133,7 @@ export const GroupSettingsForm = ({ value, onSubmit, onCancel, prefixes, isColor
formState: { errors, isValid, isDirty },
setValue,
getValues,
+ trigger,
} = formData;
const getFormIsValid = () => {
@@ -221,6 +222,10 @@ export const GroupSettingsForm = ({ value, onSubmit, onCancel, prefixes, isColor
});
}, []);
+ useEffect(() => {
+ if (!isDirty) return;
+ trigger();
+ }, [isSmart, isDirty]);
useEffect(() => {
setCurrentPrefixes(mergePrefixes(prefixes, subPrefixes(newPrefix)));
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 32a7a62a289..d1448f6bfff 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -12528,10 +12528,10 @@ react-highlight-words@0.17.0:
memoize-one "^4.0.0"
prop-types "^15.5.8"
-react-hook-form@7.12.2:
- version "7.12.2"
- resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.12.2.tgz#2660afbf03c4ef360a9314ebf46ce3d972296c77"
- integrity sha512-cpxocjrgpMAJCMJQR51BQhMoEx80/EQqePNihMTgoTYTqCRbd2GExi+N4GJIr+cFqrmbwNj9wxk5oLWYQsUefg==
+react-hook-form@7.61.1:
+ version "7.61.1"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.61.1.tgz#8c1f086ccd921a6e90df6800787e9ca3833f86c1"
+ integrity sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==
react-intl@5.21.0:
version "5.21.0"