From da14ed4e7464fbdb797efe50a5b220e92ecd1ac8 Mon Sep 17 00:00:00 2001 From: Vinix Date: Tue, 28 Oct 2025 13:07:25 +0700 Subject: [PATCH] add error code icon functionality and color selection in forms --- .../master/brandDevice/AddBrandDevice.jsx | 31 +- .../master/brandDevice/EditBrandDevice.jsx | 43 ++- .../brandDevice/component/ErrorCodeForm.jsx | 126 +++++++- .../brandDevice/component/SolutionField.jsx | 281 ++++++++++++------ .../master/brandDevice/hooks/errorCode.js | 3 + 5 files changed, 379 insertions(+), 105 deletions(-) diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx index ae5451a..cec0645 100644 --- a/src/pages/master/brandDevice/AddBrandDevice.jsx +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -36,6 +36,7 @@ const AddBrandDevice = () => { const [loading, setLoading] = useState(false); const [formData, setFormData] = useState(defaultData); const [errorCodes, setErrorCodes] = useState([]); + const [errorCodeIcon, setErrorCodeIcon] = useState(null); const { solutionFields, @@ -99,6 +100,8 @@ const AddBrandDevice = () => { error_code: ec.error_code, error_code_name: ec.error_code_name || '', error_code_description: ec.error_code_description || '', + error_code_color: ec.error_code_color || '#000000', + path_icon: ec.path_icon || '', is_active: ec.status !== undefined ? ec.status : true, solution: (ec.solution || []).map((sol) => ({ solution_name: sol.solution_name, @@ -123,6 +126,8 @@ const AddBrandDevice = () => { error_code: 'DEFAULT', error_code_name: 'Default Error Code', error_code_description: 'Default error description', + error_code_color: '#000000', + path_icon: '', is_active: true, solution: [ { @@ -169,9 +174,11 @@ const AddBrandDevice = () => { error_code: record.error_code, error_code_name: record.error_code_name, error_code_description: record.error_code_description, + error_code_color: record.error_code_color, status: record.status, }); setFileList(record.fileList || []); + setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(true); setEditingErrorCodeKey(null); @@ -185,9 +192,11 @@ const AddBrandDevice = () => { error_code: record.error_code, error_code_name: record.error_code_name, error_code_description: record.error_code_description, + error_code_color: record.error_code_color, status: record.status, }); setFileList(record.fileList || []); + setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(record.key); @@ -197,9 +206,15 @@ const AddBrandDevice = () => { }; const handleAddErrorCode = async (newErrorCode) => { + // Include the current icon in the error code + const errorCodeWithIcon = { + ...newErrorCode, + errorCodeIcon: errorCodeIcon + }; + if (editingErrorCodeKey) { const updatedCodes = errorCodes.map((item) => - item.key === editingErrorCodeKey ? newErrorCode : item + item.key === editingErrorCodeKey ? errorCodeWithIcon : item ); setErrorCodes(updatedCodes); NotifOk({ @@ -208,7 +223,7 @@ const AddBrandDevice = () => { message: 'Error code berhasil diupdate!', }); } else { - const updatedCodes = [...errorCodes, newErrorCode]; + const updatedCodes = [...errorCodes, errorCodeWithIcon]; setErrorCodes(updatedCodes); NotifOk({ icon: 'success', @@ -228,6 +243,7 @@ const AddBrandDevice = () => { solution_type_0: 'text', }); setFileList([]); + setErrorCodeIcon(null); resetSolutionFields(); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(null); @@ -326,6 +342,14 @@ const AddBrandDevice = () => { setFileList(newFileList); }; + const handleErrorCodeIconUpload = (iconData) => { + setErrorCodeIcon(iconData); + }; + + const handleErrorCodeIconRemove = () => { + setErrorCodeIcon(null); + }; + const renderStepContent = () => { if (currentStep === 0) { return ( @@ -381,6 +405,9 @@ const AddBrandDevice = () => { onCreateNewErrorCode={handleCreateNewErrorCode} onResetForm={resetErrorCodeForm} errorCodes={errorCodes} + errorCodeIcon={errorCodeIcon} + onErrorCodeIconUpload={handleErrorCodeIconUpload} + onErrorCodeIconRemove={handleErrorCodeIconRemove} /> diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index 7cf4e38..c379b98 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -4,6 +4,7 @@ import { Divider, Typography, Button, Steps, Form, Row, Col, Card, Spin, Modal } import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { getBrandById, updateBrand } from '../../../api/master-brand'; +import { getFileUrl } from '../../../api/file-uploads'; import BrandForm from './component/BrandForm'; import ErrorCodeForm from './component/ErrorCodeForm'; import ErrorCodeTable from './component/ListErrorCode'; @@ -37,6 +38,7 @@ const EditBrandDevice = () => { const [loading, setLoading] = useState(true); const [formData, setFormData] = useState(defaultData); const [errorCodes, setErrorCodes] = useState([]); + const [errorCodeIcon, setErrorCodeIcon] = useState(null); const { solutionFields, @@ -111,8 +113,21 @@ const EditBrandDevice = () => { error_code: ec.error_code, error_code_name: ec.error_code_name || '', error_code_description: ec.error_code_description || '', + error_code_color: ec.error_code_color || '#000000', + path_icon: ec.path_icon || '', status: ec.is_active, solution: ec.solution || [], + errorCodeIcon: ec.path_icon ? { + name: 'icon', + uploadPath: ec.path_icon, + url: (() => { + const pathParts = ec.path_icon.split('/'); + const folder = pathParts[0]; + const filename = pathParts.slice(1).join('/'); + return getFileUrl(folder, filename); + })(), + type_solution: 'image' + } : null, })) : []; @@ -171,6 +186,8 @@ const EditBrandDevice = () => { error_code: ec.error_code, error_code_name: ec.error_code_name || '', error_code_description: ec.error_code_description || '', + error_code_color: ec.error_code_color || '#000000', + path_icon: ec.errorCodeIcon?.uploadPath || ec.path_icon || '', is_active: ec.status !== undefined ? ec.status : true, solution: (ec.solution || []).map((sol) => ({ solution_name: sol.solution_name, @@ -215,8 +232,10 @@ const EditBrandDevice = () => { error_code: record.error_code, error_code_name: record.error_code_name, error_code_description: record.error_code_description, + error_code_color: record.error_code_color, status: record.status, }); + setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(true); setEditingErrorCodeKey(record.key); @@ -230,8 +249,10 @@ const EditBrandDevice = () => { error_code: record.error_code, error_code_name: record.error_code_name, error_code_description: record.error_code_description, + error_code_color: record.error_code_color, status: record.status, }); + setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(record.key); @@ -246,10 +267,16 @@ const EditBrandDevice = () => { }; const handleAddErrorCode = (newErrorCode) => { + // Include the current icon in the error code + const errorCodeWithIcon = { + ...newErrorCode, + errorCodeIcon: errorCodeIcon + }; + let updatedErrorCodes; if (editingErrorCodeKey) { updatedErrorCodes = errorCodes.map((item) => - item.key === editingErrorCodeKey ? newErrorCode : item + item.key === editingErrorCodeKey ? errorCodeWithIcon : item ); NotifOk({ icon: 'success', @@ -257,7 +284,7 @@ const EditBrandDevice = () => { message: 'Error code berhasil diupdate!', }); } else { - updatedErrorCodes = [...errorCodes, newErrorCode]; + updatedErrorCodes = [...errorCodes, errorCodeWithIcon]; NotifOk({ icon: 'success', title: 'Berhasil', @@ -277,6 +304,7 @@ const EditBrandDevice = () => { solution_type_0: 'text', }); setFileList([]); + setErrorCodeIcon(null); resetSolutionFields(); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(null); @@ -305,6 +333,14 @@ const EditBrandDevice = () => { resetErrorCodeForm(); }; + const handleErrorCodeIconUpload = (iconData) => { + setErrorCodeIcon(iconData); + }; + + const handleErrorCodeIconRemove = () => { + setErrorCodeIcon(null); + }; + const handleFileView = (pathSolution, fileType) => { localStorage.setItem(`brand_device_edit_${id}_last_phase`, currentStep.toString()); @@ -399,6 +435,9 @@ const EditBrandDevice = () => { onCreateNewErrorCode={handleCreateNewErrorCode} onResetForm={resetErrorCodeForm} errorCodes={errorCodes} + errorCodeIcon={errorCodeIcon} + onErrorCodeIconUpload={handleErrorCodeIconUpload} + onErrorCodeIconRemove={handleErrorCodeIconRemove} /> diff --git a/src/pages/master/brandDevice/component/ErrorCodeForm.jsx b/src/pages/master/brandDevice/component/ErrorCodeForm.jsx index c86bf7a..7490fe0 100644 --- a/src/pages/master/brandDevice/component/ErrorCodeForm.jsx +++ b/src/pages/master/brandDevice/component/ErrorCodeForm.jsx @@ -1,7 +1,8 @@ -import { Form, Divider, Button, Switch, Input, ConfigProvider, Typography } from 'antd'; -import { PlusOutlined } from '@ant-design/icons'; +import { Form, Divider, Button, Switch, Input, ConfigProvider, Typography, Upload, message } from 'antd'; +import { PlusOutlined, UploadOutlined, DeleteOutlined } from '@ant-design/icons'; import { NotifAlert } from '../../../../components/Global/ToastNotif'; import SolutionField from './SolutionField'; +import { uploadFile, getFileUrl } from '../../../../api/file-uploads'; const { Text } = Typography; @@ -24,10 +25,67 @@ const ErrorCodeForm = ({ onFileView, onCreateNewErrorCode, onResetForm, - errorCodes + errorCodes, + errorCodeIcon, + onErrorCodeIconUpload, + onErrorCodeIconRemove }) => { 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) { + // Extract folder and filename from the path + const pathParts = iconPath.split('/'); + const folder = pathParts[0]; + const filename = pathParts.slice(1).join('/'); + + onErrorCodeIconUpload({ + name: file.name, + uploadPath: iconPath, + url: getFileUrl(folder, filename), // Use the same endpoint as file uploads + type_solution: fileType, + solutionId: 'icon' + }); + message.success(`${file.name} uploaded successfully!`); + } else { + message.error('Failed to upload icon'); + } + } catch (error) { + console.error('Error uploading icon:', error); + message.error('Failed to upload icon'); + } + + return false; // Prevent default upload behavior + }; + + const handleRemoveIcon = () => { + onErrorCodeIconRemove(); + message.success('Icon removed'); + }; + const handleAddErrorCode = async () => { try { const values = await errorCodeForm.validateFields(); @@ -92,6 +150,8 @@ const ErrorCodeForm = ({ error_code: values.error_code, error_code_name: values.error_code_name, error_code_description: values.error_code_description, + error_code_color: values.error_code_color || '#000000', + path_icon: errorCodeIcon?.uploadPath || '', status: values.status === undefined ? true : values.status, solution: solutions, key: editingErrorCodeKey || `temp-${Date.now()}` @@ -161,6 +221,66 @@ const ErrorCodeForm = ({ + + + + + +
+ {errorCodeIcon ? ( + <> + Error Code Icon +
+
+ {errorCodeIcon.name} +
+ {!isErrorCodeFormReadOnly && ( + + )} +
+ + ) : ( + + + + )} +
+
+ Upload an icon image (max 2MB, JPG/PNG/GIF) +
+
+ diff --git a/src/pages/master/brandDevice/component/SolutionField.jsx b/src/pages/master/brandDevice/component/SolutionField.jsx index c0692e9..7df25d7 100644 --- a/src/pages/master/brandDevice/component/SolutionField.jsx +++ b/src/pages/master/brandDevice/component/SolutionField.jsx @@ -18,36 +18,53 @@ const SolutionField = ({ onFileUpload, currentSolutionData, onFileView, - errorCodeForm + errorCodeForm, }) => { useEffect(() => { if (currentSolutionData && errorCodeForm) { if (currentSolutionData.solution_name) { - errorCodeForm.setFieldValue(`solution_name_${fieldId}`, currentSolutionData.solution_name); + errorCodeForm.setFieldValue( + `solution_name_${fieldId}`, + currentSolutionData.solution_name + ); } if (currentSolutionData.type_solution === 'text' && currentSolutionData.text_solution) { - errorCodeForm.setFieldValue(`text_solution_${fieldId}`, currentSolutionData.text_solution); + errorCodeForm.setFieldValue( + `text_solution_${fieldId}`, + currentSolutionData.text_solution + ); } if (currentSolutionData.type_solution) { - const formValue = currentSolutionData.type_solution === 'image' || currentSolutionData.type_solution === 'pdf' ? 'file' : currentSolutionData.type_solution; + const formValue = + currentSolutionData.type_solution === 'image' || + currentSolutionData.type_solution === 'pdf' + ? 'file' + : currentSolutionData.type_solution; errorCodeForm.setFieldValue(`solution_type_${fieldId}`, formValue); } - if (currentSolutionData.is_active !== undefined) { - errorCodeForm.setFieldValue(`solution_status_${fieldId}`, currentSolutionData.is_active); + // Only set status if it's not already set to prevent overwriting user changes + const currentStatus = errorCodeForm.getFieldValue(`solution_status_${fieldId}`); + if (currentSolutionData.is_active !== undefined && currentStatus === undefined) { + errorCodeForm.setFieldValue( + `solution_status_${fieldId}`, + currentSolutionData.is_active + ); } } }, [currentSolutionData, fieldId, errorCodeForm]); const handleBeforeUpload = async (file) => { - const isAllowedType = ['application/pdf', 'image/jpeg', 'image/png', 'image/gif'].includes(file.type); + 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.` + message: `${file.name} bukan file PDF atau gambar yang diizinkan.`, }); return Upload.LIST_IGNORE; } @@ -72,13 +89,13 @@ const SolutionField = ({ NotifOk({ icon: 'success', title: 'Berhasil', - message: `${file.name} berhasil diupload!` + message: `${file.name} berhasil diupload!`, }); } else { NotifAlert({ icon: 'error', title: 'Gagal', - message: `Gagal mengupload ${file.name}` + message: `Gagal mengupload ${file.name}`, }); } } catch (error) { @@ -86,7 +103,7 @@ const SolutionField = ({ NotifAlert({ icon: 'error', title: 'Error', - message: `Gagal mengupload ${file.name}. Silakan coba lagi.` + message: `Gagal mengupload ${file.name}. Silakan coba lagi.`, }); } @@ -101,10 +118,17 @@ const SolutionField = ({ padding: 16, border: '1px solid #d9d9d9', borderRadius: 8, - transition: 'all 0.3s ease' + transition: 'all 0.3s ease', }} > -
+
Solution {index + 1} -
- ); - } - return null; - })()} + + - )} + ) : ( + <> + {/* Show existing file info for both preview and edit mode */} + {currentSolutionData && + currentSolutionData.type_solution !== 'text' && + currentSolutionData.path_solution && ( + + {(() => { + const solution = currentSolutionData; + const fileName = + solution.file_upload_name || + solution.path_solution?.split('/')[1] || + 'File'; + const fileType = solution.type_solution; - - file.solutionId === fieldId), - // Add existing file to fileList if it exists - ...(currentSolutionData && currentSolutionData.type_solution !== 'text' && currentSolutionData.path_solution ? [{ - uid: `existing-${fieldId}`, - name: currentSolutionData.file_upload_name || currentSolutionData.path_solution?.split('/')[1] || 'File', - status: 'done', - url: null, // We'll use the path_solution for viewing - solutionId: fieldId, - type_solution: currentSolutionData.type_solution, - uploadPath: currentSolutionData.path_solution, - existingFile: true - }] : []) - ]} - onRemove={(file) => { - }} - beforeUpload={handleBeforeUpload} - > - - - - - ); + if (fileType !== 'text' && solution.path_solution) { + return ( +
+ + {fileType === 'image' + ? '[Image]' + : '[Document]'}{' '} + {fileName} + + +
+ ); + } + return null; + })()} +
+ )} + + + file.solutionId === fieldId), + // Add existing file to fileList if it exists + ...(currentSolutionData && + currentSolutionData.type_solution !== 'text' && + currentSolutionData.path_solution + ? [ + { + uid: `existing-${fieldId}`, + name: + currentSolutionData.file_upload_name || + currentSolutionData.path_solution?.split( + '/' + )[1] || + 'File', + status: 'done', + url: null, // We'll use the path_solution for viewing + solutionId: fieldId, + type_solution: + currentSolutionData.type_solution, + uploadPath: currentSolutionData.path_solution, + existingFile: true, + }, + ] + : []), + ]} + onRemove={(file) => {}} + beforeUpload={handleBeforeUpload} + > + + + + + ); }}
); }; -export default SolutionField; \ No newline at end of file +export default SolutionField; diff --git a/src/pages/master/brandDevice/hooks/errorCode.js b/src/pages/master/brandDevice/hooks/errorCode.js index 99ce5ec..f1d3e9f 100644 --- a/src/pages/master/brandDevice/hooks/errorCode.js +++ b/src/pages/master/brandDevice/hooks/errorCode.js @@ -194,6 +194,9 @@ export const useErrorCodeLogic = (errorCodeForm, fileList) => { }; const handleSolutionStatusChange = (fieldId, status) => { + // Update form immediately + errorCodeForm.setFieldValue(`solution_status_${fieldId}`, status); + // Then update local state setSolutionStatuses(prev => ({ ...prev, [fieldId]: status