From 601f2566cb4a42d4dbb8e910b13779071f246978 Mon Sep 17 00:00:00 2001 From: Anikesh Suresh Date: Mon, 16 Dec 2024 13:23:15 +0000 Subject: [PATCH] generalise edit image dialog for attachemnts #1075 --- src/api/api.types.tsx | 6 +- src/api/images.test.tsx | 6 +- src/api/images.tsx | 13 +-- ....tsx => editFileDialog.component.test.tsx} | 23 +++--- ...onent.tsx => editFileDialog.component.tsx} | 82 ++++++++++--------- src/common/images/imageGallery.component.tsx | 10 ++- src/form.schemas.tsx | 4 +- src/mocks/handlers.ts | 4 +- 8 files changed, 82 insertions(+), 66 deletions(-) rename src/common/images/{editImageDialog.component.test.tsx => editFileDialog.component.test.tsx} (88%) rename src/common/images/{editImageDialog.component.tsx => editFileDialog.component.tsx} (65%) diff --git a/src/api/api.types.tsx b/src/api/api.types.tsx index 2cd5ebad2..186cdad9e 100644 --- a/src/api/api.types.tsx +++ b/src/api/api.types.tsx @@ -250,7 +250,11 @@ export interface ImagePost { description?: string | null; } -export type ImagePatch = Partial; +export interface ObjectFilePatch { + file_name?: string; + title?: string | null; + description?: string | null; +} export interface APIImage extends Required>, diff --git a/src/api/images.test.tsx b/src/api/images.test.tsx index 23ad0b777..af13b2f1d 100644 --- a/src/api/images.test.tsx +++ b/src/api/images.test.tsx @@ -1,7 +1,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import ImagesJSON from '../mocks/Images.json'; import { hooksWrapperWithProviders } from '../testUtils'; -import { ImagePatch } from './api.types'; +import { ObjectFilePatch } from './api.types'; import { useGetImage, useGetImages, usePatchImage } from './images'; describe('images api functions', () => { @@ -65,7 +65,7 @@ describe('images api functions', () => { }); describe('usePatchImage', () => { - let mockDataPatch: ImagePatch; + let mockDataPatch: ObjectFilePatch; beforeEach(() => { mockDataPatch = { file_name: 'edited_image.jpeg', @@ -78,7 +78,7 @@ describe('images api functions', () => { wrapper: hooksWrapperWithProviders(), }); - result.current.mutate({ id: '1', image: mockDataPatch }); + result.current.mutate({ id: '1', fileMetadata: mockDataPatch }); await waitFor(() => expect(result.current.isSuccess).toBeTruthy()); expect(result.current.data).toEqual({ diff --git a/src/api/images.tsx b/src/api/images.tsx index da18381e9..fbbb39ead 100644 --- a/src/api/images.tsx +++ b/src/api/images.tsx @@ -7,7 +7,7 @@ import { } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { storageApi } from './api'; -import { APIImage, APIImageWithURL, ImagePatch } from './api.types'; +import { APIImage, APIImageWithURL, ObjectFilePatch } from './api.types'; export const getImage = async (id: string): Promise => { return storageApi.get(`/images/${id}`).then((response) => { @@ -50,20 +50,23 @@ export const useGetImages = ( }); }; -const patchImage = async (id: string, image: ImagePatch): Promise => { +const patchImage = async ( + id: string, + fileMetadata: ObjectFilePatch +): Promise => { return storageApi - .patch(`/images/${id}`, image) + .patch(`/images/${id}`, fileMetadata) .then((response) => response.data); }; export const usePatchImage = (): UseMutationResult< APIImage, AxiosError, - { id: string; image: ImagePatch } + { id: string; fileMetadata: ObjectFilePatch } > => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ id, image }) => patchImage(id, image), + mutationFn: ({ id, fileMetadata }) => patchImage(id, fileMetadata), onSuccess: (updatedImage: APIImage) => { queryClient.invalidateQueries({ queryKey: ['Images'] }); queryClient.invalidateQueries({ diff --git a/src/common/images/editImageDialog.component.test.tsx b/src/common/images/editFileDialog.component.test.tsx similarity index 88% rename from src/common/images/editImageDialog.component.test.tsx rename to src/common/images/editFileDialog.component.test.tsx index 3e4b26480..76081ba2d 100644 --- a/src/common/images/editImageDialog.component.test.tsx +++ b/src/common/images/editFileDialog.component.test.tsx @@ -3,26 +3,29 @@ import userEvent, { UserEvent } from '@testing-library/user-event'; import { http } from 'msw'; import { MockInstance } from 'vitest'; import { storageApi } from '../../api/api'; +import { usePatchImage } from '../../api/images'; import handleIMS_APIError from '../../handleIMS_APIError'; import ImagesJSON from '../../mocks/Images.json'; import { server } from '../../mocks/server'; import { renderComponentWithRouterProvider } from '../../testUtils'; -import EditImageDialog, { ImageDialogProps } from './editImageDialog.component'; +import EditFileDialog, { FileDialogProps } from './editFileDialog.component'; vi.mock('../../handleIMS_APIError'); -describe('Edit image dialog', () => { +describe('Edit file dialog', () => { const onClose = vi.fn(); - let props: ImageDialogProps; + let props: FileDialogProps; let user: UserEvent; const createView = () => { - return renderComponentWithRouterProvider(); + return renderComponentWithRouterProvider(); }; beforeEach(() => { props = { open: true, onClose: onClose, + fileType: 'Image', + usePatchFile: usePatchImage, }; user = userEvent.setup(); }); @@ -30,7 +33,7 @@ describe('Edit image dialog', () => { afterEach(() => { vi.clearAllMocks(); }); - const modifyImageValues = (values: { + const modifyFileValues = (values: { file_name?: string; title?: string; description?: string; @@ -56,7 +59,7 @@ describe('Edit image dialog', () => { beforeEach(() => { props = { ...props, - selectedImage: ImagesJSON[0], + selectedFile: ImagesJSON[0], }; axiosPatchSpy = vi.spyOn(storageApi, 'patch'); @@ -71,7 +74,7 @@ describe('Edit image dialog', () => { createView(); - modifyImageValues({ + modifyFileValues({ file_name: 'Image A', }); @@ -84,7 +87,7 @@ describe('Edit image dialog', () => { it('Edits an image correctly', async () => { createView(); - modifyImageValues({ + modifyFileValues({ file_name: 'test_file_name.jpeg', title: 'Test Title', description: 'Test Description', @@ -119,7 +122,7 @@ describe('Edit image dialog', () => { it('Required fields show error if they are whitespace or current value just removed', async () => { createView(); - modifyImageValues({ + modifyFileValues({ file_name: '', }); const saveButton = screen.getByRole('button', { name: 'Save' }); @@ -132,7 +135,7 @@ describe('Edit image dialog', () => { it('CatchAllError request works correctly and displays refresh page message', async () => { createView(); - modifyImageValues({ + modifyFileValues({ file_name: 'Error 500', }); const saveButton = screen.getByRole('button', { name: 'Save' }); diff --git a/src/common/images/editImageDialog.component.tsx b/src/common/images/editFileDialog.component.tsx similarity index 65% rename from src/common/images/editImageDialog.component.tsx rename to src/common/images/editFileDialog.component.tsx index 500807561..9648598b1 100644 --- a/src/common/images/editImageDialog.component.tsx +++ b/src/common/images/editFileDialog.component.tsx @@ -15,31 +15,37 @@ import { useForm } from 'react-hook-form'; import React from 'react'; +import { UseMutationResult } from '@tanstack/react-query'; import { AxiosError } from 'axios'; -import { APIImage, ImagePatch } from '../../api/api.types'; -import { usePatchImage } from '../../api/images'; -import { ImagesSchema } from '../../form.schemas'; +import { APIImage, ObjectFilePatch } from '../../api/api.types'; +import { FileSchema } from '../../form.schemas'; import handleIMS_APIError from '../../handleIMS_APIError'; -export interface ImageDialogProps { +export interface FileDialogProps { open: boolean; onClose: () => void; - selectedImage?: APIImage; + selectedFile?: APIImage; + fileType: 'Image' | 'Attachment'; + usePatchFile: () => UseMutationResult< + APIImage, + AxiosError, + { id: string; fileMetadata: ObjectFilePatch } + >; } -const EditImageDialog = (props: ImageDialogProps) => { - const { open, onClose, selectedImage } = props; +const EditFileDialog = (props: FileDialogProps) => { + const { open, onClose, selectedFile, fileType, usePatchFile } = props; - const { mutateAsync: patchImage, isPending: isEditPending } = usePatchImage(); + const { mutateAsync: patchFile, isPending: isEditPending } = usePatchFile(); - const initalImage: ImagePatch = React.useMemo( + const initialFile: ObjectFilePatch = React.useMemo( () => - selectedImage ?? { + selectedFile ?? { file_name: '', title: '', description: '', }, - [selectedImage] + [selectedFile] ); const { @@ -50,15 +56,15 @@ const EditImageDialog = (props: ImageDialogProps) => { setError, clearErrors, reset, - } = useForm({ - resolver: zodResolver(ImagesSchema('patch')), - defaultValues: initalImage, + } = useForm({ + resolver: zodResolver(FileSchema('patch')), + defaultValues: initialFile, }); // Load the values for editing React.useEffect(() => { - reset(initalImage); - }, [initalImage, reset]); + reset(initialFile); + }, [initialFile, reset]); // Clears form errors when a value has been changed React.useEffect(() => { @@ -66,7 +72,7 @@ const EditImageDialog = (props: ImageDialogProps) => { const subscription = watch(() => clearErrors('root.formError')); return () => subscription.unsubscribe(); } - }, [clearErrors, errors, selectedImage, watch]); + }, [clearErrors, errors, selectedFile, watch]); const handleClose = React.useCallback(() => { reset(); @@ -74,28 +80,26 @@ const EditImageDialog = (props: ImageDialogProps) => { onClose(); }, [clearErrors, onClose, reset]); - const handleEditImage = React.useCallback( - (imageData: ImagePatch) => { - if (selectedImage) { - const isFileNameUpdated = - imageData.file_name !== selectedImage.file_name; + const handleEditFile = React.useCallback( + (fileData: ObjectFilePatch) => { + if (selectedFile) { + const isFileNameUpdated = fileData.file_name !== selectedFile.file_name; const isDescriptionUpdated = - imageData.description !== selectedImage.description; + fileData.description !== selectedFile.description; - const isTitleUpdated = imageData.title !== selectedImage.title; + const isTitleUpdated = fileData.title !== selectedFile.title; - let imageToEdit: ImagePatch = {}; + let fileToEdit: ObjectFilePatch = {}; - if (isFileNameUpdated) imageToEdit.file_name = imageData.file_name; - if (isDescriptionUpdated) - imageToEdit.description = imageData.description; - if (isTitleUpdated) imageToEdit.title = imageData.title; + if (isFileNameUpdated) fileToEdit.file_name = fileData.file_name; + if (isDescriptionUpdated) fileToEdit.description = fileData.description; + if (isTitleUpdated) fileToEdit.title = fileData.title; if (isFileNameUpdated || isDescriptionUpdated || isTitleUpdated) { - patchImage({ - id: selectedImage.id, - image: imageToEdit, + patchFile({ + id: selectedFile.id, + fileMetadata: fileToEdit, }) .then(() => handleClose()) .catch((error: AxiosError) => { @@ -109,17 +113,17 @@ const EditImageDialog = (props: ImageDialogProps) => { } } }, - [selectedImage, patchImage, handleClose, setError] + [selectedFile, patchFile, handleClose, setError] ); return ( - {`Edit Image`} + {`Edit ${fileType}`} { { {