repair: sparepart brand-device

This commit is contained in:
2025-12-12 12:43:14 +07:00
parent 13255f9713
commit 9e8191f8f8
4 changed files with 249 additions and 494 deletions

View File

@@ -20,7 +20,6 @@ const CustomSparepartCard = ({
}) => { }) => {
const [previewModalVisible, setPreviewModalVisible] = useState(false); const [previewModalVisible, setPreviewModalVisible] = useState(false);
// Construct image source with proper fallback
const getImageSrc = () => { const getImageSrc = () => {
if (sparepart.sparepart_foto) { if (sparepart.sparepart_foto) {
if (sparepart.sparepart_foto.startsWith('http')) { if (sparepart.sparepart_foto.startsWith('http')) {
@@ -56,7 +55,6 @@ const CustomSparepartCard = ({
const getCardActions = () => { const getCardActions = () => {
const actions = []; const actions = [];
// Preview button
if (showPreview) { if (showPreview) {
actions.push( actions.push(
<Button <Button
@@ -73,7 +71,6 @@ const CustomSparepartCard = ({
); );
} }
// Delete button without confirmation
if (showDelete && !isReadOnly) { if (showDelete && !isReadOnly) {
actions.push( actions.push(
<Button <Button
@@ -93,7 +90,6 @@ const CustomSparepartCard = ({
return actions; return actions;
}; };
// Get card styling based on size
const getCardStyle = () => { const getCardStyle = () => {
const baseStyle = { const baseStyle = {
borderRadius: '12px', borderRadius: '12px',
@@ -132,11 +128,13 @@ const CustomSparepartCard = ({
<Card <Card
hoverable={!!onCardClick && !isReadOnly} hoverable={!!onCardClick && !isReadOnly}
style={getCardStyle()} style={getCardStyle()}
bodyStyle={{ styles={{
padding: 0, body: {
height: 'calc(100% - 48px)', padding: 0,
display: 'flex', height: 'calc(100% - 48px)',
flexDirection: 'column' display: 'flex',
flexDirection: 'column'
}
}} }}
actions={getCardActions()} actions={getCardActions()}
onClick={handleCardClick} onClick={handleCardClick}
@@ -244,55 +242,65 @@ const CustomSparepartCard = ({
{sparepart.sparepart_name || sparepart.name || 'Unnamed'} {sparepart.sparepart_name || sparepart.name || 'Unnamed'}
</Title> </Title>
{size !== 'small' && ( {/* Stock and Quantity Information */}
<> <div style={{ marginBottom: size === 'small' ? '6px' : '8px' }}>
<Text {/* Stock Status Tag */}
type="secondary" <div style={{ marginBottom: '4px' }}>
<Tag
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
style={{ style={{
fontSize: '12px', fontSize: size === 'small' ? '9px' : '10px',
display: 'block', padding: '0 4px',
marginBottom: '6px', margin: 0,
overflow: 'hidden', height: 'auto'
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}} }}
> >
Stock: {sparepart.sparepart_stock || sparepart.sparepart_stok || '0'} {sparepart.sparepart_unit || 'pcs'} {sparepart.sparepart_stok || 'Not Available'}
</Tag>
</div>
{/* Quantity */}
<div style={{ display: 'flex', alignItems: 'center' }}>
<Text
style={{
fontSize: size === 'small' ? '10px' : '11px',
fontWeight: 500,
color: '#262626',
marginRight: '4px'
}}
>
qty:
</Text> </Text>
<Text
style={{
fontSize: size === 'small' ? '10px' : '11px',
color: sparepart.sparepart_qty > 0 ? '#52c41a' : '#ff4d4f',
fontWeight: 600
}}
>
{sparepart.sparepart_qty || 0}
{sparepart.sparepart_unit ? ` ${sparepart.sparepart_unit}` : ''}
</Text>
</div>
</div>
<div style={{ marginBottom: '6px' }}> <div style={{ marginBottom: size === 'small' ? '4px' : '6px' }}>
<Text
code
style={{
fontSize: '11px',
backgroundColor: '#f5f5f5',
padding: '2px 6px',
borderRadius: '3px'
}}
>
{sparepart.sparepart_code || 'No code'}
</Text>
</div>
</>
)}
{size === 'small' && (
<Text <Text
code code
style={{ style={{
fontSize: '10px', fontSize: size === 'small' ? '10px' : '11px',
backgroundColor: '#f5f5f5', backgroundColor: '#f5f5f5',
display: 'block', padding: '2px 6px',
marginBottom: '4px' borderRadius: '3px'
}} }}
> >
{sparepart.sparepart_code || 'No code'} {sparepart.sparepart_code || 'No code'}
</Text> </Text>
)} </div>
{(sparepart.sparepart_merk || sparepart.sparepart_model) && ( {(sparepart.sparepart_merk || sparepart.sparepart_model) && (
<div style={{ <div style={{
fontSize: size === 'small' ? '10px' : '11px', fontSize: size === 'small' ? '9px' : '10px',
color: '#666', color: '#666',
lineHeight: '1.4', lineHeight: '1.4',
marginBottom: '4px' marginBottom: '4px'
@@ -332,23 +340,24 @@ const CustomSparepartCard = ({
Close Close
</Button> </Button>
]} ]}
width={700} width={800}
centered centered
bodyStyle={{ padding: '24px' }} styles={{ body: { padding: '24px' } }}
> >
<Row gutter={[24, 24]}> <Row gutter={[24, 24]}>
<Col span={8}> <Col span={10}>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<div <div
style={{ style={{
backgroundColor: '#f0f0f0', backgroundColor: '#f0f0f0',
width: '200px', width: '220px',
height: '200px', height: '220px',
margin: '0 auto 16px', margin: '0 auto 16px',
position: 'relative', position: 'relative',
borderRadius: '8px', borderRadius: '12px',
overflow: 'hidden', overflow: 'hidden',
border: '1px solid #E0E0E0', border: '1px solid #E0E0E0',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
}} }}
> >
<img <img
@@ -360,116 +369,138 @@ const CustomSparepartCard = ({
objectFit: 'cover' objectFit: 'cover'
}} }}
onError={(e) => { onError={(e) => {
e.target.src = 'https://via.placeholder.com/200x200/d9d9d9/666666?text=No+Image'; e.target.src = 'https://via.placeholder.com/220x220/d9d9d9/666666?text=No+Image';
}} }}
/> />
</div> </div>
{/* Item Type Tag */}
{sparepart.sparepart_item_type && ( {sparepart.sparepart_item_type && (
<Tag color="blue" style={{ marginTop: '12px' }}> <div style={{ marginBottom: '12px' }}>
{sparepart.sparepart_item_type} <Tag color="blue" style={{ fontSize: '14px', padding: '4px 12px' }}>
</Tag> {sparepart.sparepart_item_type}
)} </Tag>
</div>
</Col>
<Col span={16}>
<div>
<Title level={3} style={{ marginBottom: '16px' }}>
{sparepart.sparepart_name || 'Unnamed'}
</Title>
<Row gutter={[16, 16]} style={{ marginBottom: '16px' }}>
<Col span={12}>
<div style={{ marginBottom: '8px' }}>
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
Code:
</Text>
<Text style={{ fontSize: '16px', marginLeft: '8px' }}>
{sparepart.sparepart_code || 'N/A'}
</Text>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: '8px' }}>
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
Status:
</Text>
<Tag
color={sparepart.is_active ? 'green' : 'red'}
style={{ marginLeft: '8px', fontSize: '14px' }}
>
{sparepart.is_active ? 'Active' : 'Inactive'}
</Tag>
</div>
</Col>
</Row>
{sparepart.sparepart_description && (
<div style={{ marginBottom: '16px' }}>
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
Description:
</Text>
<Text style={{ fontSize: '16px', marginLeft: '8px' }}>
{sparepart.sparepart_description}
</Text>
</div> </div>
)} )}
<div style={{ marginBottom: '16px' }}> {/* Stock Status and Quantity */}
<Text strong style={{ fontSize: '16px', color: '#262626' }}> <div style={{
Stock: textAlign: 'left',
</Text> background: '#f8f9fa',
<Text style={{ fontSize: '16px', marginLeft: '8px' }}> padding: '12px',
{sparepart.sparepart_stock || sparepart.sparepart_stok || '0'} borderRadius: '8px',
{sparepart.sparepart_unit ? ` ${sparepart.sparepart_unit}` : ' units'} marginTop: '25px'
</Text> }}>
<div style={{ marginBottom: '8px' }}>
<Text strong style={{ fontSize: '14px', color: '#262626' }}>Stock Status:</Text>
<Tag
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
style={{ marginLeft: '8px', fontSize: '12px' }}
>
{sparepart.sparepart_stok || 'Not Available'}
</Tag>
</div>
<div>
<Text strong style={{ fontSize: '14px', color: '#262626' }}>Quantity:</Text>
<Text style={{ fontSize: '14px', marginLeft: '8px', fontWeight: 600 }}>
{sparepart.sparepart_qty || 0} {sparepart.sparepart_unit || ''}
</Text>
</div>
</div>
</div>
</Col>
<Col span={14}>
<div>
{/* Sparepart Name */}
<Title level={3} style={{ marginBottom: '20px', color: '#262626' }}>
{sparepart.sparepart_name || 'Unnamed'}
</Title>
{/* Basic Information */}
<div style={{ marginBottom: '24px' }}>
<Row gutter={[16, 12]}>
<Col span={24}>
<div style={{
padding: '12px',
backgroundColor: '#fafafa',
borderRadius: '8px',
border: '1px solid #f0f0f0'
}}>
<Row gutter={16}>
<Col span={8}>
<div>
<Text type="secondary" style={{ fontSize: '12px' }}>Code</Text>
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
{sparepart.sparepart_code || 'N/A'}
</div>
</div>
</Col>
<Col span={8}>
<div>
<Text type="secondary" style={{ fontSize: '12px' }}>Brand</Text>
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
{sparepart.sparepart_merk || '-'}
</div>
</div>
</Col>
<Col span={8}>
<div>
<Text type="secondary" style={{ fontSize: '12px' }}>Unit</Text>
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
{sparepart.sparepart_unit || '-'}
</div>
</div>
</Col>
</Row>
</div>
</Col>
{sparepart.sparepart_model && (
<Col span={24}>
<div>
<Text type="secondary" style={{ fontSize: '12px' }}>Model</Text>
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
{sparepart.sparepart_model}
</div>
</div>
</Col>
)}
{sparepart.sparepart_description && (
<Col span={24}>
<div>
<Text type="secondary" style={{ fontSize: '12px' }}>Description</Text>
<div style={{ fontSize: '15px', marginTop: '2px', lineHeight: '1.5' }}>
{sparepart.sparepart_description}
</div>
</div>
</Col>
)}
</Row>
</div> </div>
<Row gutter={[16, 16]} style={{ marginBottom: '16px' }}> {/* Timeline Information */}
{sparepart.sparepart_merk && ( {sparepart.created_at && (
<Col span={8}> <div>
<div> <Row gutter={16}>
<Text strong style={{ fontSize: '14px', color: '#262626' }}> <Col span={12}>
Brand: <div>
</Text> <Text type="secondary" style={{ fontSize: '12px' }}>Created</Text>
<Text style={{ fontSize: '14px', marginLeft: '8px' }}> <div style={{ fontSize: '13px', marginTop: '2px' }}>
{sparepart.sparepart_merk} {dayjs(sparepart.created_at).format('DD MMM YYYY, HH:mm')}
</Text> </div>
</div> </div>
</Col> </Col>
)} <Col span={12}>
{sparepart.sparepart_model && ( <div>
<Col span={8}> <Text type="secondary" style={{ fontSize: '12px' }}>Last Updated</Text>
<div> <div style={{ fontSize: '13px', marginTop: '2px' }}>
<Text strong style={{ fontSize: '14px', color: '#262626' }}> {dayjs(sparepart.updated_at).format('DD MMM YYYY, HH:mm')}
Model: </div>
</Text> </div>
<Text style={{ fontSize: '14px', marginLeft: '8px' }}> </Col>
{sparepart.sparepart_model} </Row>
</Text>
</div>
</Col>
)}
{sparepart.sparepart_unit && (
<Col span={8}>
<div>
<Text strong style={{ fontSize: '14px', color: '#262626' }}>
Unit:
</Text>
<Text style={{ fontSize: '14px', marginLeft: '8px' }}>
{sparepart.sparepart_unit}
</Text>
</div>
</Col>
)}
</Row>
{sparepart.updated_at && (
<div style={{ marginTop: '24px', paddingTop: '16px', borderTop: '1px solid #f0f0f0' }}>
<Text type="secondary" style={{ fontSize: '14px' }}>
Last updated: {dayjs(sparepart.updated_at).format('DD MMMM YYYY, HH:mm')}
</Text>
</div> </div>
)} )}
</div> </div>

View File

@@ -1,249 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Typography, Tag, Space, Spin, Button, Empty, message } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { getAllSparepart } from '../../../../api/sparepart';
import { addSparepartToBrand, removeSparepartFromBrand } from '../../../../api/master-brand';
import CustomSparepartCard from './CustomSparepartCard';
const { Text, Title } = Typography;
const SparepartCardSelect = ({
selectedSparepartIds = [],
onSparepartChange,
isLoading: externalLoading = false,
isReadOnly = false,
brandId = null
}) => {
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) {
// 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 = async (sparepartId) => {
if (isReadOnly) return;
const isCurrentlySelected = selectedSparepartIds.includes(sparepartId);
// If brandId is provided, save immediately to database
if (brandId) {
try {
setLoading(true);
if (isCurrentlySelected) {
// Remove from database
await removeSparepartFromBrand(brandId, sparepartId);
message.success('Sparepart removed from brand successfully');
} else {
// Add to database
await addSparepartToBrand(brandId, sparepartId);
message.success('Sparepart added to brand successfully');
}
// Update local state
const newSelectedIds = isCurrentlySelected
? selectedSparepartIds.filter(id => id !== sparepartId)
: [...selectedSparepartIds, sparepartId];
onSparepartChange(newSelectedIds);
} catch (error) {
message.error(error.message || 'Failed to update sparepart');
} finally {
setLoading(false);
}
} else {
// If no brandId (add mode), just update local state
const newSelectedIds = isCurrentlySelected
? 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 xs={24} sm={12} md={12} lg={12} key={sparepart.sparepart_id}>
<CustomSparepartCard
sparepart={sparepart}
isSelected={isSelected(sparepart.sparepart_id)}
isReadOnly={isReadOnly}
showPreview={true}
showDelete={true}
onCardClick={() => handleSparepartToggle(sparepart.sparepart_id)}
onDelete={() => {
// When delete button is clicked, remove from selection
const newSelectedIds = selectedSparepartIds.filter(id => id !== sparepart.sparepart_id);
onSparepartChange(newSelectedIds);
// Also remove from database if brandId exists
if (brandId) {
removeSparepartFromBrand(brandId, sparepart.sparepart_id)
.then(() => message.success('Sparepart removed successfully'))
.catch(error => message.error(error.message || 'Failed to remove sparepart'));
}
}}
/>
</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;

View File

@@ -1,26 +0,0 @@
import React from 'react';
import { Card, Divider, Typography } from 'antd';
import SparepartCardSelect from './SparepartCardSelect';
const { Text } = Typography;
const SparepartForm = ({
sparepartForm,
selectedSparepartIds,
onSparepartChange,
isReadOnly = false
}) => {
return (
<div style={{ minHeight: '400px' }}>
<Card title="Spareparts" style={{ minHeight: '100%' }}>
<SparepartCardSelect
selectedSparepartIds={selectedSparepartIds}
onSparepartChange={onSparepartChange}
isReadOnly={isReadOnly}
/>
</Card>
</div>
);
};
export default SparepartForm;

View File

@@ -7,7 +7,7 @@ import CustomSparepartCard from './CustomSparepartCard';
const { Text, Title } = Typography; const { Text, Title } = Typography;
const { Option } = Select; const { Option } = Select;
const SingleSparepartSelect = ({ const SparepartSelect = ({
selectedSparepartIds = [], selectedSparepartIds = [],
onSparepartChange, onSparepartChange,
isReadOnly = false isReadOnly = false
@@ -32,45 +32,25 @@ const SingleSparepartSelect = ({
} }
}, [selectedSparepartIds, spareparts]); }, [selectedSparepartIds, spareparts]);
const fetchSpareparts = async () => { const fetchSpareparts = async (searchQuery = '') => {
setLoading(true); setLoading(true);
try { try {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('limit', '1000'); params.set('limit', '1000');
if (searchQuery && searchQuery.trim() !== '') {
params.set('criteria', searchQuery.trim());
}
const response = await getAllSparepart(params); const response = await getAllSparepart(params);
if (response && (response.statusCode === 200 || response.data)) { if (response && (response.statusCode === 200 || response.data)) {
const sparepartData = response.data?.data || response.data || []; const sparepartData = response.data?.data || response.data || [];
setSpareparts(sparepartData); setSpareparts(sparepartData);
} else { } else {
setSpareparts([ 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) { } catch (error) {
setSpareparts([ 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 { } finally {
setLoading(false); setLoading(false);
} }
@@ -93,6 +73,17 @@ const SingleSparepartSelect = ({
setDropdownOpen(false); setDropdownOpen(false);
}; };
const handleSearch = (value) => {
fetchSpareparts(value);
};
const onDropdownOpenChange = (open) => {
setDropdownOpen(open);
if (open) {
fetchSpareparts();
}
};
const handleRemoveSparepart = (sparepartId) => { const handleRemoveSparepart = (sparepartId) => {
const newSelectedSpareparts = selectedSpareparts.filter(sp => sp.sparepart_id !== sparepartId); const newSelectedSpareparts = selectedSpareparts.filter(sp => sp.sparepart_id !== sparepartId);
setSelectedSpareparts(newSelectedSpareparts); setSelectedSpareparts(newSelectedSpareparts);
@@ -111,7 +102,7 @@ const SingleSparepartSelect = ({
isSelected={isSelected} isSelected={isSelected}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
showPreview={true} showPreview={true}
showDelete={isAlreadySelected && !isReadOnly} // Show delete only for already selected items showDelete={isAlreadySelected && !isReadOnly}
onCardClick={!isAlreadySelected && !isReadOnly ? () => handleSparepartSelect(sparepart.sparepart_id) : undefined} onCardClick={!isAlreadySelected && !isReadOnly ? () => handleSparepartSelect(sparepart.sparepart_id) : undefined}
onDelete={() => handleRemoveSparepart(sparepart.sparepart_id)} onDelete={() => handleRemoveSparepart(sparepart.sparepart_id)}
style={{ style={{
@@ -124,26 +115,34 @@ const SingleSparepartSelect = ({
return ( return (
<> <>
<div> {/* Fixed Search Section */}
{!isReadOnly && ( {!isReadOnly && (
<div style={{ marginBottom: 16 }}> <div style={{
<Select marginBottom: 16,
placeholder="Search and select sparepart..." position: 'sticky',
style={{ width: '100%' }} top: 0,
loading={loading} zIndex: 10,
onSelect={handleSparepartSelect} backgroundColor: 'white',
value={null} padding: '8px 0',
showSearch borderBottom: '1px solid #f0f0f0'
filterOption={(input, option) => }}>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 <Select
} placeholder="search and select sparepart"
open={dropdownOpen} style={{ width: '100%' }}
onDropdownVisibleChange={setDropdownOpen} loading={loading}
suffixIcon={<PlusOutlined />} onSelect={handleSparepartSelect}
> value={null}
{spareparts showSearch
.filter(sparepart => !selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id)) onSearch={handleSearch}
.map((sparepart) => ( filterOption={false}
open={dropdownOpen}
onOpenChange={onDropdownOpenChange}
suffixIcon={<PlusOutlined />}
>
{spareparts
.filter(sparepart => !selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id))
.slice(0, 5)
.map((sparepart) => (
<Option key={sparepart.sparepart_id} value={sparepart.sparepart_id}> <Option key={sparepart.sparepart_id} value={sparepart.sparepart_id}>
<div> <div>
<Text strong>{sparepart.sparepart_name || sparepart.name || 'Unnamed'}</Text> <Text strong>{sparepart.sparepart_name || sparepart.name || 'Unnamed'}</Text>
@@ -153,31 +152,31 @@ const SingleSparepartSelect = ({
</div> </div>
</Option> </Option>
))} ))}
</Select> </Select>
</div>
)}
<div>
{selectedSpareparts.length > 0 ? (
<div>
<Title level={5} style={{ marginBottom: 16 }}>
Selected Spareparts ({selectedSpareparts.length})
</Title>
<Row gutter={[16, 16]}>
{selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))}
</Row>
</div>
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="No spareparts selected"
style={{ margin: '20px 0' }}
/>
)}
</div> </div>
)}
{/* Scrollable Selected Items Section */}
<div>
{selectedSpareparts.length > 0 ? (
<div>
<Title level={5} style={{ marginBottom: 16 }}>
Selected Spareparts ({selectedSpareparts.length})
</Title>
<Row gutter={[16, 16]}>
{selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))}
</Row>
</div>
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="No spareparts selected"
style={{ margin: '20px 0' }}
/>
)}
</div> </div>
</> </>
); );
}; };
export default SingleSparepartSelect; export default SparepartSelect;