update ui brand device

This commit is contained in:
2025-12-04 01:17:25 +07:00
parent 1bc98de564
commit f22e120204
15 changed files with 3132 additions and 1349 deletions

View File

@@ -0,0 +1,483 @@
import React, { useState } from 'react';
import { Card, Typography, Tag, Button, Modal, Row, Col, Space } from 'antd';
import { EyeOutlined, DeleteOutlined, CheckOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
const { Text, Title } = Typography;
const CustomSparepartCard = ({
sparepart,
isSelected = false,
isReadOnly = false,
showPreview = true,
showDelete = false,
onPreview,
onDelete,
onCardClick,
loading = false,
size = 'small',
style = {},
}) => {
const [previewModalVisible, setPreviewModalVisible] = useState(false);
// Construct image source with proper fallback
const getImageSrc = () => {
if (sparepart.sparepart_foto) {
if (sparepart.sparepart_foto.startsWith('http')) {
return sparepart.sparepart_foto;
} else {
const fileName = sparepart.sparepart_foto.split('/').pop();
if (fileName === 'defaultSparepartImg.jpg') {
return `/assets/defaultSparepartImg.jpg`;
} else {
const token = localStorage.getItem('token');
const baseURL = import.meta.env.VITE_API_SERVER || '';
return `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`;
}
}
}
return 'https://via.placeholder.com/150';
};
const handlePreview = () => {
if (onPreview) {
onPreview(sparepart);
} else {
setPreviewModalVisible(true);
}
};
const handleCardClick = () => {
if (!isReadOnly && onCardClick) {
onCardClick(sparepart);
}
};
const getCardActions = () => {
const actions = [];
// Preview button
if (showPreview) {
actions.push(
<Button
key="preview"
type="text"
icon={<EyeOutlined />}
title="Lihat Detail"
style={{ color: '#1890ff' }}
onClick={(e) => {
e.stopPropagation();
handlePreview();
}}
/>
);
}
// Delete button without confirmation
if (showDelete && !isReadOnly) {
actions.push(
<Button
key="delete"
type="text"
icon={<DeleteOutlined />}
title="Hapus"
style={{ color: '#ff4d4f' }}
onClick={(e) => {
e.stopPropagation();
onDelete?.(sparepart);
}}
/>
);
}
return actions;
};
// Get card styling based on size
const getCardStyle = () => {
const baseStyle = {
borderRadius: '12px',
overflow: 'hidden',
border: isSelected ? '2px solid #1890ff' : '1px solid #E0E0E0',
cursor: isReadOnly ? 'default' : 'pointer',
position: 'relative',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
transition: 'all 0.3s ease'
};
switch (size) {
case 'small':
return {
...baseStyle,
height: '180px',
minHeight: '180px'
};
case 'large':
return {
...baseStyle,
height: '280px',
minHeight: '280px'
};
default:
return {
...baseStyle,
height: '220px',
minHeight: '220px'
};
}
};
return (
<>
<Card
hoverable={!!onCardClick && !isReadOnly}
style={getCardStyle()}
bodyStyle={{
padding: 0,
height: 'calc(100% - 48px)',
display: 'flex',
flexDirection: 'column'
}}
actions={getCardActions()}
onClick={handleCardClick}
>
<div style={{ display: 'flex', height: '100%' }}>
{/* Image Section */}
<div style={{
width: size === 'small' ? '90px' : '110px',
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: size === 'small' ? '12px' : '16px',
backgroundColor: '#fafafa',
borderRight: '1px solid #f0f0f0',
position: 'relative'
}}>
{sparepart.sparepart_item_type && (
<Tag
color="blue"
style={{
marginBottom: '8px',
fontSize: '10px',
fontWeight: 500
}}
>
{sparepart.sparepart_item_type}
</Tag>
)}
<div
style={{
width: size === 'small' ? '65px' : '75px',
height: size === 'small' ? '65px' : '75px',
backgroundColor: '#f0f0f0',
borderRadius: '8px',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid #e8e8e8'
}}
>
<img
src={getImageSrc()}
alt={sparepart.sparepart_name || 'Sparepart'}
style={{
width: '100%',
height: '100%',
objectFit: 'cover'
}}
onError={(e) => {
e.target.src = 'https://via.placeholder.com/75';
}}
/>
</div>
{/* Selection Indicator */}
{isSelected && (
<div
style={{
position: 'absolute',
top: '8px',
right: '8px',
backgroundColor: '#52c41a',
borderRadius: '50%',
width: '18px',
height: '18px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
zIndex: 2
}}
>
<CheckOutlined style={{ color: 'white', fontSize: '10px' }} />
</div>
)}
</div>
{/* Content Section */}
<div style={{
flex: 1,
padding: size === 'small' ? '12px' : '16px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
overflow: 'hidden'
}}>
<div style={{ flex: 1, overflow: 'hidden' }}>
<Title
level={size === 'small' ? 5 : 4}
style={{
margin: `0 0 ${size === 'small' ? '6px' : '8px'} 0`,
fontSize: size === 'small' ? '12px' : '14px',
fontWeight: 600,
lineHeight: '1.4',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: size === 'small' ? 2 : 3,
WebkitBoxOrient: 'vertical'
}}
>
{sparepart.sparepart_name || sparepart.name || 'Unnamed'}
</Title>
{size !== 'small' && (
<>
<Text
type="secondary"
style={{
fontSize: '12px',
display: 'block',
marginBottom: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
Stock: {sparepart.sparepart_stock || sparepart.sparepart_stok || '0'} {sparepart.sparepart_unit || 'pcs'}
</Text>
<div style={{ marginBottom: '6px' }}>
<Text
code
style={{
fontSize: '11px',
backgroundColor: '#f5f5f5',
padding: '2px 6px',
borderRadius: '3px'
}}
>
{sparepart.sparepart_code || 'No code'}
</Text>
</div>
</>
)}
{size === 'small' && (
<Text
code
style={{
fontSize: '10px',
backgroundColor: '#f5f5f5',
display: 'block',
marginBottom: '4px'
}}
>
{sparepart.sparepart_code || 'No code'}
</Text>
)}
{(sparepart.sparepart_merk || sparepart.sparepart_model) && (
<div style={{
fontSize: size === 'small' ? '10px' : '11px',
color: '#666',
lineHeight: '1.4',
marginBottom: '4px'
}}>
{sparepart.sparepart_merk && (
<div>Brand: {sparepart.sparepart_merk}</div>
)}
{sparepart.sparepart_model && (
<div>Model: {sparepart.sparepart_model}</div>
)}
</div>
)}
</div>
<Text
type="secondary"
style={{
fontSize: size === 'small' ? '9px' : '10px',
marginTop: 'auto',
paddingTop: '4px',
borderTop: '1px solid #f0f0f0'
}}
>
{sparepart.updated_at && dayjs(sparepart.updated_at).format('DD MMM YYYY')}
</Text>
</div>
</div>
</Card>
{/* Preview Modal */}
<Modal
title="Sparepart Details"
open={previewModalVisible}
onCancel={() => setPreviewModalVisible(false)}
footer={[
<Button key="close" onClick={() => setPreviewModalVisible(false)}>
Close
</Button>
]}
width={700}
centered
bodyStyle={{ padding: '24px' }}
>
<Row gutter={[24, 24]}>
<Col span={8}>
<div style={{ textAlign: 'center' }}>
<div
style={{
backgroundColor: '#f0f0f0',
width: '200px',
height: '200px',
margin: '0 auto 16px',
position: 'relative',
borderRadius: '8px',
overflow: 'hidden',
border: '1px solid #E0E0E0',
}}
>
<img
src={getImageSrc()}
alt={sparepart.sparepart_name || 'Sparepart'}
style={{
width: '100%',
height: '100%',
objectFit: 'cover'
}}
onError={(e) => {
e.target.src = 'https://via.placeholder.com/200x200/d9d9d9/666666?text=No+Image';
}}
/>
</div>
{sparepart.sparepart_item_type && (
<Tag color="blue" style={{ marginTop: '12px' }}>
{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 style={{ marginBottom: '16px' }}>
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
Stock:
</Text>
<Text style={{ fontSize: '16px', marginLeft: '8px' }}>
{sparepart.sparepart_stock || sparepart.sparepart_stok || '0'}
{sparepart.sparepart_unit ? ` ${sparepart.sparepart_unit}` : ' units'}
</Text>
</div>
<Row gutter={[16, 16]} style={{ marginBottom: '16px' }}>
{sparepart.sparepart_merk && (
<Col span={8}>
<div>
<Text strong style={{ fontSize: '14px', color: '#262626' }}>
Brand:
</Text>
<Text style={{ fontSize: '14px', marginLeft: '8px' }}>
{sparepart.sparepart_merk}
</Text>
</div>
</Col>
)}
{sparepart.sparepart_model && (
<Col span={8}>
<div>
<Text strong style={{ fontSize: '14px', color: '#262626' }}>
Model:
</Text>
<Text style={{ fontSize: '14px', marginLeft: '8px' }}>
{sparepart.sparepart_model}
</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>
</Col>
</Row>
</Modal>
</>
);
};
export default CustomSparepartCard;