feat: implement sparepart selection functionality and refactor related components

This commit is contained in:
2025-11-28 14:00:17 +07:00
parent 55a47c3a25
commit fbc5473f2b
6 changed files with 408 additions and 284 deletions

View 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;