From 1c2ddca9d47b395cca17eeec1296edcede87a22a Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Mon, 1 Dec 2025 10:51:11 +0700 Subject: [PATCH 01/42] fix: update isReadFilter --- src/pages/notification/component/ListNotification.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx index 30e3623..2e5ef42 100644 --- a/src/pages/notification/component/ListNotification.jsx +++ b/src/pages/notification/component/ListNotification.jsx @@ -202,7 +202,7 @@ const ListNotification = memo(function ListNotification(props) { })); // Fetch notifications with new pagination - const isReadFilter = activeTab === 'read' ? true : activeTab === 'unread' ? false : null; + const isReadFilter = activeTab === 'read' ? 1 : activeTab === 'unread' ? 0 : null; fetchNotifications(page, pageSize, isReadFilter); }; @@ -214,7 +214,7 @@ const ListNotification = memo(function ListNotification(props) { } // Fetch notifications on component mount and when tab changes - const isReadFilter = activeTab === 'read' ? true : activeTab === 'unread' ? false : null; + const isReadFilter = activeTab === 'read' ? 1 : activeTab === 'unread' ? 0 : null; fetchNotifications(pagination.current_page, pagination.current_limit, isReadFilter); }, [activeTab]); From 179705852697c2a3f5f04767cf46dce429bd1bd7 Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Tue, 2 Dec 2025 11:10:36 +0700 Subject: [PATCH 02/42] repair: brandDevice sparepart integration --- .../master/brandDevice/AddBrandDevice.jsx | 533 ++++++--------- .../master/brandDevice/EditBrandDevice.jsx | 617 ++++++------------ .../master/brandDevice/ViewBrandDevice.jsx | 1 - src/pages/master/brandDevice/ViewFilePage.jsx | 26 +- .../brandDevice/component/BrandForm.jsx | 172 +++-- .../component/ErrorCodeListModal.jsx | 200 ------ .../component/ErrorCodeSimpleForm.jsx | 16 +- .../brandDevice/component/ListBrandDevice.jsx | 1 - .../component/SingleSparepartSelect.jsx | 549 ++++++++++++++++ .../brandDevice/component/SolutionField.jsx | 136 ++-- .../brandDevice/component/SolutionForm.jsx | 70 +- .../component/SparepartCardSelect.jsx | 56 +- .../master/brandDevice/hooks/solution.js | 7 +- .../master/brandDevice/hooks/sparepart.js | 141 ---- .../brandDevice/hooks/useBrandDeviceLogic.jsx | 298 +++++++++ 15 files changed, 1544 insertions(+), 1279 deletions(-) delete mode 100644 src/pages/master/brandDevice/component/ErrorCodeListModal.jsx create mode 100644 src/pages/master/brandDevice/component/SingleSparepartSelect.jsx delete mode 100644 src/pages/master/brandDevice/hooks/sparepart.js create mode 100644 src/pages/master/brandDevice/hooks/useBrandDeviceLogic.jsx diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx index 0f91e57..00603cf 100644 --- a/src/pages/master/brandDevice/AddBrandDevice.jsx +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -9,7 +9,7 @@ import { Row, Col, Card, - ConfigProvider, + Spin, Table, Tag, Space, @@ -19,44 +19,44 @@ import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { createBrand } from '../../../api/master-brand'; import BrandForm from './component/BrandForm'; import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm'; -import ErrorCodeListModal from './component/ErrorCodeListModal'; -import FormActions from './component/FormActions'; import SolutionForm from './component/SolutionForm'; -import SparepartForm from './component/SparepartForm'; +import FormActions from './component/FormActions'; +import ListErrorCode from './component/ListErrorCode'; +import { useErrorCodeLogic } from './hooks/errorCode'; import { useSolutionLogic } from './hooks/solution'; -import { useSparepartLogic } from './hooks/sparepart'; -import { uploadFile, getFolderFromFileType } from '../../../api/file-uploads'; -import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'; +import { EditOutlined, DeleteOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons'; +import { useBrandDeviceLogic } from './hooks/useBrandDeviceLogic'; const { Title } = Typography; const { Step } = Steps; -const defaultData = { - brand_name: '', - brand_type: '', - brand_model: '', - brand_manufacture: '', - is_active: true, - brand_code: '', -}; - const AddBrandDevice = () => { const navigate = useNavigate(); 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([]); - const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false); - const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null); const [loading, setLoading] = useState(false); - const [formData, setFormData] = useState(defaultData); + const [formData, setFormData] = useState({ + brand_name: '', + brand_type: '', + brand_model: '', + brand_manufacture: '', + is_active: true, + }); const [errorCodes, setErrorCodes] = useState([]); + const [pendingErrorCodes, setPendingErrorCodes] = useState([]); const [errorCodeIcon, setErrorCodeIcon] = useState(null); + const [solutionForm] = Form.useForm(); const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); + const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null); + const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false); + + const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic( + errorCodeForm, + [] + ); const { solutionFields, @@ -64,47 +64,21 @@ const AddBrandDevice = () => { solutionStatuses, solutionsToDelete, firstSolutionValid, + checkFirstSolutionValid, handleAddSolutionField, handleRemoveSolutionField, handleSolutionTypeChange, handleSolutionStatusChange, resetSolutionFields, - checkFirstSolutionValid, getSolutionData, setSolutionsForExistingRecord, } = useSolutionLogic(solutionForm); - // For spareparts, we'll use the local state directly since it's just an array of IDs - const handleSparepartChange = (values) => { - setSelectedSparepartIds(values || []); - }; - - const resetSparepartFields = () => { - setSelectedSparepartIds([]); - }; - - const getSparepartData = () => { - return selectedSparepartIds; - }; - - const setSparepartsForExistingRecord = (sparepartData) => { - if (!sparepartData) { - setSelectedSparepartIds([]); - return; - } - - if (Array.isArray(sparepartData)) { - setSelectedSparepartIds(sparepartData); - } else if (typeof sparepartData === 'object' && sparepartData.spareparts) { - setSelectedSparepartIds(sparepartData.spareparts || []); - } else { - setSelectedSparepartIds(sparepartData.map(sp => sp.sparepart_id || sp.brand_sparepart_id || sp.id).filter(id => id)); - } - }; - useEffect(() => { setBreadcrumbItems([ - { title: • Master }, + { + title: • Master + }, { title: ( { const handleNextStep = async () => { try { - await brandForm.validateFields(); + const currentFormData = await brandForm.validateFields(); + + setFormData({ + brand_name: currentFormData.brand_name, + brand_type: currentFormData.brand_type || '', + brand_model: currentFormData.brand_model || '', + brand_manufacture: currentFormData.brand_manufacture || '', + is_active: currentFormData.is_active, + }); + setCurrentStep(1); } catch (error) { NotifAlert({ @@ -145,47 +128,33 @@ 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) => ({ + const transformedErrorCodes = pendingErrorCodes.length > 0 ? pendingErrorCodes.map(ec => ({ error_code: ec.error_code, - error_code_name: ec.error_code_name || '', + error_code_name: ec.error_code_name, error_code_description: ec.error_code_description || '', - error_code_color: ec.error_code_color || '#000000', + error_code_color: ec.error_code_color || '#ad4141ff', path_icon: ec.path_icon || '', is_active: ec.status !== undefined ? ec.status : true, - solution: (ec.solution || []).map((sol) => ({ + 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, - })), - })); + is_active: sol.is_active + })) + })) : []; - const sparepartData = getSparepartData(); - const finalFormData = { + const brandData = { brand_name: formData.brand_name, brand_type: formData.brand_type || '', brand_model: formData.brand_model || '', - brand_manufacture: formData.brand_manufacture, + brand_manufacture: formData.brand_manufacture || '', is_active: formData.is_active, - spareparts: sparepartData, + spareparts: selectedSparepartIds, error_code: transformedErrorCodes, }; - console.log('Final form data:', finalFormData); // Debug log - - const response = await createBrand(finalFormData); + const response = await createBrand(brandData); if (response && (response.statusCode === 200 || response.statusCode === 201)) { NotifOk({ @@ -202,7 +171,6 @@ const AddBrandDevice = () => { }); } } catch (error) { - console.error('Finish Error:', error); NotifAlert({ icon: 'error', title: 'Gagal', @@ -221,60 +189,46 @@ const AddBrandDevice = () => { error_code_color: record.error_code_color, status: record.status, }); - setFileList(record.fileList || []); setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(true); - setEditingErrorCodeKey(null); + setEditingErrorCodeKey(record.key); if (record.solution && record.solution.length > 0) { setSolutionsForExistingRecord(record.solution, solutionForm); - } - - if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart); + } else { + resetSolutionFields(); } }; 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 || '#000000', - status: record.status !== false, + error_code_color: record.error_code_color, + status: record.status, }); - setFileList(record.fileList || []); setErrorCodeIcon(record.errorCodeIcon || null); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(record.key); if (record.solution && record.solution.length > 0) { - // Reset solution fields first - resetSolutionFields(); - // Then load new solutions - setTimeout(() => { - setSolutionsForExistingRecord(record.solution, solutionForm); - }, 0); - } else { - resetSolutionFields(); + setSolutionsForExistingRecord(record.solution, solutionForm); } - if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart); + const formElement = document.querySelector('.ant-form'); + if (formElement) { + formElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }; const handleAddErrorCode = async () => { try { - const formValues = errorCodeForm.getFieldsValue(); + const errorCodeValues = await errorCodeForm.validateFields(); + const solutionData = getSolutionData(); - // Validation - if (!formValues.error_code || !formValues.error_code_name) { + // Validate error code fields + if (!errorCodeValues.error_code || !errorCodeValues.error_code_name) { NotifAlert({ icon: 'warning', title: 'Perhatian', @@ -283,10 +237,8 @@ const AddBrandDevice = () => { return; } - // Validate at least 1 solution - const solutions = getSolutionData(); - - if (solutions.length === 0) { + // Validate solution data + if (!solutionData || solutionData.length === 0) { NotifAlert({ icon: 'warning', title: 'Perhatian', @@ -295,31 +247,48 @@ const AddBrandDevice = () => { return; } + // Validate each solution has name + const invalidSolution = solutionData.find(sol => !sol.solution_name || sol.solution_name.trim() === ''); + if (invalidSolution) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Setiap solution harus memiliki nama!', + }); + return; + } + 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', + error_code: errorCodeValues.error_code, + error_code_name: errorCodeValues.error_code_name, + error_code_description: errorCodeValues.error_code_description, + error_code_color: errorCodeValues.error_code_color || '#000000', path_icon: errorCodeIcon?.uploadPath || '', - status: formValues.status !== false, + is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status, + solution: solutionData, errorCodeIcon: errorCodeIcon, - solution: solutions, + key: editingErrorCodeKey || `temp-${Date.now()}`, }; + let updatedPendingErrorCodes; if (editingErrorCodeKey) { - const updatedCodes = errorCodes.map((item) => - item.key === editingErrorCodeKey ? newErrorCode : item - ); - setErrorCodes(updatedCodes); + updatedPendingErrorCodes = pendingErrorCodes.map((item) => { + if (item.key === editingErrorCodeKey) { + return { + ...item, + ...newErrorCode, + error_code_id: item.error_code_id || newErrorCode.error_code_id, + }; + } + return item; + }); NotifOk({ icon: 'success', title: 'Berhasil', message: 'Error code berhasil diupdate!', }); } else { - const updatedCodes = [...errorCodes, newErrorCode]; - setErrorCodes(updatedCodes); + updatedPendingErrorCodes = [...pendingErrorCodes, newErrorCode]; NotifOk({ icon: 'success', title: 'Berhasil', @@ -327,15 +296,17 @@ const AddBrandDevice = () => { }); } - // Reset all forms - resetErrorCodeForm(); - resetSolutionFields(); + setPendingErrorCodes(updatedPendingErrorCodes); + setErrorCodes(updatedPendingErrorCodes); + + setTimeout(() => { + resetErrorCodeForm(); + }, 100); } catch (error) { - console.error('Error adding error code:', error); NotifAlert({ - icon: 'error', - title: 'Error', - message: 'Gagal menambahkan error code', + icon: 'warning', + title: 'Perhatian', + message: 'Harap isi semua kolom wajib (error code + minimal 1 solution)!', }); } }; @@ -344,22 +315,14 @@ const AddBrandDevice = () => { errorCodeForm.resetFields(); errorCodeForm.setFieldsValue({ status: true, - solution_status_0: true, - solution_type_0: 'text', }); - setFileList([]); setErrorCodeIcon(null); resetSolutionFields(); - resetSparepartFields(); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(null); }; - const handleCreateNewErrorCode = () => { - resetErrorCodeForm(); - }; - - const handleDeleteErrorCode = (key) => { + const handleDeleteErrorCode = async (key) => { if (errorCodes.length <= 1) { NotifAlert({ icon: 'warning', @@ -369,7 +332,8 @@ const AddBrandDevice = () => { return; } - setErrorCodes(errorCodes.filter((item) => item.key !== key)); + const updatedErrorCodes = errorCodes.filter((item) => item.key !== key); + setErrorCodes(updatedErrorCodes); NotifOk({ icon: 'success', title: 'Berhasil', @@ -377,75 +341,12 @@ const AddBrandDevice = () => { }); }; - const handleFileView = (pathSolution, fileType) => { - const filePath = pathSolution || ''; - if (!filePath) return; - - const parts = filePath.split('/'); - if (parts.length < 2) return; - - const [folder, filename] = parts; - const encodedFileName = encodeURIComponent(filename); - const navigationPath = `/master/brand-device/view/temp/files/${folder}/${encodedFileName}`; - navigate(navigationPath); - }; - - const handleSolutionFileUpload = 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) { - file.uploadPath = actualPath; - file.solution_name = file.name; - file.solutionId = solutionFields[0]; - file.type_solution = fileType; - setFileList((prevList) => [...prevList, file]); - NotifOk({ - 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 handleFileRemove = (file) => { - const newFileList = fileList.filter((item) => item.uid !== file.uid); - setFileList(newFileList); + const handleCreateNewErrorCode = () => { + resetErrorCodeForm(); + resetSolutionFields(); + setErrorCodeIcon(null); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(null); }; const handleErrorCodeIconUpload = (iconData) => { @@ -466,6 +367,9 @@ const AddBrandDevice = () => { setFormData((prev) => ({ ...prev, ...allValues })) } isEdit={false} + selectedSparepartIds={selectedSparepartIds} + onSparepartChange={setSelectedSparepartIds} + showSparepartSection={true} /> ); } @@ -473,10 +377,22 @@ const AddBrandDevice = () => { if (currentStep === 1) { return ( <> - - {/* Error Code Form Column */} - - + + + + {isErrorCodeFormReadOnly + ? editingErrorCodeKey + ? 'View Error Code' + : 'Error Code Form' + : editingErrorCodeKey + ? 'Edit Error Code' + : 'Error Code'} + + } + size="small" + >
{
- - {/* Solution Form Column */} - - + + + Solutions + + } + size="small" + >
{ solutionFields={solutionFields} solutionTypes={solutionTypes} solutionStatuses={solutionStatuses} - fileList={fileList} - solutionsToDelete={solutionsToDelete} firstSolutionValid={firstSolutionValid} onAddSolutionField={handleAddSolutionField} onRemoveSolutionField={handleRemoveSolutionField} onSolutionTypeChange={handleSolutionTypeChange} onSolutionStatusChange={handleSolutionStatusChange} - onSolutionFileUpload={handleSolutionFileUpload} - onFileView={handleFileView} + checkFirstSolutionValid={checkFirstSolutionValid} isReadOnly={isErrorCodeFormReadOnly} />
- - -
- - -
- - - {/* Error Codes Table Column */} - - - { - const solutionCount = record.solution - ? record.solution.length - : 0; - return ( - 0 ? 'green' : 'red'} - > - {solutionCount} Sol - - ); - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - width: '20%', - align: 'center', - render: (_, { status }) => ( - - {status ? 'Active' : 'Inactive'} - - ), - }, - { - title: 'Action', - key: 'action', - align: 'center', - width: '15%', - render: (_, record) => ( - - + + Error Codes ({errorCodes.length}) + + } + size="small" + > + +
+ +
@@ -657,7 +492,37 @@ const AddBrandDevice = () => { -
{renderStepContent()}
+
+ {loading && ( +
+ +
+ )} +
+ {renderStepContent()} +
+
{ ); }; -export default AddBrandDevice; +export default AddBrandDevice; \ No newline at end of file diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index b54d818..b69325f 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { Divider, Typography, @@ -10,11 +10,6 @@ import { Col, Card, Spin, - Modal, - ConfigProvider, - Table, - Tag, - Space, } from 'antd'; import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; @@ -23,55 +18,59 @@ import { getFileUrl } from '../../../api/file-uploads'; import BrandForm from './component/BrandForm'; 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 ListErrorCode from './component/ListErrorCode'; import { useSolutionLogic } from './hooks/solution'; -import { useSparepartLogic } from './hooks/sparepart'; -import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'; +import { useBrandDeviceLogic } from './hooks/useBrandDeviceLogic'; const { Title } = Typography; const { Step } = Steps; -const defaultData = { - brand_name: '', - brand_type: '', - brand_model: '', - brand_manufacture: '', - is_active: true, - brand_code: '', -}; - const EditBrandDevice = () => { const navigate = useNavigate(); const { id } = useParams(); - const location = useLocation(); const { setBreadcrumbItems } = useBreadcrumb(); const [brandForm] = Form.useForm(); const [errorCodeForm] = Form.useForm(); - const [confirmLoading, setConfirmLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(0); - const [fileList, setFileList] = useState([]); - const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false); - const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null); - const [loading, setLoading] = useState(true); - const [formData, setFormData] = useState(defaultData); - const [errorCodes, setErrorCodes] = useState([]); - const [errorCodeIcon, setErrorCodeIcon] = useState(null); const [solutionForm] = Form.useForm(); - const [sparepartForm] = Form.useForm(); + const [errorCodeIcon, setErrorCodeIcon] = useState(null); const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); + const [formData, setFormData] = useState({ + brand_name: '', + brand_type: '', + brand_model: '', + brand_manufacture: '', + is_active: true, + }); - const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic( - errorCodeForm, - fileList - ); + // Custom hooks for edit mode + const { + confirmLoading, + setConfirmLoading, + currentStep, + setCurrentStep, + loading, + setLoading, + errorCodes, + setErrorCodes, + pendingErrorCodes, + setPendingErrorCodes, + editingErrorCodeKey, + setEditingErrorCodeKey, + isErrorCodeFormReadOnly, + setIsErrorCodeFormReadOnly, + handleAddErrorCode, + handleDeleteErrorCode, + } = useBrandDeviceLogic(true, id); + const { solutionFields, solutionTypes, solutionStatuses, + solutionsToDelete, + firstSolutionValid, + checkFirstSolutionValid, handleAddSolutionField, handleRemoveSolutionField, handleSolutionTypeChange, @@ -81,34 +80,6 @@ const EditBrandDevice = () => { setSolutionsForExistingRecord, } = useSolutionLogic(solutionForm); - // For spareparts, we'll use the local state directly since it's just an array of IDs - const handleSparepartChange = (values) => { - setSelectedSparepartIds(values || []); - }; - - const resetSparepartFields = () => { - setSelectedSparepartIds([]); - }; - - const getSparepartData = () => { - return selectedSparepartIds; - }; - - const setSparepartsForExistingRecord = (sparepartData) => { - if (!sparepartData) { - setSelectedSparepartIds([]); - return; - } - - if (Array.isArray(sparepartData)) { - setSelectedSparepartIds(sparepartData); - } else if (typeof sparepartData === 'object' && sparepartData.spareparts) { - setSelectedSparepartIds(sparepartData.spareparts || []); - } else { - setSelectedSparepartIds(sparepartData.map(sp => sp.sparepart_id || sp.brand_sparepart_id || sp.id).filter(id => id)); - } - }; - useEffect(() => { const fetchBrandData = async () => { const token = localStorage.getItem('token'); @@ -117,15 +88,10 @@ const EditBrandDevice = () => { return; } - const savedPhase = - location.state?.phase || localStorage.getItem(`brand_device_edit_${id}_last_phase`); - if (savedPhase) { - setCurrentStep(parseInt(savedPhase)); - localStorage.removeItem(`brand_device_edit_${id}_last_phase`); - } - setBreadcrumbItems([ - { title: • Master }, + { + title: • Master + }, { title: ( { const brandData = response.data; const newFormData = { brand_name: brandData.brand_name, - brand_type: brandData.brand_type, - brand_model: brandData.brand_model, - brand_manufacture: brandData.brand_manufacture, + brand_type: brandData.brand_type || '', + brand_model: brandData.brand_model || '', + brand_manufacture: brandData.brand_manufacture || '', is_active: brandData.is_active, - brand_code: brandData.brand_code, }; const existingErrorCodes = brandData.error_code - ? brandData.error_code.map((ec, index) => ({ + ? brandData.error_code.map((ec) => ({ key: `existing-${ec.error_code_id}`, error_code_id: ec.error_code_id, error_code: ec.error_code, @@ -171,10 +136,9 @@ const EditBrandDevice = () => { path_icon: ec.path_icon || '', status: ec.is_active, solution: ec.solution || [], - sparepart: ec.sparepart || [], errorCodeIcon: ec.path_icon ? { - name: ec.path_icon.split('/').pop(), // Ambil nama file dari path + name: ec.path_icon.split('/').pop(), uploadPath: ec.path_icon, url: (() => { const pathParts = ec.path_icon.split('/'); @@ -191,13 +155,13 @@ const EditBrandDevice = () => { setFormData(newFormData); brandForm.setFieldsValue(newFormData); setErrorCodes(existingErrorCodes); + setPendingErrorCodes(existingErrorCodes); - // Set the selected sparepart IDs if available in the response - if (response.data.spareparts) { - // Extract the IDs from the spareparts objects - const sparepartIds = response.data.spareparts.map(sp => sp.sparepart_id); + if (brandData.spareparts && brandData.spareparts.length > 0) { + const sparepartIds = brandData.spareparts.map(sp => sp.sparepart_id); setSelectedSparepartIds(sparepartIds); - setSparepartsForExistingRecord(sparepartIds); + } else { + setSelectedSparepartIds([]); } } else { NotifAlert({ @@ -218,16 +182,20 @@ const EditBrandDevice = () => { }; fetchBrandData(); - }, [id, setBreadcrumbItems, navigate, brandForm, location]); - - const handleCancel = () => { - localStorage.removeItem(`brand_device_edit_${id}_temp_data`); - navigate('/master/brand-device'); - }; + }, [id, setBreadcrumbItems, navigate, brandForm]); const handleNextStep = async () => { try { - await brandForm.validateFields(); + const currentFormData = await brandForm.validateFields(); + + setFormData({ + brand_name: currentFormData.brand_name, + brand_type: currentFormData.brand_type || '', + brand_model: currentFormData.brand_model || '', + brand_manufacture: currentFormData.brand_manufacture || '', + is_active: currentFormData.is_active, + }); + setCurrentStep(1); } catch (error) { NotifAlert({ @@ -238,67 +206,53 @@ const EditBrandDevice = () => { } }; + const handleCancel = () => { + navigate('/master/brand-device'); + }; + const handleFinish = async () => { + const currentFormData = formData; + + if (!currentFormData.brand_name || currentFormData.brand_name.trim() === '' || + !currentFormData.brand_manufacture || currentFormData.brand_manufacture.trim() === '') { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Harap lengkapi semua field wajib diisi (Brand Name dan Manufacturer)!', + }); + return; + } + setConfirmLoading(true); try { - // Get current solution data from forms - const currentSolutionData = getSolutionData(); - - 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) => { - // 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, - })), - }; - } - - // 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, - })), - }; - }), + const brandUpdateData = { + brand_name: currentFormData.brand_name, + brand_type: currentFormData.brand_type || '', + brand_model: currentFormData.brand_model || '', + brand_manufacture: currentFormData.brand_manufacture || '', + is_active: currentFormData.is_active, + spareparts: selectedSparepartIds, + error_code: pendingErrorCodes.length > 0 ? pendingErrorCodes.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.path_icon || '', + is_active: ec.status !== undefined ? ec.status : true, + what_action_to_take: ec.what_action_to_take || '', + 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 + })) + })) : [] }; - const sparepartData = getSparepartData(); - const updatedFinalFormData = { - ...finalFormData, - spareparts: sparepartData, - }; - - const response = await updateBrand(id, updatedFinalFormData); + const response = await updateBrand(id, brandUpdateData); if (response && (response.statusCode === 200 || response.statusCode === 201)) { - localStorage.removeItem(`brand_device_edit_${id}_temp_data`); NotifOk({ icon: 'success', title: 'Berhasil', @@ -323,70 +277,50 @@ const EditBrandDevice = () => { } }; - const handlePreviewErrorCode = (record) => { - 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, - }); - setErrorCodeIcon(record.errorCodeIcon || null); - setIsErrorCodeFormReadOnly(true); - setEditingErrorCodeKey(record.key); - - // Load solutions to solution form - if (record.solution && record.solution.length > 0) { - setSolutionsForExistingRecord(record.solution, solutionForm); - } else { - resetSolutionFields(); - } - - // Load spareparts to sparepart form - if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart); - } else { - resetSparepartFields(); - } + const handleErrorCodeIconUpload = (iconData) => { + setErrorCodeIcon(iconData); }; - const handleEditErrorCode = (record) => { + const handleErrorCodeIconRemove = () => { + setErrorCodeIcon(null); + }; + + const resetErrorCodeForm = () => { + errorCodeForm.resetFields(); 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, + status: true, }); - setErrorCodeIcon(record.errorCodeIcon || null); + setErrorCodeIcon(null); + resetSolutionFields(); setIsErrorCodeFormReadOnly(false); - setEditingErrorCodeKey(record.key); - - // Load solutions to solution form - if (record.solution && record.solution.length > 0) { - setSolutionsForExistingRecord(record.solution, solutionForm); - } - - // Load spareparts to sparepart form - if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart); - } - - const formElement = document.querySelector('.ant-form'); - if (formElement) { - formElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } + setEditingErrorCodeKey(null); }; - const handleAddErrorCode = async () => { - try { - // Validate error code form - const errorCodeValues = await errorCodeForm.validateFields(); + const handleCreateNewErrorCode = () => { + resetErrorCodeForm(); + resetSolutionFields(); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(null); + }; - // Get solution data from solution form + // Local wrapper for handleAddErrorCode from useBrandDeviceLogic + const handleAddErrorCodeLocal = async () => { + try { + const errorCodeValues = await errorCodeForm.validateFields(); const solutionData = getSolutionData(); - if (solutionData.length === 0) { + // Validate error code fields + if (!errorCodeValues.error_code || !errorCodeValues.error_code_name) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Error code dan error name wajib diisi!', + }); + return; + } + + // Validate solution data + if (!solutionData || solutionData.length === 0) { NotifAlert({ icon: 'warning', title: 'Perhatian', @@ -395,27 +329,32 @@ const EditBrandDevice = () => { return; } - // Get sparepart data from sparepart form - const sparepartData = getSparepartData(); + // Validate each solution has name + const invalidSolution = solutionData.find(sol => !sol.solution_name || sol.solution_name.trim() === ''); + if (invalidSolution) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Setiap solution harus memiliki nama!', + }); + return; + } - // Create complete error code object const newErrorCode = { error_code: errorCodeValues.error_code, error_code_name: errorCodeValues.error_code_name, error_code_description: errorCodeValues.error_code_description, error_code_color: errorCodeValues.error_code_color || '#000000', path_icon: errorCodeIcon?.uploadPath || '', - status: errorCodeValues.status === undefined ? true : errorCodeValues.status, + is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status, solution: solutionData, - ...(sparepartData && sparepartData.length > 0 && { sparepart: sparepartData }), errorCodeIcon: errorCodeIcon, key: editingErrorCodeKey || `temp-${Date.now()}`, }; - let updatedErrorCodes; + let updatedPendingErrorCodes; if (editingErrorCodeKey) { - // Update existing error code - updatedErrorCodes = errorCodes.map((item) => { + updatedPendingErrorCodes = pendingErrorCodes.map((item) => { if (item.key === editingErrorCodeKey) { return { ...item, @@ -431,8 +370,7 @@ const EditBrandDevice = () => { message: 'Error code berhasil diupdate!', }); } else { - // Add new error code - updatedErrorCodes = [...errorCodes, newErrorCode]; + updatedPendingErrorCodes = [...pendingErrorCodes, newErrorCode]; NotifOk({ icon: 'success', title: 'Berhasil', @@ -440,9 +378,9 @@ const EditBrandDevice = () => { }); } - setErrorCodes(updatedErrorCodes); + setPendingErrorCodes(updatedPendingErrorCodes); + setErrorCodes(updatedPendingErrorCodes); - // Delay form reset to prevent data loss setTimeout(() => { resetErrorCodeForm(); }, 100); @@ -455,92 +393,45 @@ const EditBrandDevice = () => { } }; - const resetErrorCodeForm = () => { - errorCodeForm.resetFields(); + const handlePreviewErrorCode = (record) => { errorCodeForm.setFieldsValue({ - status: true, - solution_status_0: true, - solution_type_0: 'text', + 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([]); - setErrorCodeIcon(null); - resetSolutionFields(); - resetSparepartFields(); - setIsErrorCodeFormReadOnly(false); - setEditingErrorCodeKey(null); + setErrorCodeIcon(record.errorCodeIcon || null); + setIsErrorCodeFormReadOnly(true); + setEditingErrorCodeKey(record.key); + + if (record.solution && record.solution.length > 0) { + setSolutionsForExistingRecord(record.solution, solutionForm); + } else { + resetSolutionFields(); + } }; - const handleDeleteErrorCode = async (key) => { - if (errorCodes.length <= 1) { - NotifAlert({ - icon: 'warning', - title: 'Perhatian', - message: 'Setiap brand harus memiliki minimal 1 error code!', - }); - return; + const handleEditErrorCode = (record) => { + 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, + }); + setErrorCodeIcon(record.errorCodeIcon || null); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(record.key); + + if (record.solution && record.solution.length > 0) { + setSolutionsForExistingRecord(record.solution, solutionForm); } - const updatedErrorCodes = errorCodes.filter((item) => item.key !== key); - setErrorCodes(updatedErrorCodes); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil dihapus!', - }); - }; - - const handleCreateNewErrorCode = () => { - resetErrorCodeForm(); - resetSolutionFields(); - resetSparepartFields(); - setErrorCodeIcon(null); - setIsErrorCodeFormReadOnly(false); - setEditingErrorCodeKey(null); - }; - - const handleErrorCodeIconUpload = (iconData) => { - setErrorCodeIcon(iconData); - }; - - const handleErrorCodeIconRemove = () => { - setErrorCodeIcon(null); - }; - - const handleFileView = (pathSolution, fileType) => { - localStorage.setItem(`brand_device_edit_${id}_last_phase`, currentStep.toString()); - - const tempData = { - errorCodes: errorCodes, - fileList: fileList, - solutionFields: solutionFields, - solutionTypes: solutionTypes, - solutionStatuses: solutionStatuses, - editingErrorCodeKey: editingErrorCodeKey, - isErrorCodeFormReadOnly: isErrorCodeFormReadOnly, - solutionsToDelete: Array.from(solutionsToDelete), - currentSolutionData: window.currentSolutionData || {}, - }; - localStorage.setItem(`brand_device_edit_${id}_temp_data`, JSON.stringify(tempData)); - - const filePath = pathSolution || ''; - if (!filePath) return; - - const parts = filePath.split('/'); - if (parts.length < 2) return; - - const [folder, filename] = parts; - const encodedFileName = encodeURIComponent(filename); - const navigationPath = `/master/brand-device/edit/${id}/files/${folder}/${encodedFileName}`; - navigate(navigationPath); - }; - - const handleSolutionFileUpload = (file) => { - setFileList((prevList) => [...prevList, file]); - }; - - const handleFileRemove = (file) => { - const newFileList = fileList.filter((item) => item.uid !== file.uid); - setFileList(newFileList); + const formElement = document.querySelector('.ant-form'); + if (formElement) { + formElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } }; const renderStepContent = () => { @@ -553,6 +444,9 @@ const EditBrandDevice = () => { setFormData((prev) => ({ ...prev, ...allValues })) } isEdit={true} + selectedSparepartIds={selectedSparepartIds} + onSparepartChange={setSelectedSparepartIds} + showSparepartSection={true} /> ); } @@ -561,7 +455,7 @@ const EditBrandDevice = () => { return ( <> -
+ @@ -589,12 +483,12 @@ const EditBrandDevice = () => { errorCodeIcon={errorCodeIcon} onErrorCodeIconUpload={handleErrorCodeIconUpload} onErrorCodeIconRemove={handleErrorCodeIconRemove} - onAddErrorCode={handleAddErrorCode} + onAddErrorCode={handleAddErrorCodeLocal} /> - + @@ -610,140 +504,53 @@ const EditBrandDevice = () => { solution_status_0: true, solution_type_0: 'text', }} + onValuesChange={checkFirstSolutionValid} > - + - Spareparts + Error Codes ({errorCodes.length}) } size="small" > -
- - -
- - - -
{ - const solutionCount = record.solution - ? record.solution.length - : 0; - return ( - 0 ? 'green' : 'red'} - > - {solutionCount} Sol - - ); - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - width: '20%', - align: 'center', - render: (_, { status }) => ( - - {status ? 'Active' : 'Inactive'} - - ), - }, - { - title: 'Action', - key: 'action', - align: 'center', - width: '15%', - render: (_, record) => ( - - + @@ -807,4 +614,4 @@ const EditBrandDevice = () => { ); }; -export default EditBrandDevice; +export default EditBrandDevice; \ No newline at end of file diff --git a/src/pages/master/brandDevice/ViewBrandDevice.jsx b/src/pages/master/brandDevice/ViewBrandDevice.jsx index 9e5096d..f0a7940 100644 --- a/src/pages/master/brandDevice/ViewBrandDevice.jsx +++ b/src/pages/master/brandDevice/ViewBrandDevice.jsx @@ -52,7 +52,6 @@ const ViewBrandDevice = () => { }); } } catch (error) { - console.error('Fetch Brand Device Error:', error); NotifAlert({ icon: 'error', title: 'Error', diff --git a/src/pages/master/brandDevice/ViewFilePage.jsx b/src/pages/master/brandDevice/ViewFilePage.jsx index 4476450..3d16f1b 100644 --- a/src/pages/master/brandDevice/ViewFilePage.jsx +++ b/src/pages/master/brandDevice/ViewFilePage.jsx @@ -26,17 +26,7 @@ const ViewFilePage = () => { const [pdfBlobUrl, setPdfBlobUrl] = useState(null); const [pdfLoading, setPdfLoading] = useState(false); - // Debug: Log URL parameters and location const isFromEdit = window.location.pathname.includes('/edit/'); - console.log('ViewFilePage URL Parameters:', { - id, - fileType, - fileName, - allParams: params, - windowLocation: window.location.pathname, - urlParts: window.location.pathname.split('/'), - isFromEdit - }); let fallbackId = id; let fallbackFileType = fileType; @@ -45,7 +35,6 @@ const ViewFilePage = () => { if (!fileName || !fileType || !id) { const urlParts = window.location.pathname.split('/'); - // console.log('URL Parts from pathname:', urlParts); const viewIndex = urlParts.indexOf('view'); const editIndex = urlParts.indexOf('edit'); @@ -55,13 +44,6 @@ const ViewFilePage = () => { fallbackId = urlParts[actionIndex + 1]; fallbackFileType = urlParts[actionIndex + 3]; fallbackFileName = decodeURIComponent(urlParts[actionIndex + 4]); - - console.log('Fallback extraction:', { - fallbackId, - fallbackFileType, - fallbackFileName, - actionType: viewIndex !== -1 ? 'view' : 'edit' - }); } } @@ -95,12 +77,9 @@ const ViewFilePage = () => { const folder = getFolderFromFileType('pdf'); try { const blobData = await getFile(folder, decodedFileName); - console.log('PDF blob data received:', blobData); const blobUrl = window.URL.createObjectURL(blobData); setPdfBlobUrl(blobUrl); - console.log('PDF blob URL created successfully:', blobUrl); } catch (pdfError) { - console.error('Error loading PDF:', pdfError); setError('Failed to load PDF file: ' + (pdfError.message || pdfError)); setPdfBlobUrl(null); } finally { @@ -110,7 +89,6 @@ const ViewFilePage = () => { setLoading(false); } catch (error) { - console.error('Error fetching data:', error); setError('Failed to load data'); setLoading(false); } @@ -160,7 +138,7 @@ const ViewFilePage = () => { const targetPhase = savedPhase ? parseInt(savedPhase) : 1; - console.log('ViewFilePage handleBack - Edit mode:', { + console.log({ savedPhase, targetPhase, id: fallbackId || id @@ -345,12 +323,10 @@ const ViewFilePage = () => { const folder = getFolderFromFileType('pdf'); getFile(folder, actualFileName) .then(blobData => { - console.log('Retry PDF blob data:', blobData); const blobUrl = window.URL.createObjectURL(blobData); setPdfBlobUrl(blobUrl); }) .catch(error => { - console.error('Error retrying PDF load:', error); setError('Failed to load PDF file: ' + (error.message || error)); setPdfBlobUrl(null); }) diff --git a/src/pages/master/brandDevice/component/BrandForm.jsx b/src/pages/master/brandDevice/component/BrandForm.jsx index 2c7227e..129cc50 100644 --- a/src/pages/master/brandDevice/component/BrandForm.jsx +++ b/src/pages/master/brandDevice/component/BrandForm.jsx @@ -1,76 +1,122 @@ -import React from 'react'; -import { Form, Input, Row, Col, Typography, Switch } from 'antd'; +import React, { useState } from 'react'; +import { Form, Input, Row, Col, Typography, Switch, Button, Card, Divider } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import SingleSparepartSelect from './SingleSparepartSelect'; const { Text } = Typography; -const BrandForm = ({ form, formData, onValuesChange, isEdit = false }) => { +const BrandForm = ({ + form, + formData, + onValuesChange, + isEdit = false, + selectedSparepartIds = [], + onSparepartChange, + showSparepartSection = false +}) => { const isActive = Form.useWatch('is_active', form) ?? formData.is_active ?? true; + const [showSparepart, setShowSparepart] = useState(showSparepartSection); return ( -
- -
- - - - - {isActive ? 'Running' : 'Offline'} - -
-
+
+ + +
+ + + + + {isActive ? 'Running' : 'Offline'} + +
+
- - + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Add Sparepart Button */} +
+ +
- -
- - - - - - - - - - - - - - - - - - - - - - - - + {/* Sparepart Selection Section */} + {showSparepart && ( + + + + )} + ); }; diff --git a/src/pages/master/brandDevice/component/ErrorCodeListModal.jsx b/src/pages/master/brandDevice/component/ErrorCodeListModal.jsx deleted file mode 100644 index 89a74b4..0000000 --- a/src/pages/master/brandDevice/component/ErrorCodeListModal.jsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { useState } from 'react'; -import { Modal, Table, Button, Space, message, Tag, ConfigProvider } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'; -import { NotifConfirmDialog, NotifOk, NotifAlert } from '../../../../components/Global/ToastNotif'; - -const ErrorCodeListModal = ({ - visible, - onClose, - errorCodes, - loading, - onPreview, - onEdit, - onDelete, - onAddNew, -}) => { - const [confirmLoading, setConfirmLoading] = useState(false); - - const columns = [ - { - title: 'No', - key: 'no', - width: '5%', - align: 'center', - render: (_, __, index) => index + 1, - }, - { - title: 'Error Code', - dataIndex: 'error_code', - key: 'error_code', - width: '15%', - }, - { - title: 'Error Name', - dataIndex: 'error_code_name', - key: 'error_code_name', - width: '30%', - render: (text) => text || '-', - }, - { - title: 'Description', - dataIndex: 'error_code_description', - key: 'error_code_description', - width: '25%', - render: (text) => text || '-', - ellipsis: true, - }, - { - title: 'Solutions', - key: 'solutions', - width: '10%', - align: 'center', - render: (_, record) => { - const solutionCount = record.solution ? record.solution.length : 0; - return 0 ? 'green' : 'red'}>{solutionCount} Sol; - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - width: '10%', - align: 'center', - render: (_, { status }) => ( - {status ? 'Active' : 'Inactive'} - ), - }, - { - title: 'Action', - key: 'action', - align: 'center', - width: '15%', - render: (_, record) => ( - - - - - } - open={visible} - onCancel={onClose} - closable={false} - maskClosable={false} - width={1200} - footer={[ - , - ]} - > -
`${range[0]}-${range[1]} of ${total} items`, - }} - scroll={{ x: 1000 }} - size="small" - /> - - ); -}; - -export default ErrorCodeListModal; diff --git a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx index 79a0661..398296a 100644 --- a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx +++ b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx @@ -54,7 +54,6 @@ const ErrorCodeSimpleForm = ({ message.error(`Failed to upload ${file.name}`); } } catch (error) { - console.error('Error uploading icon:', error); message.error(`Failed to upload ${file.name}`); } }; @@ -122,7 +121,7 @@ const ErrorCodeSimpleForm = ({ /> - + {!isErrorCodeFormReadOnly ? ( - diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx index 884da82..4c80945 100644 --- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -181,7 +181,6 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { }); } } catch (error) { - console.error('Delete Brand Device Error:', error); NotifAlert({ icon: 'error', title: 'Error', diff --git a/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx b/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx new file mode 100644 index 0000000..42037b1 --- /dev/null +++ b/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx @@ -0,0 +1,549 @@ +import React, { useState, useEffect } from 'react'; +import { Select, Card, Typography, Tag, Spin, Empty, Button, Image, Row, Col, Modal } from 'antd'; +import { PlusOutlined, DeleteOutlined, CheckOutlined, EyeOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import { getAllSparepart } from '../../../../api/sparepart'; +import dayjs from 'dayjs'; + +const { Text, Title } = Typography; +const { Option } = Select; + +const SingleSparepartSelect = ({ + selectedSparepartIds = [], + onSparepartChange, + isReadOnly = false +}) => { + const [spareparts, setSpareparts] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedSpareparts, setSelectedSpareparts] = useState([]); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [previewModal, setPreviewModal] = useState({ visible: false, sparepart: null }); + + useEffect(() => { + fetchSpareparts(); + }, []); + + useEffect(() => { + if (selectedSparepartIds && selectedSparepartIds.length > 0) { + const fullSelectedSpareparts = spareparts.filter(sp => + selectedSparepartIds.includes(sp.sparepart_id) + ); + setSelectedSpareparts(fullSelectedSpareparts); + } else { + setSelectedSpareparts([]); + } + }, [selectedSparepartIds, spareparts]); + + const fetchSpareparts = async () => { + setLoading(true); + try { + const params = new URLSearchParams(); + params.set('limit', '1000'); + + const response = await getAllSparepart(params); + if (response && (response.statusCode === 200 || response.data)) { + const sparepartData = response.data?.data || response.data || []; + setSpareparts(sparepartData); + } else { + setSpareparts([ + { + sparepart_id: 1, + sparepart_name: 'Compressor Oil Filter', + sparepart_description: 'Oil filter for compressor', + sparepart_foto: null, + sparepart_code: 'SP-001', + sparepart_merk: 'Brand A', + sparepart_model: 'Model X', + is_active: true, + stock_quantity: 50 + } + ]); + } + } catch (error) { + setSpareparts([ + { + sparepart_id: 1, + sparepart_name: 'Compressor Oil Filter', + sparepart_description: 'Oil filter for compressor', + sparepart_foto: null, + sparepart_code: 'SP-001', + sparepart_merk: 'Brand A', + sparepart_model: 'Model X', + is_active: true, + stock_quantity: 50 + } + ]); + } finally { + setLoading(false); + } + }; + + const handleSparepartSelect = (sparepartId) => { + const selectedSparepart = spareparts.find(sp => sp.sparepart_id === sparepartId); + + if (selectedSparepart) { + const isAlreadySelected = selectedSpareparts.some(sp => sp.sparepart_id === sparepartId); + + if (!isAlreadySelected) { + const newSelectedSpareparts = [...selectedSpareparts, selectedSparepart]; + setSelectedSpareparts(newSelectedSpareparts); + + const newSelectedIds = newSelectedSpareparts.map(sp => sp.sparepart_id); + onSparepartChange(newSelectedIds); + } + } + setDropdownOpen(false); + }; + + const handleRemoveSparepart = (sparepartId) => { + const newSelectedSpareparts = selectedSpareparts.filter(sp => sp.sparepart_id !== sparepartId); + setSelectedSpareparts(newSelectedSpareparts); + + const newSelectedIds = newSelectedSpareparts.map(sp => sp.sparepart_id); + onSparepartChange(newSelectedIds); + }; + + const handlePreviewSparepart = (sparepart) => { + setPreviewModal({ visible: true, sparepart }); + }; + + const handlePreviewModalClose = () => { + setPreviewModal({ visible: false, sparepart: null }); + }; + + const renderSparepartCard = (sparepart, isSelected = false) => { + const isAlreadySelected = selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id); + + let imgSrc; + if (sparepart.sparepart_foto) { + if (sparepart.sparepart_foto.startsWith('http')) { + imgSrc = sparepart.sparepart_foto; + } else { + const fileName = sparepart.sparepart_foto.split('/').pop(); + if (fileName === 'defaultSparepartImg.jpg') { + imgSrc = `/assets/defaultSparepartImg.jpg`; + } else { + const token = localStorage.getItem('token'); + const baseURL = import.meta.env.VITE_API_SERVER || ''; + imgSrc = `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`; + } + } + } else { + imgSrc = 'https://via.placeholder.com/150'; + } + + return ( + + handleSparepartSelect(sparepart.sparepart_id) : undefined} + actions={[ + // Preview action (selalu available) + { + e.stopPropagation(); + handlePreviewSparepart(sparepart); + }} + />, + // Delete action (hanya untuk selected items) + isSelected && !isReadOnly && ( + { + e.stopPropagation(); + handleRemoveSparepart(sparepart.sparepart_id); + }} + /> + ), + ].filter(Boolean)} + > + + +
+ {sparepart.sparepart_item_type && ( + + {sparepart.sparepart_item_type} + + )} +
+
+ {sparepart.sparepart_name { + e.target.src = 'https://via.placeholder.com/150'; + }} + /> +
+ {isAlreadySelected && ( +
+ +
+ )} +
+
+ + +
+ {/* Title dengan proper hierarchy */} + + {sparepart.sparepart_name || sparepart.name || 'Unnamed'} + + + {/* Stock Information */} + + Available Stock: {sparepart.sparepart_stock || '0'} + + +
+ + {/* Code */} +
+ + {sparepart.sparepart_code || 'No code'} + +
+ + {/* Brand/Model/Unit Information */} + {(sparepart.sparepart_merk || sparepart.sparepart_model || sparepart.sparepart_unit) && ( +
+ {sparepart.sparepart_merk && ( +
Brand: {sparepart.sparepart_merk}
+ )} + {sparepart.sparepart_model && ( +
Model: {sparepart.sparepart_model}
+ )} + {sparepart.sparepart_unit && ( +
Unit: {sparepart.sparepart_unit}
+ )} +
+ )} + + {/* Last Updated */} + {sparepart.updated_at && ( + + Last updated: {dayjs(sparepart.updated_at).format('DD MMM YYYY')} + + )} +
+ + + + + ); + }; + + return ( + <> +
+ {!isReadOnly && ( +
+ +
+ )} + +
+ {selectedSpareparts.length > 0 ? ( +
+ + Selected Spareparts ({selectedSpareparts.length}) + + + {selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))} + +
+ ) : ( + + )} +
+
+ + {/* Preview Modal */} + + Close + + ]} + width={800} + centered + bodyStyle={{ padding: '24px' }} + > + {previewModal.sparepart && ( + +
+
+
+ {previewModal.sparepart.sparepart_foto ? ( + {previewModal.sparepart.sparepart_name { + e.target.src = 'https://via.placeholder.com/200x200/d9d9d9/666666?text=No+Image'; + }} + /> + ) : ( +
+ No Image +
+ )} +
+ + {previewModal.sparepart.sparepart_item_type && ( + + {previewModal.sparepart.sparepart_item_type} + + )} +
+ + + +
+ + {previewModal.sparepart.sparepart_name || 'Unnamed'} + + +
+ + Code: + + + {previewModal.sparepart.sparepart_code || 'N/A'} + +
+ + {previewModal.sparepart.sparepart_description && ( +
+ + Description: + + + {previewModal.sparepart.sparepart_description} + +
+ )} + +
+ + Status: + + + {previewModal.sparepart.is_active ? 'Active' : 'Inactive'} + +
+ +
+ + Stock: + + + {previewModal.sparepart.sparepart_stock || '0'} + {previewModal.sparepart_unit ? ` ${previewModal.sparepart.unit}` : ' units'} + +
+ + + {previewModal.sparepart.sparepart_merk && ( +
+
+ + Brand: + + + {previewModal.sparepart.sparepart_merk} + +
+ + )} + {previewModal.sparepart.sparepart_model && ( + +
+ + Model: + + + {previewModal.sparepart.sparepart_model} + +
+ + )} + {previewModal.sparepart.sparepart_unit && ( + +
+ + Unit: + + + {previewModal.sparepart.sparepart_unit} + +
+ + )} + + + {previewModal.sparepart.updated_at && ( +
+ + Last updated: {dayjs(previewModal.sparepart.updated_at).format('DD MMMM YYYY, HH:mm')} + +
+ )} + + + + )} + + + ); +}; + +export default SingleSparepartSelect; \ 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 792db80..13f241e 100644 --- a/src/pages/master/brandDevice/component/SolutionField.jsx +++ b/src/pages/master/brandDevice/component/SolutionField.jsx @@ -22,25 +22,6 @@ const SolutionFieldNew = ({ 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 = [ @@ -86,7 +67,6 @@ const SolutionFieldNew = ({ }); } } catch (error) { - console.error('Error uploading file:', error); NotifAlert({ icon: 'error', title: 'Error', @@ -104,8 +84,9 @@ const SolutionFieldNew = ({ >