feat: update sparepart quantity and stock status handling in DetailSparepart and SparepartCardList components

This commit is contained in:
2025-12-03 13:26:54 +07:00
parent 0694497f8d
commit 7a5a9aafd1
3 changed files with 113 additions and 35 deletions

View File

@@ -43,9 +43,10 @@ const DetailSparepart = (props) => {
sparepart_description: '',
sparepart_model: '',
sparepart_item_type: null,
sparepart_qty: 0,
sparepart_unit: '',
sparepart_merk: '',
sparepart_stok: '0',
sparepart_stok: 'Not Available',
sparepart_foto: '',
};
@@ -224,11 +225,14 @@ const DetailSparepart = (props) => {
if (formData.sparepart_merk && formData.sparepart_merk.trim() !== '') {
payload.sparepart_merk = formData.sparepart_merk;
}
if (formData.sparepart_stok && formData.sparepart_stok.trim() !== '') {
payload.sparepart_stok = formData.sparepart_stok.toString();
} else {
payload.sparepart_stok = '0'; // Set default value jika tidak diisi
}
// sparepart_qty disimpan sebagai angka kuantitas
// Untuk menghindari error validasi, jika qty 0, kita tetap kirim 1 ke backend tapi statusnya "Not Available"
const qty = parseInt(formData.sparepart_qty) || 0;
const actualQty = qty > 0 ? qty : 1; // Kirim minimal 1 ke backend
payload.sparepart_qty = actualQty;
// sparepart_stok ditentukan otomatis berdasarkan qty sebenarnya
payload.sparepart_stok = qty > 0 ? 'Available' : 'Not Available';
// Sertakan sparepart_foto hanya jika nilainya tidak kosong, agar tidak memicu validasi
if (imageUrl && imageUrl.trim() !== '') {
payload.sparepart_foto = imageUrl;
@@ -496,12 +500,12 @@ const DetailSparepart = (props) => {
</Select>
</Col>
<Col span={12}>
<Text strong>Stock</Text>
<Text strong>Qty</Text>
<Input
name="sparepart_stok"
value={formData.sparepart_stok}
name="sparepart_qty"
value={formData.sparepart_qty}
onChange={handleInputChange}
placeholder="Initial stock"
placeholder="Enter quantity"
readOnly={props.readOnly}
type="number"
/>
@@ -516,6 +520,20 @@ const DetailSparepart = (props) => {
readOnly={props.readOnly}
/>
</Col>
<Col span={12}>
<Text strong>Status</Text>
<Input
name="sparepart_stok"
value={parseInt(formData.sparepart_qty) > 0 ? 'Available' : 'Not Available'}
readOnly={true}
placeholder="Auto calculated"
style={{
backgroundColor: '#f5f5f5',
cursor: 'not-allowed',
color: parseInt(formData.sparepart_qty) > 0 ? '#52c41a' : '#ff4d4f'
}}
/>
</Col>
</Row>
</Col>
</Row>

View File

@@ -72,11 +72,18 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
render: (sparepart_merk) => sparepart_merk || '-'
},
{
title: 'Stock',
title: 'Qty',
dataIndex: 'sparepart_qty',
key: 'sparepart_qty',
width: '8%',
render: (sparepart_qty) => sparepart_qty || '0'
},
{
title: 'Status',
dataIndex: 'sparepart_stok',
key: 'sparepart_stok',
width: '8%',
render: (sparepart_stok) => sparepart_stok || '0'
render: (sparepart_stok) => sparepart_stok || 'Not Available'
},
{
title: 'Action',

View File

@@ -37,16 +37,22 @@ const SparepartCardList = ({
return;
}
const newStock = Number(item.sparepart_stok) + quantityToAdd;
if (newStock < 0) {
NotifAlert({ icon: 'error', title: 'Error', message: 'Stock cannot be negative.' });
const currentQty = Number(item.sparepart_qty) || 0;
const newQty = currentQty + quantityToAdd;
if (newQty < 0) {
NotifAlert({ icon: 'error', title: 'Error', message: 'Quantity cannot be negative.' });
return;
}
setLoadingQuantities((prev) => ({ ...prev, [item.sparepart_id]: true }));
// sparepart_qty disimpan sebagai angka kuantitas
// sparepart_stok ditentukan otomatis berdasarkan qty
// Untuk menghindari error validasi, jika newQty 0, kita tetap kirim 1 ke backend tapi statusnya "Not Available"
const actualQty = newQty > 0 ? newQty : 1; // Kirim minimal 1 ke backend
const payload = {
sparepart_stok: newStock.toString(), // Convert number to string as required by API
sparepart_qty: actualQty,
sparepart_stok: newQty > 0 ? 'Available' : 'Not Available', // Otomatis tentukan status berdasarkan newQty asli
};
// Hanya tambahkan field jika nilainya tidak kosong untuk menghindari validasi error
@@ -62,6 +68,12 @@ const SparepartCardList = ({
if (item.sparepart_description && item.sparepart_description.trim() !== '') {
payload.sparepart_description = item.sparepart_description;
}
if (item.sparepart_item_type && item.sparepart_item_type !== null) {
payload.sparepart_item_type = item.sparepart_item_type;
}
if (item.sparepart_foto && item.sparepart_foto.trim() !== '') {
payload.sparepart_foto = item.sparepart_foto;
}
try {
const response = await updateSparepart(item.sparepart_id, payload);
@@ -73,6 +85,16 @@ const SparepartCardList = ({
title: 'Success',
message: 'Stock updated successfully.',
});
// Cek apakah qty baru kurang dari 1, tampilkan alert
if (newQty < 1) {
NotifAlert({
icon: 'warning',
title: 'Low Stock',
message: `Warning: Sparepart "${item.sparepart_name}" is out of stock. Please restock immediately.`,
});
}
if (onStockUpdate) {
onStockUpdate();
}
@@ -139,7 +161,8 @@ const SparepartCardList = ({
style={{
backgroundColor: '#f0f0f0',
width: '100%',
paddingTop: '100%', /* Ini membuat tinggi sama dengan lebar (aspect ratio 1:1) */
paddingTop:
'100%' /* Ini membuat tinggi sama dengan lebar (aspect ratio 1:1) */,
position: 'relative',
borderRadius: '4px',
overflow: 'hidden',
@@ -153,30 +176,50 @@ const SparepartCardList = ({
imgSrc = item.sparepart_foto;
} else {
// Gunakan format file URL seperti di brandDevice
const fileName = item.sparepart_foto.split('/').pop();
const fileName = item.sparepart_foto
.split('/')
.pop();
// Jika filename adalah default file, gunakan dari public assets
if (fileName === 'defaultSparepartImg.jpg') {
if (
fileName === 'defaultSparepartImg.jpg'
) {
imgSrc = `/assets/defaultSparepartImg.jpg`;
} else {
// Gunakan API getFileUrl untuk mendapatkan URL yang benar untuk file upload
const token = localStorage.getItem('token');
const baseURL = import.meta.env.VITE_API_SERVER || '';
imgSrc = `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`;
const token =
localStorage.getItem('token');
const baseURL =
import.meta.env.VITE_API_SERVER ||
'';
imgSrc = `${baseURL}/file-uploads/images/${encodeURIComponent(
fileName
)}${
token
? `?token=${encodeURIComponent(
token
)}`
: ''
}`;
}
}
console.log('Image path being constructed:', imgSrc);
console.log(
'Image path being constructed:',
imgSrc
);
} else {
imgSrc = 'https://via.placeholder.com/150';
}
return (
<div style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}>
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}
>
<img
src={imgSrc}
alt={item[header]}
@@ -186,10 +229,19 @@ const SparepartCardList = ({
objectFit: 'cover', // Mengisi container dan crop sisi berlebih
}}
onError={(e) => {
console.error('Image failed to load:', imgSrc);
e.target.src = 'https://via.placeholder.com/150';
console.error(
'Image failed to load:',
imgSrc
);
e.target.src =
'https://via.placeholder.com/150';
}}
onLoad={() => console.log('Image loaded successfully:', imgSrc)}
onLoad={() =>
console.log(
'Image loaded successfully:',
imgSrc
)
}
/>
</div>
);
@@ -249,8 +301,9 @@ const SparepartCardList = ({
>
{item[header]}
</Title>
<Text type="secondary">
Available Stock: {item.sparepart_stok || '0'}
<Text type="secondary">Qty: {item.sparepart_qty || 0}</Text>
<Text type="secondary" style={{ display: 'block' }}>
Stok: {item.sparepart_stok || 'Not Available'}
</Text>
<Divider style={{ margin: '8px 0' }} />