feat: implement sparepart selection functionality and refactor related components
This commit is contained in:
@@ -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 = () => {
|
||||
</Form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* Sparepart Form Column */}
|
||||
<Col span={8}>
|
||||
<Card size="small" title="Spareparts">
|
||||
<Form
|
||||
form={sparepartForm}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
sparepart_status_0: true,
|
||||
}}
|
||||
>
|
||||
<SparepartForm
|
||||
sparepartForm={sparepartForm}
|
||||
sparepartFields={sparepartFields}
|
||||
onAddSparepartField={handleAddSparepartField}
|
||||
onRemoveSparepartField={handleRemoveSparepartField}
|
||||
selectedSparepartIds={selectedSparepartIds}
|
||||
onSparepartChange={handleSparepartChange}
|
||||
isReadOnly={isErrorCodeFormReadOnly}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
@@ -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 = () => {
|
||||
<Form
|
||||
form={sparepartForm}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
sparepart_status_0: true,
|
||||
}}
|
||||
>
|
||||
<SparepartForm
|
||||
sparepartForm={sparepartForm}
|
||||
sparepartFields={sparepartFields}
|
||||
onAddSparepartField={handleAddSparepartField}
|
||||
onRemoveSparepartField={handleRemoveSparepartField}
|
||||
selectedSparepartIds={selectedSparepartIds}
|
||||
onSparepartChange={handleSparepartChange}
|
||||
isReadOnly={isErrorCodeFormReadOnly}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
310
src/pages/master/brandDevice/component/SparepartCardSelect.jsx
Normal file
310
src/pages/master/brandDevice/component/SparepartCardSelect.jsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Select Spareparts
|
||||
</Title>
|
||||
<div style={{ position: 'relative', width: '200px' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search spareparts..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
style={{
|
||||
padding: '8px 30px 8px 12px',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '6px',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
<SearchOutlined
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#bfbfbf'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{combinedLoading ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : filteredSpareparts.length === 0 ? (
|
||||
<Empty
|
||||
description="No spareparts found"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{filteredSpareparts.map(sparepart => (
|
||||
<Col span={8} key={sparepart.sparepart_id}>
|
||||
<Card
|
||||
size="small"
|
||||
hoverable
|
||||
style={{
|
||||
border: isSelected(sparepart.sparepart_id)
|
||||
? '2px solid #23A55A'
|
||||
: '1px solid #d9d9d9',
|
||||
backgroundColor: isSelected(sparepart.sparepart_id)
|
||||
? '#f6ffed'
|
||||
: 'white',
|
||||
cursor: isReadOnly ? 'default' : 'pointer',
|
||||
position: 'relative'
|
||||
}}
|
||||
onClick={() => handleSparepartToggle(sparepart.sparepart_id)}
|
||||
>
|
||||
<div style={{ position: 'absolute', top: 8, right: 8 }}>
|
||||
{isSelected(sparepart.sparepart_id) ? (
|
||||
<CheckCircleOutlined
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
color: '#23A55A',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CloseCircleOutlined
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
color: '#d9d9d9',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', marginBottom: 12 }}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: 120,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#f5f5f5',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{sparepart.sparepart_foto ? (
|
||||
<Image
|
||||
src={sparepart.sparepart_foto}
|
||||
alt={sparepart.sparepart_name}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
preview={false}
|
||||
fallback="/assets/defaultSparepartImg.jpg"
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
color: '#bfbfbf',
|
||||
fontSize: 12
|
||||
}}>
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text
|
||||
strong
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: '14px',
|
||||
marginBottom: 4,
|
||||
color: isSelected(sparepart.sparepart_id) ? '#23A55A' : 'inherit'
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_name}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
display: 'block',
|
||||
marginBottom: 4
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_description || 'No description'}
|
||||
</Text>
|
||||
|
||||
<Space size="small" style={{ marginBottom: 4 }}>
|
||||
<Tag color="blue" style={{ margin: 0 }}>
|
||||
{sparepart.sparepart_code}
|
||||
</Tag>
|
||||
<Tag color="geekblue" style={{ margin: 0 }}>
|
||||
{sparepart.sparepart_merk || 'N/A'}
|
||||
</Tag>
|
||||
</Space>
|
||||
|
||||
{sparepart.sparepart_model && (
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
color: '#666'
|
||||
}}>
|
||||
Model: {sparepart.sparepart_model}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
|
||||
{selectedSparepartIds.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Text strong>Selected Spareparts: </Text>
|
||||
<Space wrap>
|
||||
{selectedSparepartIds.map(id => {
|
||||
const sparepart = spareparts.find(sp => sp.sparepart_id === id);
|
||||
return sparepart ? (
|
||||
<Tag key={id} color="green">
|
||||
{sparepart.sparepart_name} (ID: {id})
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag key={id} color="green">
|
||||
Sparepart ID: {id}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SparepartCardSelect;
|
||||
@@ -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 (
|
||||
<div style={{
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginBottom: 16,
|
||||
backgroundColor: isReadOnly ? '#f5f5f5' : 'white'
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
||||
<Text strong>Sparepart #{index + 1}</Text>
|
||||
<Space>
|
||||
<Form.Item
|
||||
name={[fieldName, 'sparepart_id']}
|
||||
rules={[{ required: false, message: 'Sparepart wajib dipilih!' }]} /* Making it optional since sparepart is optional */
|
||||
style={{ margin: 0, width: 200 }}
|
||||
>
|
||||
<Select
|
||||
placeholder="Pilih sparepart"
|
||||
loading={loading}
|
||||
disabled={isReadOnly}
|
||||
options={sparepartOptions}
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<Form.Item name={[fieldName, 'status']} valuePropName="checked" noStyle>
|
||||
<Switch
|
||||
disabled={isReadOnly}
|
||||
onChange={(checked) => {
|
||||
setCurrentStatus(checked);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: currentStatus ? '#23A55A' : '#bfbfbf'
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Text style={{ fontSize: 12, color: '#666' }}>
|
||||
{currentStatus ? 'Active' : 'Inactive'}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{canRemove && !isReadOnly && (
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* Sparepart Description */}
|
||||
<Form.Item
|
||||
name={[fieldName, 'description']}
|
||||
label="Deskripsi"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="Deskripsi sparepart"
|
||||
rows={2}
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SparepartField;
|
||||
@@ -1,74 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Card, Typography, Divider, Button } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import SparepartField from './SparepartField';
|
||||
import React from 'react';
|
||||
import { Card, Divider, Typography } from 'antd';
|
||||
import SparepartCardSelect from './SparepartCardSelect';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const SparepartForm = ({
|
||||
sparepartForm,
|
||||
sparepartFields,
|
||||
onAddSparepartField,
|
||||
onRemoveSparepartField,
|
||||
isReadOnly = false,
|
||||
spareparts = [],
|
||||
onSparepartChange
|
||||
selectedSparepartIds,
|
||||
onSparepartChange,
|
||||
isReadOnly = false
|
||||
}) => {
|
||||
const [sparepartList, setSparepartList] = useState([]);
|
||||
|
||||
const handleSparepartChange = (list) => {
|
||||
setSparepartList(list);
|
||||
if (onSparepartChange) {
|
||||
onSparepartChange(list);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form
|
||||
form={sparepartForm}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
sparepart_status_0: true,
|
||||
}}
|
||||
>
|
||||
<Divider orientation="left">Sparepart Items</Divider>
|
||||
|
||||
{sparepartFields.map((field, index) => (
|
||||
<SparepartField
|
||||
key={field.key}
|
||||
fieldKey={field.key}
|
||||
fieldName={field.name}
|
||||
index={index}
|
||||
sparepartStatus={field.status}
|
||||
onRemove={() => onRemoveSparepartField(field.key)}
|
||||
isReadOnly={isReadOnly}
|
||||
canRemove={sparepartFields.length > 1}
|
||||
spareparts={sparepartList}
|
||||
onSparepartChange={handleSparepartChange}
|
||||
/>
|
||||
))}
|
||||
|
||||
{!isReadOnly && (
|
||||
<>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={onAddSparepartField}
|
||||
icon={<PlusOutlined />}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
+ Add Sparepart
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Text type="secondary">
|
||||
* Sparepart is optional and can be added for each error code if needed.
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
<Card size="small" title="Spareparts">
|
||||
<SparepartCardSelect
|
||||
selectedSparepartIds={selectedSparepartIds}
|
||||
onSparepartChange={onSparepartChange}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ export const useSparepartLogic = (sparepartForm) => {
|
||||
delete newStatuses[key];
|
||||
return newStatuses;
|
||||
});
|
||||
|
||||
|
||||
// Add to delete list if it's not a new field
|
||||
setSparepartsToDelete(prev => new Set([...prev, key]));
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user