Compare commits

...

2 Commits

5 changed files with 230 additions and 374 deletions

View File

@@ -640,6 +640,12 @@ const AddBrandDevice = () => {
}}> }}>
<Card <Card
title={ title={
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
}}>
<span style={{ <span style={{
fontSize: '16px', fontSize: '16px',
fontWeight: '600', fontWeight: '600',
@@ -656,6 +662,31 @@ const AddBrandDevice = () => {
}}></span> }}></span>
Error Code Form Error Code Form
</span> </span>
<Button
type="primary"
size="large"
onClick={handleSaveErrorCode}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
borderRadius: '8px',
height: '40px',
padding: '0 24px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(35, 165, 90, 0.2)',
transition: 'all 0.3s ease'
}}
onMouseEnter={(e) => {
e.target.style.boxShadow = '0 4px 8px rgba(35, 165, 90, 0.3)';
}}
onMouseLeave={(e) => {
e.target.style.boxShadow = '0 2px 4px rgba(35, 165, 90, 0.2)';
}}
>
{editingErrorCodeKey ? 'Update Error Code' : 'Save Error Code'}
</Button>
</div>
} }
style={{ style={{
width: '100%', width: '100%',
@@ -681,24 +712,6 @@ const AddBrandDevice = () => {
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)' boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
}}> }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '12px',
paddingBottom: '8px',
borderBottom: '1px solid #f5f5f5'
}}>
<div style={{
width: '3px',
height: '16px',
backgroundColor: '#23A55A',
borderRadius: '2px'
}}></div>
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
Error Code Details
</h4>
</div>
<ErrorCodeForm <ErrorCodeForm
errorCodeForm={errorCodeForm} errorCodeForm={errorCodeForm}
isErrorCodeFormReadOnly={isErrorCodeFormReadOnly} isErrorCodeFormReadOnly={isErrorCodeFormReadOnly}
@@ -838,34 +851,6 @@ const AddBrandDevice = () => {
Cancel Cancel
</Button> </Button>
)} )}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button
type="primary"
size="large"
onClick={handleSaveErrorCode}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
borderRadius: '8px',
height: '40px',
padding: '0 24px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(35, 165, 90, 0.2)',
transition: 'all 0.3s ease'
}}
onMouseEnter={(e) => {
e.target.style.boxShadow = '0 4px 8px rgba(35, 165, 90, 0.3)';
}}
onMouseLeave={(e) => {
e.target.style.boxShadow = '0 2px 4px rgba(35, 165, 90, 0.2)';
}}
>
{editingErrorCodeKey ? 'Update Error Code' : 'Simpan Error Code'}
</Button>
</div>
</div> </div>
</div> </div>
</Card> </Card>
@@ -1026,7 +1011,7 @@ const AddBrandDevice = () => {
borderColor: '#23A55A', borderColor: '#23A55A',
}} }}
> >
Lanjut Error Code
</Button> </Button>
)} )}
{currentStep === 1 && ( {currentStep === 1 && (
@@ -1039,7 +1024,7 @@ const AddBrandDevice = () => {
borderColor: '#23A55A', borderColor: '#23A55A',
}} }}
> >
Selesai Done
</Button> </Button>
)} )}
</div> </div>

View File

@@ -724,6 +724,12 @@ const EditBrandDevice = () => {
}}> }}>
<Card <Card
title={ title={
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
}}>
<span style={{ <span style={{
fontSize: '16px', fontSize: '16px',
fontWeight: '600', fontWeight: '600',
@@ -740,6 +746,31 @@ const EditBrandDevice = () => {
}}></span> }}></span>
Error Code Form Error Code Form
</span> </span>
<Button
type="primary"
size="large"
onClick={handleSaveErrorCode}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
borderRadius: '8px',
height: '40px',
padding: '0 24px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(35, 165, 90, 0.2)',
transition: 'all 0.3s ease'
}}
onMouseEnter={(e) => {
e.target.style.boxShadow = '0 4px 8px rgba(35, 165, 90, 0.3)';
}}
onMouseLeave={(e) => {
e.target.style.boxShadow = '0 2px 4px rgba(35, 165, 90, 0.2)';
}}
>
{editingErrorCodeKey ? 'Update Error Code' : 'Save Error Code'}
</Button>
</div>
} }
style={{ style={{
width: '100%', width: '100%',
@@ -765,24 +796,6 @@ const EditBrandDevice = () => {
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)' boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
}}> }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '12px',
paddingBottom: '8px',
borderBottom: '1px solid #f5f5f5'
}}>
<div style={{
width: '3px',
height: '16px',
backgroundColor: '#23A55A',
borderRadius: '2px'
}}></div>
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
Error Code Details
</h4>
</div>
<ErrorCodeForm <ErrorCodeForm
errorCodeForm={errorCodeForm} errorCodeForm={errorCodeForm}
isErrorCodeFormReadOnly={isErrorCodeFormReadOnly} isErrorCodeFormReadOnly={isErrorCodeFormReadOnly}
@@ -925,34 +938,6 @@ const EditBrandDevice = () => {
Cancel Cancel
</Button> </Button>
)} )}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button
type="primary"
size="large"
onClick={handleSaveErrorCode}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
borderRadius: '8px',
height: '40px',
padding: '0 24px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(35, 165, 90, 0.2)',
transition: 'all 0.3s ease'
}}
onMouseEnter={(e) => {
e.target.style.boxShadow = '0 4px 8px rgba(35, 165, 90, 0.3)';
}}
onMouseLeave={(e) => {
e.target.style.boxShadow = '0 2px 4px rgba(35, 165, 90, 0.2)';
}}
>
{editingErrorCodeKey ? 'Update Error Code' : 'Simpan Error Code'}
</Button>
</div>
</div> </div>
</div> </div>
</Card> </Card>
@@ -1016,7 +1001,7 @@ const EditBrandDevice = () => {
borderColor: '#23A55A', borderColor: '#23A55A',
}} }}
> >
Selesai Done
</Button> </Button>
)} )}
</div> </div>

View File

@@ -46,6 +46,11 @@ const CustomSparepartCard = ({
} }
}; };
const truncateText = (text, maxLength = 15) => {
if (!text) return 'Unnamed';
return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text;
};
const handleCardClick = () => { const handleCardClick = () => {
if (!isReadOnly && onCardClick) { if (!isReadOnly && onCardClick) {
onCardClick(sparepart); onCardClick(sparepart);
@@ -125,210 +130,88 @@ const CustomSparepartCard = ({
return ( return (
<> <>
<Card <div
hoverable={!!onCardClick && !isReadOnly} style={{
style={getCardStyle()} border: '1px solid #f0f0f0',
styles={{ borderRadius: '6px',
body: { padding: '12px 16px',
padding: 0, marginBottom: '8px',
height: 'calc(100% - 48px)', backgroundColor: 'white',
cursor: onCardClick && !isReadOnly ? 'pointer' : 'default',
transition: 'all 0.2s ease',
display: 'flex', display: 'flex',
flexDirection: 'column' alignItems: 'center',
} justifyContent: 'space-between'
}} }}
actions={getCardActions()}
onClick={handleCardClick} onClick={handleCardClick}
> >
<div style={{ display: 'flex', height: '100%' }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '4px' }}>
<div style={{ <Text
width: size === 'small' ? '90px' : '110px', strong
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={{ style={{
marginBottom: '8px', fontSize: '14px',
fontSize: '10px', color: '#262626',
fontWeight: 500 marginRight: '12px'
}} }}
title={sparepart.sparepart_name || sparepart.name || 'Unnamed'}
> >
{sparepart.sparepart_item_type} {truncateText(sparepart.sparepart_name || sparepart.name || 'Unnamed')}
</Tag> </Text>
)}
<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>
{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>
<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>
<div style={{ marginBottom: size === 'small' ? '6px' : '8px' }}>
<div style={{ marginBottom: '4px' }}>
<Tag <Tag
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'} color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
style={{ style={{ fontSize: '11px', margin: 0 }}
fontSize: size === 'small' ? '9px' : '10px',
padding: '0 4px',
margin: 0,
height: 'auto'
}}
> >
{sparepart.sparepart_stok || 'Not Available'} {sparepart.sparepart_stok || 'Not Available'}
</Tag> </Tag>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
<Text <Text style={{ fontSize: '12px', color: '#666', marginRight: '4px' }}>
style={{
fontSize: size === 'small' ? '10px' : '11px',
fontWeight: 500,
color: '#262626',
marginRight: '4px'
}}
>
qty: qty:
</Text> </Text>
<Text <Text
style={{ style={{
fontSize: size === 'small' ? '10px' : '11px', fontSize: '12px',
color: sparepart.sparepart_qty > 0 ? '#52c41a' : '#ff4d4f', fontWeight: 600,
fontWeight: 600 color: '#262626'
}} }}
> >
{sparepart.sparepart_qty || 0} {sparepart.sparepart_qty || 0}
{sparepart.sparepart_unit ? ` ${sparepart.sparepart_unit}` : ''}
</Text> </Text>
</div> </div>
</div> </div>
</div>
<div style={{ marginBottom: size === 'small' ? '4px' : '6px' }}> <Space size="small">
<Text {showPreview && (
code <Button
style={{ type="text"
fontSize: size === 'small' ? '10px' : '11px', icon={<EyeOutlined />}
backgroundColor: '#f5f5f5', size="small"
padding: '2px 6px', onClick={(e) => {
borderRadius: '3px' e.stopPropagation();
handlePreview();
}} }}
> title="Preview"
{sparepart.sparepart_code || 'No code'} />
</Text>
</div>
{(sparepart.sparepart_merk || sparepart.sparepart_model) && (
<div style={{
fontSize: size === 'small' ? '9px' : '10px',
color: '#666',
lineHeight: '1.4',
marginBottom: '4px'
}}>
{sparepart.sparepart_merk && (
<div>Brand: {sparepart.sparepart_merk}</div>
)} )}
{sparepart.sparepart_model && ( {showDelete && !isReadOnly && (
<div>Model: {sparepart.sparepart_model}</div> <Button
)} type="text"
</div> icon={<DeleteOutlined />}
)} size="small"
</div> danger
onClick={(e) => {
<Text e.stopPropagation();
type="secondary" onDelete?.(sparepart);
style={{
fontSize: size === 'small' ? '9px' : '10px',
marginTop: 'auto',
paddingTop: '4px',
borderTop: '1px solid #f0f0f0'
}} }}
> title="Remove"
{sparepart.updated_at && dayjs(sparepart.updated_at).format('DD MMM YYYY')} />
</Text> )}
</Space>
</div> </div>
</div>
</Card>
<Modal <Modal

View File

@@ -201,56 +201,19 @@ const ErrorCodeForm = ({
error_code_color: '#000000' error_code_color: '#000000'
}} }}
> >
<Form.Item {/* Header bar with color picker, icon upload, and status toggle */}
label="Status" <div style={{
name="status" display: 'flex',
valuePropName="checked" justifyContent: 'space-between',
> alignItems: 'flex-start',
<div style={{ display: 'flex', alignItems: 'center' }}> marginBottom: '16px',
<Form.Item name="status" valuePropName="checked" noStyle> gap: '16px'
<Switch }}>
disabled={isErrorCodeFormReadOnly} {/* Color picker on left */}
/> <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
</Form.Item>
<Text style={{ marginLeft: 8 }}>
{statusWatch ? 'Active' : 'Inactive'}
</Text>
</div>
</Form.Item>
<Form.Item
label="Error Code"
name="error_code"
rules={[{ required: true, message: 'Error code wajib diisi!' }]}
>
<Input
placeholder="Enter error code"
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>
<Form.Item
label="Error Name"
name="error_code_name"
rules={[{ required: !isErrorCodeFormReadOnly, message: 'Error name wajib diisi!' }]}
>
<Input placeholder="Enter error name" disabled={isErrorCodeFormReadOnly} />
</Form.Item>
<Form.Item label="Description" name="error_code_description">
<Input.TextArea
placeholder="Enter error description"
rows={3}
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>
<Form.Item label="Color & Icon">
<div style={{ display: 'flex', gap: '12px', alignItems: 'flex-start' }}>
<Form.Item <Form.Item
name="error_code_color" name="error_code_color"
noStyle noStyle
style={{ flex: '0 0 auto' }}
getValueFromEvent={(e) => e.target.value} getValueFromEvent={(e) => e.target.value}
getValueProps={(value) => ({ value: value || '#000000' })} getValueProps={(value) => ({ value: value || '#000000' })}
> >
@@ -267,11 +230,55 @@ const ErrorCodeForm = ({
/> />
</Form.Item> </Form.Item>
<Form.Item noStyle style={{ flex: '1 1 auto' }}> {/* Icon upload beside color picker */}
<div style={{ flex: 1, maxWidth: '300px' }}>
{renderIconUpload()} {renderIconUpload()}
</div>
</div>
{/* Status toggle on right */}
<div style={{ display: 'flex', alignItems: 'center' }}>
<Form.Item name="status" valuePropName="checked" noStyle>
<Switch
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>
<Text style={{ marginLeft: 8 }}>
{statusWatch ? 'Active' : 'Inactive'}
</Text>
</div>
</div>
{/* Error Code and Error Name in one row with 1/3 and 2/3 ratio */}
<div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
<Form.Item
label="Error Code"
name="error_code"
rules={[{ required: true, message: 'Error code wajib diisi!' }]}
style={{ flex: 1, marginBottom: 0, maxWidth: '33.33%' }}
>
<Input
placeholder="Enter error code"
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>
<Form.Item
label="Error Name"
name="error_code_name"
rules={[{ required: !isErrorCodeFormReadOnly, message: 'Error name wajib diisi!' }]}
style={{ flex: 2, marginBottom: 0, maxWidth: '66.67%' }}
>
<Input placeholder="Enter error name" disabled={isErrorCodeFormReadOnly} />
</Form.Item> </Form.Item>
</div> </div>
<Form.Item label="Description" name="error_code_description">
<Input.TextArea
placeholder="Enter error description"
rows={3}
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item> </Form.Item>
</Form> </Form>
</ConfigProvider> </ConfigProvider>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Select, Typography, Tag, Spin, Empty, Button, Row, Col } from 'antd'; import { Select, Typography, Tag, Spin, Empty, Button } from 'antd';
import { PlusOutlined, DeleteOutlined, CheckOutlined, EyeOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { PlusOutlined, DeleteOutlined, CheckOutlined, EyeOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { getAllSparepart } from '../../../../api/sparepart'; import { getAllSparepart } from '../../../../api/sparepart';
import CustomSparepartCard from './CustomSparepartCard'; import CustomSparepartCard from './CustomSparepartCard';
@@ -96,8 +96,8 @@ const SparepartSelect = ({
const isAlreadySelected = selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id); const isAlreadySelected = selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id);
return ( return (
<Col xs={24} sm={24} md={12} lg={12} key={sparepart.sparepart_id}>
<CustomSparepartCard <CustomSparepartCard
key={sparepart.sparepart_id}
sparepart={sparepart} sparepart={sparepart}
isSelected={isSelected} isSelected={isSelected}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
@@ -105,11 +105,7 @@ const SparepartSelect = ({
showDelete={isAlreadySelected && !isReadOnly} 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={{
border: isAlreadySelected ? '2px solid #52c41a' : undefined,
}}
/> />
</Col>
); );
}; };
@@ -163,9 +159,9 @@ const SparepartSelect = ({
<Title level={5} style={{ marginBottom: 16 }}> <Title level={5} style={{ marginBottom: 16 }}>
Selected Spareparts ({selectedSpareparts.length}) Selected Spareparts ({selectedSpareparts.length})
</Title> </Title>
<Row gutter={[16, 16]}> <div>
{selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))} {selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))}
</Row> </div>
</div> </div>
) : ( ) : (
<Empty <Empty