lavoce #27

Merged
bragaz_rexita merged 42 commits from lavoce into main 2025-12-22 09:28:35 +00:00
3 changed files with 113 additions and 35 deletions
Showing only changes of commit 7a5a9aafd1 - Show all commits

View File

@@ -43,9 +43,10 @@ const DetailSparepart = (props) => {
sparepart_description: '', sparepart_description: '',
sparepart_model: '', sparepart_model: '',
sparepart_item_type: null, sparepart_item_type: null,
sparepart_qty: 0,
sparepart_unit: '', sparepart_unit: '',
sparepart_merk: '', sparepart_merk: '',
sparepart_stok: '0', sparepart_stok: 'Not Available',
sparepart_foto: '', sparepart_foto: '',
}; };
@@ -224,11 +225,14 @@ const DetailSparepart = (props) => {
if (formData.sparepart_merk && formData.sparepart_merk.trim() !== '') { if (formData.sparepart_merk && formData.sparepart_merk.trim() !== '') {
payload.sparepart_merk = formData.sparepart_merk; payload.sparepart_merk = formData.sparepart_merk;
} }
if (formData.sparepart_stok && formData.sparepart_stok.trim() !== '') { // sparepart_qty disimpan sebagai angka kuantitas
payload.sparepart_stok = formData.sparepart_stok.toString(); // Untuk menghindari error validasi, jika qty 0, kita tetap kirim 1 ke backend tapi statusnya "Not Available"
} else { const qty = parseInt(formData.sparepart_qty) || 0;
payload.sparepart_stok = '0'; // Set default value jika tidak diisi 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 // Sertakan sparepart_foto hanya jika nilainya tidak kosong, agar tidak memicu validasi
if (imageUrl && imageUrl.trim() !== '') { if (imageUrl && imageUrl.trim() !== '') {
payload.sparepart_foto = imageUrl; payload.sparepart_foto = imageUrl;
@@ -496,12 +500,12 @@ const DetailSparepart = (props) => {
</Select> </Select>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Text strong>Stock</Text> <Text strong>Qty</Text>
<Input <Input
name="sparepart_stok" name="sparepart_qty"
value={formData.sparepart_stok} value={formData.sparepart_qty}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="Initial stock" placeholder="Enter quantity"
readOnly={props.readOnly} readOnly={props.readOnly}
type="number" type="number"
/> />
@@ -516,6 +520,20 @@ const DetailSparepart = (props) => {
readOnly={props.readOnly} readOnly={props.readOnly}
/> />
</Col> </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> </Row>
</Col> </Col>
</Row> </Row>

View File

@@ -72,11 +72,18 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
render: (sparepart_merk) => sparepart_merk || '-' 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', dataIndex: 'sparepart_stok',
key: 'sparepart_stok', key: 'sparepart_stok',
width: '8%', width: '8%',
render: (sparepart_stok) => sparepart_stok || '0' render: (sparepart_stok) => sparepart_stok || 'Not Available'
}, },
{ {
title: 'Action', title: 'Action',

View File

@@ -37,16 +37,22 @@ const SparepartCardList = ({
return; return;
} }
const newStock = Number(item.sparepart_stok) + quantityToAdd; const currentQty = Number(item.sparepart_qty) || 0;
if (newStock < 0) { const newQty = currentQty + quantityToAdd;
NotifAlert({ icon: 'error', title: 'Error', message: 'Stock cannot be negative.' }); if (newQty < 0) {
NotifAlert({ icon: 'error', title: 'Error', message: 'Quantity cannot be negative.' });
return; return;
} }
setLoadingQuantities((prev) => ({ ...prev, [item.sparepart_id]: true })); 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 = { 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 // 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() !== '') { if (item.sparepart_description && item.sparepart_description.trim() !== '') {
payload.sparepart_description = item.sparepart_description; 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 { try {
const response = await updateSparepart(item.sparepart_id, payload); const response = await updateSparepart(item.sparepart_id, payload);
@@ -73,6 +85,16 @@ const SparepartCardList = ({
title: 'Success', title: 'Success',
message: 'Stock updated successfully.', 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) { if (onStockUpdate) {
onStockUpdate(); onStockUpdate();
} }
@@ -139,7 +161,8 @@ const SparepartCardList = ({
style={{ style={{
backgroundColor: '#f0f0f0', backgroundColor: '#f0f0f0',
width: '100%', 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', position: 'relative',
borderRadius: '4px', borderRadius: '4px',
overflow: 'hidden', overflow: 'hidden',
@@ -153,30 +176,50 @@ const SparepartCardList = ({
imgSrc = item.sparepart_foto; imgSrc = item.sparepart_foto;
} else { } else {
// Gunakan format file URL seperti di brandDevice // 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 // Jika filename adalah default file, gunakan dari public assets
if (fileName === 'defaultSparepartImg.jpg') { if (
fileName === 'defaultSparepartImg.jpg'
) {
imgSrc = `/assets/defaultSparepartImg.jpg`; imgSrc = `/assets/defaultSparepartImg.jpg`;
} else { } else {
// Gunakan API getFileUrl untuk mendapatkan URL yang benar untuk file upload // Gunakan API getFileUrl untuk mendapatkan URL yang benar untuk file upload
const token = localStorage.getItem('token'); const token =
const baseURL = import.meta.env.VITE_API_SERVER || ''; localStorage.getItem('token');
imgSrc = `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(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 { } else {
imgSrc = 'https://via.placeholder.com/150'; imgSrc = 'https://via.placeholder.com/150';
} }
return ( return (
<div style={{ <div
position: 'absolute', style={{
top: 0, position: 'absolute',
left: 0, top: 0,
width: '100%', left: 0,
height: '100%', width: '100%',
}}> height: '100%',
}}
>
<img <img
src={imgSrc} src={imgSrc}
alt={item[header]} alt={item[header]}
@@ -186,10 +229,19 @@ const SparepartCardList = ({
objectFit: 'cover', // Mengisi container dan crop sisi berlebih objectFit: 'cover', // Mengisi container dan crop sisi berlebih
}} }}
onError={(e) => { onError={(e) => {
console.error('Image failed to load:', imgSrc); console.error(
e.target.src = 'https://via.placeholder.com/150'; '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> </div>
); );
@@ -249,8 +301,9 @@ const SparepartCardList = ({
> >
{item[header]} {item[header]}
</Title> </Title>
<Text type="secondary"> <Text type="secondary">Qty: {item.sparepart_qty || 0}</Text>
Available Stock: {item.sparepart_stok || '0'} <Text type="secondary" style={{ display: 'block' }}>
Stok: {item.sparepart_stok || 'Not Available'}
</Text> </Text>
<Divider style={{ margin: '8px 0' }} /> <Divider style={{ margin: '8px 0' }} />