From 5822dbbc826845eebbecdecc3384adacfdb6500c Mon Sep 17 00:00:00 2001 From: Vinix Date: Thu, 13 Nov 2025 14:22:42 +0700 Subject: [PATCH] Refactor Add and Edit Brand Device components to include solutions and spareparts forms, enhancing error code management and UI layout --- .../master/brandDevice/AddBrandDevice.jsx | 406 +++++++++++++----- .../master/brandDevice/EditBrandDevice.jsx | 350 +++++++++++---- 2 files changed, 554 insertions(+), 202 deletions(-) diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx index cec0645..5d368ad 100644 --- a/src/pages/master/brandDevice/AddBrandDevice.jsx +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -1,14 +1,19 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Divider, Typography, Button, Steps, Form, Row, Col, Card } from 'antd'; +import { Divider, Typography, Button, Steps, Form, Row, Col, Card, ConfigProvider } from 'antd'; import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { createBrand } from '../../../api/master-brand'; import BrandForm from './component/BrandForm'; import ErrorCodeForm from './component/ErrorCodeForm'; +import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm'; import ErrorCodeTable from './component/ListErrorCode'; +import ErrorCodeListModal from './component/ErrorCodeListModal'; import FormActions from './component/FormActions'; -import { useErrorCodeLogic } from './hooks/errorCode'; +import SparepartForm from './component/SparepartForm'; +import SolutionForm from './component/SolutionForm'; +import { useSolutionLogic } from './hooks/solution'; +import { useSparepartLogic } from './hooks/sparepart'; import { uploadFile, getFolderFromFileType } from '../../../api/file-uploads'; const { Title } = Typography; @@ -28,6 +33,8 @@ const AddBrandDevice = () => { const { setBreadcrumbItems } = useBreadcrumb(); const [brandForm] = Form.useForm(); const [errorCodeForm] = Form.useForm(); + const [solutionForm] = Form.useForm(); + const [sparepartForm] = Form.useForm(); const [confirmLoading, setConfirmLoading] = useState(false); const [currentStep, setCurrentStep] = useState(0); const [fileList, setFileList] = useState([]); @@ -37,21 +44,53 @@ const AddBrandDevice = () => { const [formData, setFormData] = useState(defaultData); const [errorCodes, setErrorCodes] = useState([]); const [errorCodeIcon, setErrorCodeIcon] = useState(null); + const [showErrorCodeModal, setShowErrorCodeModal] = useState(false); + const [sparepartImages, setSparepartImages] = useState({}); const { solutionFields, solutionTypes, solutionStatuses, - firstSolutionValid, solutionsToDelete, + firstSolutionValid, handleAddSolutionField, handleRemoveSolutionField, handleSolutionTypeChange, handleSolutionStatusChange, resetSolutionFields, checkFirstSolutionValid, + getSolutionData, setSolutionsForExistingRecord, - } = useErrorCodeLogic(errorCodeForm, fileList); + } = useSolutionLogic(solutionForm); + + const { + sparepartFields, + sparepartTypes, + sparepartStatuses, + handleAddSparepartField, + handleRemoveSparepartField, + handleSparepartTypeChange, + handleSparepartStatusChange, + resetSparepartFields, + getSparepartData, + setSparepartForExistingRecord, + } = useSparepartLogic(sparepartForm); + + // Handlers for sparepart image upload + const handleSparepartImageUpload = (fieldKey, imageData) => { + setSparepartImages(prev => ({ + ...prev, + [fieldKey]: imageData + })); + }; + + const handleSparepartImageRemove = (fieldKey) => { + setSparepartImages(prev => { + const newImages = { ...prev }; + delete newImages[fieldKey]; + return newImages; + }); + }; useEffect(() => { setBreadcrumbItems([ @@ -96,6 +135,17 @@ const AddBrandDevice = () => { const handleFinish = async () => { setConfirmLoading(true); try { + // Validation: Ensure at least one error code + if (errorCodes.length === 0) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Setidaknya tambahkan 1 error code!', + }); + setConfirmLoading(false); + return; + } + const transformedErrorCodes = errorCodes.map((ec) => ({ error_code: ec.error_code, error_code_name: ec.error_code_name || '', @@ -110,6 +160,13 @@ const AddBrandDevice = () => { path_solution: sol.path_solution || '', is_active: sol.is_active !== false, })), + // Note: Sparepart data is collected but not sent to backend yet + // sparepart: (ec.sparepart || []).map((sp) => ({ + // type: sp.type || 'required', + // name: sp.name || '', + // quantity: sp.quantity || 1, + // is_active: sp.is_active !== false, + // })), })); const finalFormData = { @@ -118,30 +175,11 @@ const AddBrandDevice = () => { brand_model: formData.brand_model || '', brand_manufacture: formData.brand_manufacture, is_active: formData.is_active, - error_code: - transformedErrorCodes.length > 0 - ? transformedErrorCodes - : [ - { - 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: [ - { - solution_name: 'Default Solution', - type_solution: 'text', - text_solution: 'Default solution text', - path_solution: '', - is_active: true, - }, - ], - }, - ], + error_code: transformedErrorCodes, }; + console.log('Final form data:', finalFormData); // Debug log + const response = await createBrand(finalFormData); if (response && (response.statusCode === 200 || response.statusCode === 201)) { @@ -159,6 +197,7 @@ const AddBrandDevice = () => { }); } } catch (error) { + console.error('Finish Error:', error); NotifAlert({ icon: 'error', title: 'Gagal', @@ -183,17 +222,26 @@ const AddBrandDevice = () => { setEditingErrorCodeKey(null); if (record.solution && record.solution.length > 0) { - setSolutionsForExistingRecord(record.solution, errorCodeForm); + setSolutionsForExistingRecord(record.solution, solutionForm); + } + + if (record.sparepart && record.sparepart.length > 0) { + setSparepartForExistingRecord(record.sparepart, sparepartForm); } }; const handleEditErrorCode = (record) => { + // Prevent infinite loop + if (editingErrorCodeKey === record.key) { + return; + } + errorCodeForm.setFieldsValue({ 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, + error_code_color: record.error_code_color || '#000000', + status: record.status !== false, }); setFileList(record.fileList || []); setErrorCodeIcon(record.errorCodeIcon || null); @@ -201,38 +249,102 @@ const AddBrandDevice = () => { setEditingErrorCodeKey(record.key); if (record.solution && record.solution.length > 0) { - setSolutionsForExistingRecord(record.solution, errorCodeForm); + // Reset solution fields first + resetSolutionFields(); + // Then load new solutions + setTimeout(() => { + setSolutionsForExistingRecord(record.solution, solutionForm); + }, 0); + } else { + resetSolutionFields(); + } + + if (record.sparepart && record.sparepart.length > 0) { + // Reset sparepart fields first + resetSparepartFields(); + // Then load new spareparts + setTimeout(() => { + setSparepartForExistingRecord(record.sparepart, sparepartForm); + }, 0); + } else { + resetSparepartFields(); } }; - const handleAddErrorCode = async (newErrorCode) => { - // Include the current icon in the error code - const errorCodeWithIcon = { - ...newErrorCode, - errorCodeIcon: errorCodeIcon - }; + const handleAddErrorCode = async () => { + try { + const formValues = errorCodeForm.getFieldsValue(); - if (editingErrorCodeKey) { - const updatedCodes = errorCodes.map((item) => - item.key === editingErrorCodeKey ? errorCodeWithIcon : item - ); - setErrorCodes(updatedCodes); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil diupdate!', - }); - } else { - const updatedCodes = [...errorCodes, errorCodeWithIcon]; - setErrorCodes(updatedCodes); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil ditambahkan!', + // Validation + if (!formValues.error_code || !formValues.error_code_name) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Error code dan error name wajib diisi!', + }); + return; + } + + // Validate at least 1 solution + const solutions = getSolutionData(); + + if (solutions.length === 0) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Setiap error code harus memiliki minimal 1 solution!', + }); + return; + } + + // Get sparepart data (optional, no backend yet) + const spareparts = getSparepartData() || []; + + const newErrorCode = { + key: Date.now(), + error_code: formValues.error_code, + error_code_name: formValues.error_code_name || '', + error_code_description: formValues.error_code_description || '', + error_code_color: formValues.error_code_color || '#000000', + path_icon: errorCodeIcon?.uploadPath || '', + status: formValues.status !== false, + errorCodeIcon: errorCodeIcon, + solution: solutions, + sparepart: spareparts, + }; + + if (editingErrorCodeKey) { + const updatedCodes = errorCodes.map((item) => + item.key === editingErrorCodeKey ? newErrorCode : item + ); + setErrorCodes(updatedCodes); + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Error code berhasil diupdate!', + }); + } else { + const updatedCodes = [...errorCodes, newErrorCode]; + setErrorCodes(updatedCodes); + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Error code berhasil ditambahkan!', + }); + } + + // Reset all forms + resetErrorCodeForm(); + resetSolutionFields(); + resetSparepartFields(); + } catch (error) { + console.error('Error adding error code:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: 'Gagal menambahkan error code', }); } - - resetErrorCodeForm(); }; const resetErrorCodeForm = () => { @@ -366,62 +478,132 @@ const AddBrandDevice = () => { if (currentStep === 1) { return ( - - - - {isErrorCodeFormReadOnly - ? 'View Error Code' - : editingErrorCodeKey - ? 'Edit Error Code' - : 'Tambah Error Code'} - -
- - - - - - -
+ <> + + {/* Error Code Form Column */} + + +
+ + +
+ + + {/* Solution Form Column */} + + +
+ + +
+ + + {/* Sparepart Form Column */} + + +
+ + +
+ +
+ + {/* Error Codes List Button */} + + + + + + + + + {/* Error Codes List Modal */} + setShowErrorCodeModal(false)} + errorCodes={errorCodes} + loading={loading} + onPreview={handlePreviewErrorCode} + onEdit={handleEditErrorCode} + onDelete={handleDeleteErrorCode} + onAddNew={handleCreateNewErrorCode} + /> + ); } return null; @@ -434,7 +616,7 @@ const AddBrandDevice = () => { - +
{renderStepContent()}
diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index c379b98..b0e392e 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -1,15 +1,19 @@ import { useEffect, useState } from 'react'; import { useNavigate, useParams, useLocation } from 'react-router-dom'; -import { Divider, Typography, Button, Steps, Form, Row, Col, Card, Spin, Modal } from 'antd'; +import { Divider, Typography, Button, Steps, Form, Row, Col, Card, Spin, Modal, ConfigProvider } from 'antd'; 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'; +import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm'; +import SolutionForm from './component/SolutionForm'; +import SparepartForm from './component/SparepartForm'; +import ErrorCodeListModal from './component/ErrorCodeListModal'; import FormActions from './component/FormActions'; import { useErrorCodeLogic } from './hooks/errorCode'; +import { useSolutionLogic } from './hooks/solution'; +import { useSparepartLogic } from './hooks/sparepart'; const { Title } = Typography; const { Step } = Steps; @@ -39,21 +43,59 @@ const EditBrandDevice = () => { const [formData, setFormData] = useState(defaultData); const [errorCodes, setErrorCodes] = useState([]); const [errorCodeIcon, setErrorCodeIcon] = useState(null); + const [showErrorCodeModal, setShowErrorCodeModal] = useState(false); + const [sparepartImages, setSparepartImages] = useState({}); + const [solutionForm] = Form.useForm(); + const [sparepartForm] = Form.useForm(); + + const { + errorCodeFields, + addErrorCode, + removeErrorCode, + editErrorCode, + } = useErrorCodeLogic(errorCodeForm, fileList); const { solutionFields, solutionTypes, solutionStatuses, - firstSolutionValid, - solutionsToDelete, handleAddSolutionField, handleRemoveSolutionField, handleSolutionTypeChange, handleSolutionStatusChange, resetSolutionFields, - checkFirstSolutionValid, + getSolutionData, setSolutionsForExistingRecord, - } = useErrorCodeLogic(errorCodeForm, fileList); + } = useSolutionLogic(solutionForm); + + const { + sparepartFields, + sparepartTypes, + sparepartStatuses, + handleAddSparepartField, + handleRemoveSparepartField, + handleSparepartTypeChange, + handleSparepartStatusChange, + resetSparepartFields, + getSparepartData, + setSparepartForExistingRecord, + } = useSparepartLogic(sparepartForm); + + // Handlers for sparepart image upload + const handleSparepartImageUpload = (fieldKey, imageData) => { + setSparepartImages(prev => ({ + ...prev, + [fieldKey]: imageData + })); + }; + + const handleSparepartImageRemove = (fieldKey) => { + setSparepartImages(prev => { + const newImages = { ...prev }; + delete newImages[fieldKey]; + return newImages; + }); + }; useEffect(() => { const fetchBrandData = async () => { @@ -176,27 +218,65 @@ const EditBrandDevice = () => { const handleFinish = async () => { setConfirmLoading(true); try { + // Get current solution and sparepart data from forms + const currentSolutionData = getSolutionData(); + const currentSparepartData = getSparepartData(); + const finalFormData = { brand_name: formData.brand_name, brand_type: formData.brand_type || '', brand_model: formData.brand_model || '', brand_manufacture: formData.brand_manufacture, is_active: formData.is_active, - error_code: errorCodes.map((ec) => ({ - 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, - type_solution: sol.type_solution, - text_solution: sol.text_solution || '', - path_solution: sol.path_solution || '', - is_active: sol.is_active !== false, - })), - })), + error_code: errorCodes.map((ec) => { + // If editing current error code, get latest data from forms + if (ec.key === editingErrorCodeKey) { + return { + 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: currentSolutionData.map((sol) => ({ + solution_name: sol.solution_name, + type_solution: sol.type_solution, + text_solution: sol.text_solution || '', + path_solution: sol.path_solution || '', + is_active: sol.is_active !== false, + })), + sparepart: currentSparepartData.map((sp) => ({ + name: sp.name, + description: sp.description || '', + is_active: sp.is_active !== false, + sparepart_image: sparepartImages[sp.key || sp.id] || null, + })), + }; + } + + // Return existing data for other error codes + return { + 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, + type_solution: sol.type_solution, + text_solution: sol.text_solution || '', + path_solution: sol.path_solution || '', + is_active: sol.is_active !== false, + })), + sparepart: (ec.sparepart || []).map((sp) => ({ + name: sp.name, + description: sp.description || '', + is_active: sp.is_active !== false, + sparepart_image: sp.sparepart_image || null, + })), + }; + }), }; const response = await updateBrand(id, finalFormData); @@ -256,8 +336,23 @@ const EditBrandDevice = () => { setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(record.key); + // Load solutions to solution form if (record.solution && record.solution.length > 0) { - setSolutionsForExistingRecord(record.solution, errorCodeForm); + setSolutionsForExistingRecord(record.solution, solutionForm); + } + + // Load spareparts to sparepart form + if (record.sparepart && record.sparepart.length > 0) { + setSparepartForExistingRecord(record.sparepart, sparepartForm); + + // Load sparepart images + const newSparepartImages = {}; + record.sparepart.forEach(sparepart => { + if (sparepart.sparepart_image) { + newSparepartImages[sparepart.id || sparepart.key] = sparepart.sparepart_image; + } + }); + setSparepartImages(newSparepartImages); } const formElement = document.querySelector('.ant-form'); @@ -331,6 +426,12 @@ const EditBrandDevice = () => { const handleCreateNewErrorCode = () => { resetErrorCodeForm(); + resetSolutionFields(); + resetSparepartFields(); + setErrorCodeIcon(null); + setSparepartImages({}); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(null); }; const handleErrorCodeIconUpload = (iconData) => { @@ -394,73 +495,142 @@ const EditBrandDevice = () => { if (currentStep === 1) { return ( - - - - {isErrorCodeFormReadOnly - ? editingErrorCodeKey - ? 'View Error Code' - : 'Error Code Form' - : editingErrorCodeKey - ? 'Edit Error Code' - : 'Tambah Error Code'} - -
- - - - - ({ - key: `loading-${index}`, - error_code: 'Loading...', - error_code_name: 'Loading...', - solution: [], - })) - : errorCodes - } - loading={loading} - onPreview={handlePreviewErrorCode} - onEdit={handleEditErrorCode} - onDelete={handleDeleteErrorCode} - onFileView={handleFileView} - /> - -
+ <> + + + + {isErrorCodeFormReadOnly + ? editingErrorCodeKey + ? 'View Error Code' + : 'Error Code Form' + : editingErrorCodeKey + ? 'Edit Error Code' + : 'Tambah Error Code'} + + } + size="small" + > +
+ + +
+ + + Solutions} + size="small" + > +
+ + +
+ + + Spareparts} + size="small" + > +
+ + +
+ +
+ + {/* Error Codes List Button */} + + + + + + + + + {/* Error Codes List Modal */} + setShowErrorCodeModal(false)} + errorCodes={errorCodes} + loading={loading} + onPreview={handlePreviewErrorCode} + onEdit={handleEditErrorCode} + onDelete={handleDeleteErrorCode} + onAddNew={handleCreateNewErrorCode} + /> + ); } return null;