From de8f0ba2b60ef2696be9a9e9b875f52b1780b4f9 Mon Sep 17 00:00:00 2001 From: Vinix Date: Thu, 13 Nov 2025 14:22:23 +0700 Subject: [PATCH] Add forms and hooks for managing error codes and spare parts --- .../component/ErrorCodeSimpleForm.jsx | 233 ++++++++++++++++ .../brandDevice/component/SolutionField.jsx | 7 +- .../component/SolutionFieldNew.jsx | 243 ++++++++++++++++ .../brandDevice/component/SolutionForm.jsx | 80 ++++++ .../brandDevice/component/SparepartForm.jsx | 259 ++++++++++++++++++ .../master/brandDevice/hooks/solution.js | 166 +++++++++++ .../master/brandDevice/hooks/sparepart.js | 115 ++++++++ 7 files changed, 1101 insertions(+), 2 deletions(-) create mode 100644 src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx create mode 100644 src/pages/master/brandDevice/component/SolutionFieldNew.jsx create mode 100644 src/pages/master/brandDevice/component/SolutionForm.jsx create mode 100644 src/pages/master/brandDevice/component/SparepartForm.jsx create mode 100644 src/pages/master/brandDevice/hooks/solution.js create mode 100644 src/pages/master/brandDevice/hooks/sparepart.js diff --git a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx new file mode 100644 index 0000000..791c59b --- /dev/null +++ b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx @@ -0,0 +1,233 @@ +import { + Form, + Input, + Switch, + Upload, + Button, + Typography, + message, + ConfigProvider, +} from 'antd'; +import { UploadOutlined } from '@ant-design/icons'; +import { uploadFile } from '../../../../api/file-uploads'; + +const { Text } = Typography; + +const ErrorCodeSimpleForm = ({ + errorCodeForm, + isErrorCodeFormReadOnly = false, + errorCodeIcon, + onErrorCodeIconUpload, + onErrorCodeIconRemove, + onAddErrorCode, +}) => { + const statusValue = Form.useWatch('status', errorCodeForm); + + const handleIconUpload = async (file) => { + // Check if file is an image + const isImage = file.type.startsWith('image/'); + if (!isImage) { + message.error('You can only upload image files!'); + return Upload.LIST_IGNORE; + } + + // Check file size (max 2MB) + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('Image must be smaller than 2MB!'); + return Upload.LIST_IGNORE; + } + + try { + const fileExtension = file.name.split('.').pop().toLowerCase(); + const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes( + fileExtension + ); + const fileType = isImageFile ? 'image' : 'pdf'; + const folder = 'images'; + + const uploadResponse = await uploadFile(file, folder); + const iconPath = uploadResponse.data?.path_icon || uploadResponse.data?.path_solution || ''; + + if (iconPath) { + onErrorCodeIconUpload({ + name: file.name, + uploadPath: iconPath, + fileExtension, + isImage: isImageFile, + size: file.size, + }); + message.success(`${file.name} uploaded successfully!`); + } else { + message.error(`Failed to upload ${file.name}`); + } + } catch (error) { + console.error('Error uploading icon:', error); + message.error(`Failed to upload ${file.name}`); + } + }; + + const handleIconRemove = () => { + onErrorCodeIconRemove(); + }; + + return ( + <> + {/* Status Switch */} + +
+ + + + + {statusValue ? 'Active' : 'Inactive'} + +
+
+ + {/* Error Code */} + + + + + {/* Error Name */} + + + + + {/* Error Description */} + + + + + {/* Color and Icon in same row */} + + + + + + + + {!isErrorCodeFormReadOnly ? ( + + + + ) : ( +
+ No upload allowed +
+ )} +
+
+ + {errorCodeIcon && ( +
+
+ Error Code Icon +
+ {errorCodeIcon.name} +
+ + Size: {(errorCodeIcon.size / 1024).toFixed(1)} KB + +
+ {!isErrorCodeFormReadOnly && ( + + )} +
+
+ )} +
+ + {/* Add Error Code Button */} + {!isErrorCodeFormReadOnly && ( + + + + + + )} + + ); +}; + +export default ErrorCodeSimpleForm; \ No newline at end of file diff --git a/src/pages/master/brandDevice/component/SolutionField.jsx b/src/pages/master/brandDevice/component/SolutionField.jsx index dca30ec..6ec931e 100644 --- a/src/pages/master/brandDevice/component/SolutionField.jsx +++ b/src/pages/master/brandDevice/component/SolutionField.jsx @@ -139,7 +139,10 @@ const SolutionField = ({ icon={} onClick={() => onRemove(fieldId)} disabled={isReadOnly} - style={{ borderColor: '#ff4d4f' }} + style={{ + borderColor: '#ff4d4f', + color: '#ff4d4f' + }} /> @@ -161,7 +164,7 @@ const SolutionField = ({ /> - {(watchedStatus ?? true) ? 'Active' : 'Non Active'} + {(watchedStatus ?? true) ? 'Active' : 'Inactive'} diff --git a/src/pages/master/brandDevice/component/SolutionFieldNew.jsx b/src/pages/master/brandDevice/component/SolutionFieldNew.jsx new file mode 100644 index 0000000..792db80 --- /dev/null +++ b/src/pages/master/brandDevice/component/SolutionFieldNew.jsx @@ -0,0 +1,243 @@ +import React, { useState, useEffect } from 'react'; +import { Form, Input, Button, Switch, Radio, Upload, Typography, Space } from 'antd'; +import { DeleteOutlined, UploadOutlined, EyeOutlined } from '@ant-design/icons'; +import { uploadFile, getFolderFromFileType } from '../../../../api/file-uploads'; +import { NotifAlert } from '../../../../components/Global/ToastNotif'; + +const { Text } = Typography; +const { TextArea } = Input; + +const SolutionFieldNew = ({ + fieldKey, + fieldName, + index, + solutionType, + solutionStatus, + isReadOnly = false, + canRemove = true, + onTypeChange, + onStatusChange, + onRemove, + onFileUpload, + onFileView, + fileList = [] +}) => { + const [currentStatus, setCurrentStatus] = useState(solutionStatus ?? true); + + // Watch form values + const getFieldValue = () => { + try { + const form = document.querySelector(`[data-field="${fieldName}"]`)?.form; + if (form) { + const formData = new FormData(form); + return formData.get(`${fieldName}.status`) === 'on'; + } + return currentStatus; + } catch { + return currentStatus; + } + }; + + useEffect(() => { + setCurrentStatus(solutionStatus ?? true); + }, [solutionStatus]); + const handleFileUpload = async (file) => { + try { + const isAllowedType = [ + 'application/pdf', + 'image/jpeg', + 'image/png', + 'image/gif', + ].includes(file.type); + + if (!isAllowedType) { + NotifAlert({ + icon: 'error', + title: 'Error', + message: `${file.name} bukan file PDF atau gambar yang diizinkan.`, + }); + return; + } + + const fileExtension = file.name.split('.').pop().toLowerCase(); + const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExtension); + const fileType = isImage ? 'image' : 'pdf'; + const folder = getFolderFromFileType(fileType); + + const uploadResponse = await uploadFile(file, folder); + const actualPath = uploadResponse.data?.path_solution || ''; + + if (actualPath) { + // Store the file info with the solution field + file.uploadPath = actualPath; + file.solutionId = fieldKey; + file.type_solution = fileType; + onFileUpload(file); + NotifAlert({ + icon: 'success', + title: 'Berhasil', + message: `${file.name} berhasil diupload!`, + }); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: `Gagal mengupload ${file.name}`, + }); + } + } catch (error) { + console.error('Error uploading file:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: `Gagal mengupload ${file.name}. Silakan coba lagi.`, + }); + } + }; + + const renderSolutionContent = () => { + if (solutionType === 'text') { + return ( + +