From 8e6055372629cadcd6c0aab7fe43db59a6e8217a Mon Sep 17 00:00:00 2001 From: Aditya Gambhir <67105262+Aditya-gam@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:58:09 -0700 Subject: [PATCH 1/4] feat(equipment): send image with status update Add optional image param to updateEquipment; when present send multipart/form-data with field 'image' and do not set Content-Type. Restrict accepted formats to PNG/JPEG and 5MB in UpdateEquipment and DragAndDrop. Validate before submit and surface backend error message; clear file state on success and append note when image not saved on failure. Made-with: Cursor --- src/actions/bmdashboard/equipmentActions.js | 26 ++++++-- .../Equipment/Update/UpdateEquipment.jsx | 66 ++++++++++++------- .../common/DragAndDrop/DragAndDrop.jsx | 4 +- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/actions/bmdashboard/equipmentActions.js b/src/actions/bmdashboard/equipmentActions.js index 4de9f486c7..b8daa54a9c 100644 --- a/src/actions/bmdashboard/equipmentActions.js +++ b/src/actions/bmdashboard/equipmentActions.js @@ -98,7 +98,7 @@ export const updateMultipleEquipmentLogs = (projectId, bulkArr) => dispatch => { }); } -export const updateEquipment = (equipmentId, updateData) => async (dispatch, getState) => { +export const updateEquipment = (equipmentId, updateData, imageFile = null) => async (dispatch, getState) => { const url = `${ENDPOINTS.BM_EQUIPMENT_STATUS_UPDATE(equipmentId)}`; try { @@ -127,15 +127,30 @@ export const updateEquipment = (equipmentId, updateData) => async (dispatch, get const statusUpdateData = { condition: updateData.condition, - lastUsedBy: updateData.lastUsedBy, - lastUsedFor: updateData.lastUsedFor, - replacementRequired: updateData.replacementRequired, + lastUsedBy: updateData.lastUsedBy || '', + lastUsedFor: updateData.lastUsedFor || '', + replacementRequired: updateData.replacementRequired || '', description: updateData.description || '', notes: updateData.notes || '', createdBy: currentUserId, }; - const res = await axios.put(url, statusUpdateData); + let res; + if (imageFile) { + const formData = new FormData(); + formData.append('condition', statusUpdateData.condition); + formData.append('createdBy', statusUpdateData.createdBy); + formData.append('lastUsedBy', statusUpdateData.lastUsedBy); + formData.append('lastUsedFor', statusUpdateData.lastUsedFor); + formData.append('replacementRequired', statusUpdateData.replacementRequired); + formData.append('description', statusUpdateData.description); + formData.append('notes', statusUpdateData.notes); + formData.append('image', imageFile); + // Do NOT set Content-Type — axios sets multipart/form-data with the correct boundary + res = await axios.put(url, formData); + } else { + res = await axios.put(url, statusUpdateData); + } dispatch(setEquipment(res.data)); toast.success('Equipment status updated successfully!'); @@ -143,7 +158,6 @@ export const updateEquipment = (equipmentId, updateData) => async (dispatch, get return res.data; } catch (err) { - let errorMessage = 'Failed to update equipment status.'; if (err.response) { diff --git a/src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx b/src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx index 2c88181b25..181ec24699 100644 --- a/src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx +++ b/src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx @@ -11,6 +11,10 @@ import FilePreview from '~/components/common/FilePreview/FilePreview'; // Import import styles from './UpdateEquipment.module.css'; import styles1 from '../../BMDashboard.module.css'; +const ALLOWED_MIME_TYPES = ['image/png', 'image/jpeg']; +const MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024; +const INVALID_IMAGE_MSG = 'Invalid image. Use PNG, JPG, or JPEG under 5MB.'; + export default function UpdateEquipment() { const history = useHistory(); const { equipmentId } = useParams(); @@ -136,13 +140,14 @@ export default function UpdateEquipment() { const validFiles = files.filter(file => { const fileType = file.type || ''; const fileName = file.name || ''; - const isValidType = fileType.startsWith('image/'); - const isValidExtension = fileName.match(/\.(jpg|jpeg|png|gif|webp)$/i); - return isValidType || isValidExtension; + const isValidType = ALLOWED_MIME_TYPES.includes(fileType); + const isValidExtension = /\.(jpg|jpeg|png)$/i.test(fileName); + const isValidSize = file.size <= MAX_IMAGE_SIZE_BYTES; + return (isValidType || isValidExtension) && isValidSize; }); if (validFiles.length === 0) { - console.warn('No valid image files found'); + setSubmitError(INVALID_IMAGE_MSG); return; } @@ -172,6 +177,18 @@ export default function UpdateEquipment() { return; } + const imageFile = uploadedFiles.length > 0 ? uploadedFiles[0] : null; + + if (imageFile) { + const isValidType = ALLOWED_MIME_TYPES.includes(imageFile.type); + const isValidSize = imageFile.size <= MAX_IMAGE_SIZE_BYTES; + if (!isValidType || !isValidSize) { + setSubmitError(INVALID_IMAGE_MSG); + setIsSubmitting(false); + return; + } + } + setIsSubmitting(true); setSubmitError(''); setSubmitSuccess(''); @@ -186,36 +203,35 @@ export default function UpdateEquipment() { notes: sendNote === 'yes' ? notes : '', }; - await dispatch(updateEquipment(equipmentId, updateData)); + await dispatch(updateEquipment(equipmentId, updateData, imageFile)); dispatch(fetchEquipmentById(equipmentId)); setIsUpdated(true); - let successMessage = 'Equipment status updated successfully!'; - - if (uploadedFiles.length > 0) { - successMessage += ' The form has been updated.'; - cleanupFilePreviews(uploadedFilesPreview); - setUploadedFiles([]); - setUploadedFilesPreview([]); - } + const successMessage = imageFile + ? 'Equipment status updated successfully! Image saved.' + : 'Equipment status updated successfully!'; + cleanupFilePreviews(uploadedFilesPreview); + setUploadedFiles([]); + setUploadedFilesPreview([]); setSubmitSuccess(successMessage); if (lastUsedBy === 'other') setLastUsedByOther(''); if (lastUsedFor === 'other') setLastUsedForOther(''); } catch (error) { - console.error('Update failed:', error); - - let errorMessage = 'Failed to update equipment. Please try again.'; - - if (error.message?.includes('User not authenticated')) { - errorMessage = 'Authentication error. Please check if you are logged in.'; - } else if (error.response?.data?.error?.includes('Invalid user ID format')) { - errorMessage = 'User ID format error. Please contact support.'; - } + const baseError = + error.response?.data?.error || + error.response?.data?.message || + (error.request ? 'No response from server. Please check your connection.' : null) || + error.message || + 'Failed to update equipment. Please try again.'; + + const errorMessage = + uploadedFiles.length > 0 + ? `${baseError} Note: Images were selected and previewed but not saved to database.` + : baseError; if (uploadedFiles.length > 0) { - errorMessage += ' Note: Images were selected and previewed but not saved to database.'; setUploadedFilesPreview(prev => prev.map(file => ({ ...file, @@ -662,7 +678,7 @@ export default function UpdateEquipment() { @@ -720,7 +736,7 @@ export default function UpdateEquipment() { {hasNotSavedFiles ? 'Images are shown for preview purposes only and are not saved to the database.' - : 'Images are uploaded for preview. Form data will be saved to database.'} + : 'Images will be submitted with the form and saved to the database.'} diff --git a/src/components/common/DragAndDrop/DragAndDrop.jsx b/src/components/common/DragAndDrop/DragAndDrop.jsx index 18ce9a5fc2..60795a21d6 100644 --- a/src/components/common/DragAndDrop/DragAndDrop.jsx +++ b/src/components/common/DragAndDrop/DragAndDrop.jsx @@ -86,7 +86,7 @@ const DragAndDrop = ({ updateUploadedFiles }) => { type="file" name="file-upload-input" multiple={true} - accept="image/jpeg, image/jpg, image/png, image/gif, image/webp" + accept="image/png, image/jpeg, image/jpg" onChange={handleChange} aria-label="Upload image files" /> @@ -173,7 +173,7 @@ const DragAndDrop = ({ updateUploadedFiles }) => { id="file-upload-description" style={{ margin: '4px 0' }} > - Accepted: PNG, JPG, JPEG, GIF, WEBP + Accepted: PNG, JPG, JPEG (max 5MB)
| {el.code} | - + |