Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/actions/bmdashboard/equipmentActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -127,23 +127,37 @@ 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!');
dispatch(fetchEquipmentById(equipmentId));

return res.data;
} catch (err) {

let errorMessage = 'Failed to update equipment status.';

if (err.response) {
Expand Down
10 changes: 5 additions & 5 deletions src/components/BMDashboard/Equipment/List/EquipmentsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ function EquipmentsTable({ equipment, project }) {
<td>{new Date(rec.rentalDueDate).toLocaleDateString()}</td>

<td className="materials_cell">
<button
type="button"
onClick={() => handleOpenModal(rec, 'UpdatesEdit')}
aria-label="Edit updates"
<Link
to={`/bmdashboard/tools/${rec._id}/update`}
aria-label="Update equipment status"
style={{ display: 'inline-flex', color: 'inherit', textDecoration: 'none' }}
>
<BiPencil />
</button>
</Link>
<Button
color="primary"
outline
Expand Down
66 changes: 41 additions & 25 deletions src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import styles from './UpdateEquipment.module.css';
import styles1 from '../../BMDashboard.module.css';

const ALLOWED_MIME_TYPES = ['image/png', 'image/jpeg'];

Check warning on line 14 in src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`ALLOWED_MIME_TYPES` should be a `Set`, and use `ALLOWED_MIME_TYPES.has()` to check existence or non-existence.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZzu8mYY9g_lfcjCB-O4&open=AZzu8mYY9g_lfcjCB-O4&pullRequest=4999
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();
Expand Down Expand Up @@ -136,13 +140,14 @@
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;
}

Expand All @@ -163,7 +168,7 @@
);

const handleSubmit = useCallback(
async e => {

Check failure on line 171 in src/components/BMDashboard/Equipment/Update/UpdateEquipment.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZzu8mYY9g_lfcjCB-O5&open=AZzu8mYY9g_lfcjCB-O5&pullRequest=4999
e.preventDefault();

const validationError = validateForm();
Expand All @@ -172,6 +177,18 @@
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('');
Expand All @@ -186,36 +203,35 @@
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,
Expand Down Expand Up @@ -662,7 +678,7 @@
<Label for="file-upload-input" style={formLabelStyle}>
Upload latest picture of this tool or equipment. (optional)
<small className="text-muted ms-2" style={{ color: darkMode ? '#b0b7c0' : '#6c757d' }}>
Accepted: PNG, JPG, JPEG, GIF, WEBP
Accepted: PNG, JPG, JPEG (max 5MB)
</small>
</Label>

Expand Down Expand Up @@ -720,7 +736,7 @@
<i className="fas fa-info-circle me-1" />
{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.'}
</small>
</Alert>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/DragAndDrop/DragAndDrop.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down Expand Up @@ -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)
</p>
<div
className={styles.uploadIndicator}
Expand Down
Loading