From fbc5473f2be9891d6ede40ea09084ee535d92aa0 Mon Sep 17 00:00:00 2001 From: vinix Date: Fri, 28 Nov 2025 14:00:17 +0700 Subject: [PATCH] feat: implement sparepart selection functionality and refactor related components --- .../master/brandDevice/AddBrandDevice.jsx | 69 ++-- .../master/brandDevice/EditBrandDevice.jsx | 83 +++-- .../component/SparepartCardSelect.jsx | 310 ++++++++++++++++++ .../brandDevice/component/SparepartField.jsx | 152 --------- .../brandDevice/component/SparepartForm.jsx | 76 +---- .../master/brandDevice/hooks/sparepart.js | 2 +- 6 files changed, 408 insertions(+), 284 deletions(-) create mode 100644 src/pages/master/brandDevice/component/SparepartCardSelect.jsx delete mode 100644 src/pages/master/brandDevice/component/SparepartField.jsx diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx index 2c7bd57..0f91e57 100644 --- a/src/pages/master/brandDevice/AddBrandDevice.jsx +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -56,6 +56,7 @@ const AddBrandDevice = () => { const [formData, setFormData] = useState(defaultData); const [errorCodes, setErrorCodes] = useState([]); const [errorCodeIcon, setErrorCodeIcon] = useState(null); + const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); const { solutionFields, @@ -73,19 +74,33 @@ const AddBrandDevice = () => { setSolutionsForExistingRecord, } = useSolutionLogic(solutionForm); - const { - sparepartFields, - sparepartTypes, - sparepartStatuses, - sparepartsToDelete, - handleAddSparepartField, - handleRemoveSparepartField, - handleSparepartTypeChange, - handleSparepartStatusChange, - resetSparepartFields, - getSparepartData, - setSparepartsForExistingRecord, - } = useSparepartLogic(sparepartForm); + // 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([ @@ -155,22 +170,16 @@ const AddBrandDevice = () => { path_solution: sol.path_solution || '', is_active: sol.is_active !== false, })), - ...(ec.sparepart && ec.sparepart.length > 0 && { - sparepart: ec.sparepart.map((sp) => ({ - sparepart_name: sp.sparepart_name || sp.name || sp.label || '', - brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.sparepart_description || '', - is_active: sp.is_active !== false, - path_foto: sp.path_foto || '', - })), - }), })); + const sparepartData = 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, + spareparts: sparepartData, error_code: transformedErrorCodes, }; @@ -222,7 +231,7 @@ const AddBrandDevice = () => { } if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart, sparepartForm); + setSparepartsForExistingRecord(record.sparepart); } }; @@ -254,6 +263,10 @@ const AddBrandDevice = () => { } else { resetSolutionFields(); } + + if (record.sparepart && record.sparepart.length > 0) { + setSparepartsForExistingRecord(record.sparepart); + } }; const handleAddErrorCode = async () => { @@ -282,7 +295,6 @@ const AddBrandDevice = () => { return; } - const sparepartData = getSparepartData(); const newErrorCode = { key: Date.now(), error_code: formValues.error_code, @@ -293,7 +305,6 @@ const AddBrandDevice = () => { status: formValues.status !== false, errorCodeIcon: errorCodeIcon, solution: solutions, - ...(sparepartData && sparepartData.length > 0 && { sparepart: sparepartData }), // Only add sparepart if there are spareparts }; if (editingErrorCodeKey) { @@ -516,22 +527,16 @@ const AddBrandDevice = () => { - - {/* Sparepart Form Column */}
diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index 0581433..b54d818 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -61,6 +61,7 @@ const EditBrandDevice = () => { const [errorCodeIcon, setErrorCodeIcon] = useState(null); const [solutionForm] = Form.useForm(); const [sparepartForm] = Form.useForm(); + const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic( errorCodeForm, @@ -80,19 +81,33 @@ const EditBrandDevice = () => { setSolutionsForExistingRecord, } = useSolutionLogic(solutionForm); - const { - sparepartFields, - sparepartTypes, - sparepartStatuses, - sparepartsToDelete, - handleAddSparepartField, - handleRemoveSparepartField, - handleSparepartTypeChange, - handleSparepartStatusChange, - resetSparepartFields, - getSparepartData, - setSparepartsForExistingRecord, - } = useSparepartLogic(sparepartForm); + // 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 () => { @@ -176,6 +191,14 @@ const EditBrandDevice = () => { setFormData(newFormData); brandForm.setFieldsValue(newFormData); setErrorCodes(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); + setSelectedSparepartIds(sparepartIds); + setSparepartsForExistingRecord(sparepartIds); + } } else { NotifAlert({ icon: 'error', @@ -244,12 +267,6 @@ const EditBrandDevice = () => { path_solution: sol.path_solution || '', is_active: sol.is_active !== false, })), - sparepart: (ec.sparepart || []).map((sp) => ({ - sparepart_name: sp.sparepart_name || sp.name || sp.label || '', - brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.brand_sparepart_description || '', - is_active: sp.is_active !== false, - path_foto: sp.path_foto || '', - })), }; } @@ -268,19 +285,17 @@ const EditBrandDevice = () => { path_solution: sol.path_solution || '', is_active: sol.is_active !== false, })), - ...(ec.sparepart && ec.sparepart.length > 0 && { - sparepart: ec.sparepart.map((sp) => ({ - sparepart_name: sp.sparepart_name || sp.name || sp.label || '', - brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.brand_sparepart_description || '', - is_active: sp.is_active !== false, - path_foto: sp.path_foto || '', - })), - }), }; }), }; - const response = await updateBrand(id, finalFormData); + const sparepartData = getSparepartData(); + const updatedFinalFormData = { + ...finalFormData, + spareparts: sparepartData, + }; + + const response = await updateBrand(id, updatedFinalFormData); if (response && (response.statusCode === 200 || response.statusCode === 201)) { localStorage.removeItem(`brand_device_edit_${id}_temp_data`); @@ -329,7 +344,7 @@ const EditBrandDevice = () => { // Load spareparts to sparepart form if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart, sparepartForm); + setSparepartsForExistingRecord(record.sparepart); } else { resetSparepartFields(); } @@ -354,7 +369,7 @@ const EditBrandDevice = () => { // Load spareparts to sparepart form if (record.sparepart && record.sparepart.length > 0) { - setSparepartsForExistingRecord(record.sparepart, sparepartForm); + setSparepartsForExistingRecord(record.sparepart); } const formElement = document.querySelector('.ant-form'); @@ -624,15 +639,11 @@ const EditBrandDevice = () => {
diff --git a/src/pages/master/brandDevice/component/SparepartCardSelect.jsx b/src/pages/master/brandDevice/component/SparepartCardSelect.jsx new file mode 100644 index 0000000..16016cd --- /dev/null +++ b/src/pages/master/brandDevice/component/SparepartCardSelect.jsx @@ -0,0 +1,310 @@ +import React, { useState, useEffect } from 'react'; +import { Card, Row, Col, Image, Typography, Tag, Space, Spin, Button, Empty } from 'antd'; +import { CheckCircleOutlined, CloseCircleOutlined, SearchOutlined } from '@ant-design/icons'; +import { getAllSparepart } from '../../../../api/sparepart'; + +const { Text, Title } = Typography; + +const SparepartCardSelect = ({ + selectedSparepartIds = [], + onSparepartChange, + isLoading: externalLoading = false, + isReadOnly = false +}) => { + const [spareparts, setSpareparts] = useState([]); + const [loading, setLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + loadSpareparts(); + }, []); + + const loadSpareparts = async () => { + setLoading(true); + try { + const params = new URLSearchParams(); + params.set('limit', '1000'); // Get all spareparts + + const response = await getAllSparepart(params); + if (response && (response.statusCode === 200 || response.data)) { + const sparepartData = response.data?.data || response.data || []; + setSpareparts(sparepartData); + } else { + // For demo purposes, use mock data if API fails + 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' + }, + { + sparepart_id: 2, + sparepart_name: 'Air Intake Filter', + sparepart_description: 'Air intake filter', + sparepart_foto: null, + sparepart_code: 'SP-002', + sparepart_merk: 'Brand B', + sparepart_model: 'Model Y' + }, + { + sparepart_id: 3, + sparepart_name: 'Cooling Fan Motor', + sparepart_description: 'Motor for cooling fan', + sparepart_foto: null, + sparepart_code: 'SP-003', + sparepart_merk: 'Brand C', + sparepart_model: 'Model Z' + }, + ]); + } + } catch (error) { + console.error('Error loading spareparts:', error); + // Default mock data + 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' + }, + { + sparepart_id: 2, + sparepart_name: 'Air Intake Filter', + sparepart_description: 'Air intake filter', + sparepart_foto: null, + sparepart_code: 'SP-002', + sparepart_merk: 'Brand B', + sparepart_model: 'Model Y' + }, + { + sparepart_id: 3, + sparepart_name: 'Cooling Fan Motor', + sparepart_description: 'Motor for cooling fan', + sparepart_foto: null, + sparepart_code: 'SP-003', + sparepart_merk: 'Brand C', + sparepart_model: 'Model Z' + }, + ]); + } finally { + setLoading(false); + } + }; + + const filteredSpareparts = spareparts.filter(sp => + sp.sparepart_name.toLowerCase().includes(searchTerm.toLowerCase()) || + sp.sparepart_code.toLowerCase().includes(searchTerm.toLowerCase()) || + sp.sparepart_merk?.toLowerCase().includes(searchTerm.toLowerCase()) || + sp.sparepart_model?.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const handleSparepartToggle = (sparepartId) => { + if (isReadOnly) return; + + const newSelectedIds = selectedSparepartIds.includes(sparepartId) + ? selectedSparepartIds.filter(id => id !== sparepartId) + : [...selectedSparepartIds, sparepartId]; + + onSparepartChange(newSelectedIds); + }; + + const isSelected = (sparepartId) => selectedSparepartIds.includes(sparepartId); + + const combinedLoading = loading || externalLoading; + + return ( +
+
+ + + Select Spareparts + +
+ setSearchTerm(e.target.value)} + style={{ + padding: '8px 30px 8px 12px', + border: '1px solid #d9d9d9', + borderRadius: '6px', + width: '100%' + }} + /> + +
+
+
+ + {combinedLoading ? ( +
+ +
+ ) : filteredSpareparts.length === 0 ? ( + + ) : ( + + {filteredSpareparts.map(sparepart => ( + + handleSparepartToggle(sparepart.sparepart_id)} + > +
+ {isSelected(sparepart.sparepart_id) ? ( + + ) : ( + + )} +
+ +
+
+ {sparepart.sparepart_foto ? ( + {sparepart.sparepart_name} + ) : ( +
+ No Image +
+ )} +
+
+ +
+ + {sparepart.sparepart_name} + + + + {sparepart.sparepart_description || 'No description'} + + + + + {sparepart.sparepart_code} + + + {sparepart.sparepart_merk || 'N/A'} + + + + {sparepart.sparepart_model && ( +
+ Model: {sparepart.sparepart_model} +
+ )} +
+
+ + ))} +
+ )} + + {selectedSparepartIds.length > 0 && ( +
+ Selected Spareparts: + + {selectedSparepartIds.map(id => { + const sparepart = spareparts.find(sp => sp.sparepart_id === id); + return sparepart ? ( + + {sparepart.sparepart_name} (ID: {id}) + + ) : ( + + Sparepart ID: {id} + + ); + })} + +
+ )} +
+ ); +}; + +export default SparepartCardSelect; \ No newline at end of file diff --git a/src/pages/master/brandDevice/component/SparepartField.jsx b/src/pages/master/brandDevice/component/SparepartField.jsx deleted file mode 100644 index f643de8..0000000 --- a/src/pages/master/brandDevice/component/SparepartField.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Form, Select, Button, Switch, Typography, Space, Input, message } from 'antd'; -import { DeleteOutlined } from '@ant-design/icons'; -import { getAllSparepart } from '../../../../api/sparepart'; - -const { Text } = Typography; - -const SparepartField = ({ - fieldKey, - fieldName, - index, - sparepartType, - sparepartStatus, - isReadOnly = false, - canRemove = true, - onRemove, - spareparts = [], - onSparepartChange -}) => { - const [currentStatus, setCurrentStatus] = useState(sparepartStatus ?? true); - const [sparepartList, setSparepartList] = useState([]); - const [loading, setLoading] = useState(false); - - useEffect(() => { - setCurrentStatus(sparepartStatus ?? true); - loadSpareparts(); - }, [sparepartStatus]); - - const loadSpareparts = async () => { - setLoading(true); - try { - // Get all spareparts from the API - const params = new URLSearchParams(); - params.set('limit', '100'); // Get all spareparts - - const response = await getAllSparepart(params); - // Response structure should have { data: [...], statusCode: 200 } - if (response && (response.statusCode === 200 || response.data)) { - // If response has data array directly - const sparepartData = response.data?.data || response.data || []; - setSparepartList(sparepartData); - if (onSparepartChange) { - onSparepartChange(sparepartData); - } - } else { - // For demo purposes, use mock data if API fails - setSparepartList([ - { brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' }, - { brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' }, - { brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' }, - ]); - if (onSparepartChange) { - onSparepartChange([ - { brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' }, - { brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' }, - { brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' }, - ]); - } - } - } catch (error) { - console.error('Error loading spareparts:', error); - // Default mock data - const mockSpareparts = [ - { brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' }, - { brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' }, - { brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' }, - ]; - setSparepartList(mockSpareparts); - if (onSparepartChange) { - onSparepartChange(mockSpareparts); - } - } finally { - setLoading(false); - } - }; - - const sparepartOptions = sparepartList.map(sparepart => ({ - label: sparepart.sparepart_name || sparepart.sparepart_name || `Sparepart ${sparepart.sparepart_id || sparepart.brand_sparepart_id}`, - value: sparepart.sparepart_id || sparepart.brand_sparepart_id, - description: sparepart.sparepart_description - })); - - return ( -
-
- Sparepart #{index + 1} - - -