lavoce #25
@@ -20,36 +20,65 @@ html body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom Orange Sidebar Menu Styles */
|
/* Custom green Sidebar Menu Styles */
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-item-selected {
|
.custom-green-menu.ant-menu-dark .ant-menu-item-selected {
|
||||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-item-selected::after {
|
.custom-green-menu.ant-menu-dark .ant-menu-item-selected::after {
|
||||||
border-right-color: white !important;
|
border-right-color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-item:hover,
|
.custom-green-menu.ant-menu-dark .ant-menu-item:hover,
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-submenu-title:hover {
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-title:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.15) !important;
|
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title {
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark.ant-menu-inline .ant-menu-sub {
|
.custom-green-menu.ant-menu-dark.ant-menu-inline .ant-menu-sub {
|
||||||
background: rgba(0, 0, 0, 0.2) !important;
|
background: rgba(0, 0, 0, 0.2) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-item,
|
.custom-green-menu.ant-menu-dark .ant-menu-item,
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-submenu-title {
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-title {
|
||||||
color: rgba(255, 255, 255, 0.9) !important;
|
color: rgba(255, 255, 255, 0.9) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-item-active,
|
.custom-green-menu.ant-menu-dark .ant-menu-item-active,
|
||||||
.custom-orange-menu.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title {
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*start styling for scrollbar menu */
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: linear-gradient(180deg, #1BAA56 0%, rgb(5, 75, 34) 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: linear-gradient(180deg, #2bc56d 0%, rgb(8, 94, 43) 100%);
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #1BAA56 transparent;
|
||||||
|
}
|
||||||
|
/* Hilangkan panah atas/bawah */
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-button {
|
||||||
|
display: none !important;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
/*end styling for scrollbar menu */
|
||||||
@@ -422,7 +422,7 @@ const LayoutMenu = () => {
|
|||||||
border: 'none',
|
border: 'none',
|
||||||
}}
|
}}
|
||||||
theme="dark"
|
theme="dark"
|
||||||
className="custom-orange-menu"
|
className="custom-green-menu"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,8 +30,24 @@ const LayoutSidebar = () => {
|
|||||||
zIndex: 9999
|
zIndex: 9999
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{/* Logo section - fixed height */}
|
||||||
|
<div style={{flexShrink: 0,minHeight: '64px'}}>
|
||||||
<LayoutLogo />
|
<LayoutLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Menu section - scrollable */}
|
||||||
|
<div style={{flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<div className="custom-menu-scrollbar" style={{flex: 1, overflowY: 'auto', overflowX: 'hidden', backgroundColor: 'transparent'}}>
|
||||||
<LayoutMenu />
|
<LayoutMenu />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Sider>
|
</Sider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const AddBrandDevice = () => {
|
|||||||
const [formData, setFormData] = useState(defaultData);
|
const [formData, setFormData] = useState(defaultData);
|
||||||
const [errorCodes, setErrorCodes] = useState([]);
|
const [errorCodes, setErrorCodes] = useState([]);
|
||||||
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
||||||
|
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
solutionFields,
|
solutionFields,
|
||||||
@@ -73,19 +74,33 @@ const AddBrandDevice = () => {
|
|||||||
setSolutionsForExistingRecord,
|
setSolutionsForExistingRecord,
|
||||||
} = useSolutionLogic(solutionForm);
|
} = useSolutionLogic(solutionForm);
|
||||||
|
|
||||||
const {
|
// For spareparts, we'll use the local state directly since it's just an array of IDs
|
||||||
sparepartFields,
|
const handleSparepartChange = (values) => {
|
||||||
sparepartTypes,
|
setSelectedSparepartIds(values || []);
|
||||||
sparepartStatuses,
|
};
|
||||||
sparepartsToDelete,
|
|
||||||
handleAddSparepartField,
|
const resetSparepartFields = () => {
|
||||||
handleRemoveSparepartField,
|
setSelectedSparepartIds([]);
|
||||||
handleSparepartTypeChange,
|
};
|
||||||
handleSparepartStatusChange,
|
|
||||||
resetSparepartFields,
|
const getSparepartData = () => {
|
||||||
getSparepartData,
|
return selectedSparepartIds;
|
||||||
setSparepartsForExistingRecord,
|
};
|
||||||
} = useSparepartLogic(sparepartForm);
|
|
||||||
|
const setSparepartsForExistingRecord = (sparepartData) => {
|
||||||
|
if (!sparepartData) {
|
||||||
|
setSelectedSparepartIds([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(sparepartData)) {
|
||||||
|
setSelectedSparepartIds(sparepartData);
|
||||||
|
} else if (typeof sparepartData === 'object' && sparepartData.spareparts) {
|
||||||
|
setSelectedSparepartIds(sparepartData.spareparts || []);
|
||||||
|
} else {
|
||||||
|
setSelectedSparepartIds(sparepartData.map(sp => sp.sparepart_id || sp.brand_sparepart_id || sp.id).filter(id => id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBreadcrumbItems([
|
setBreadcrumbItems([
|
||||||
@@ -155,22 +170,16 @@ const AddBrandDevice = () => {
|
|||||||
path_solution: sol.path_solution || '',
|
path_solution: sol.path_solution || '',
|
||||||
is_active: sol.is_active !== false,
|
is_active: sol.is_active !== false,
|
||||||
})),
|
})),
|
||||||
...(ec.sparepart && ec.sparepart.length > 0 && {
|
|
||||||
sparepart: ec.sparepart.map((sp) => ({
|
|
||||||
sparepart_name: sp.sparepart_name || sp.name || sp.label || '',
|
|
||||||
brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.sparepart_description || '',
|
|
||||||
is_active: sp.is_active !== false,
|
|
||||||
path_foto: sp.path_foto || '',
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const sparepartData = getSparepartData();
|
||||||
const finalFormData = {
|
const finalFormData = {
|
||||||
brand_name: formData.brand_name,
|
brand_name: formData.brand_name,
|
||||||
brand_type: formData.brand_type || '',
|
brand_type: formData.brand_type || '',
|
||||||
brand_model: formData.brand_model || '',
|
brand_model: formData.brand_model || '',
|
||||||
brand_manufacture: formData.brand_manufacture,
|
brand_manufacture: formData.brand_manufacture,
|
||||||
is_active: formData.is_active,
|
is_active: formData.is_active,
|
||||||
|
spareparts: sparepartData,
|
||||||
error_code: transformedErrorCodes,
|
error_code: transformedErrorCodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,7 +231,7 @@ const AddBrandDevice = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (record.sparepart && record.sparepart.length > 0) {
|
if (record.sparepart && record.sparepart.length > 0) {
|
||||||
setSparepartsForExistingRecord(record.sparepart, sparepartForm);
|
setSparepartsForExistingRecord(record.sparepart);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,6 +263,10 @@ const AddBrandDevice = () => {
|
|||||||
} else {
|
} else {
|
||||||
resetSolutionFields();
|
resetSolutionFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (record.sparepart && record.sparepart.length > 0) {
|
||||||
|
setSparepartsForExistingRecord(record.sparepart);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddErrorCode = async () => {
|
const handleAddErrorCode = async () => {
|
||||||
@@ -282,7 +295,6 @@ const AddBrandDevice = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sparepartData = getSparepartData();
|
|
||||||
const newErrorCode = {
|
const newErrorCode = {
|
||||||
key: Date.now(),
|
key: Date.now(),
|
||||||
error_code: formValues.error_code,
|
error_code: formValues.error_code,
|
||||||
@@ -293,7 +305,6 @@ const AddBrandDevice = () => {
|
|||||||
status: formValues.status !== false,
|
status: formValues.status !== false,
|
||||||
errorCodeIcon: errorCodeIcon,
|
errorCodeIcon: errorCodeIcon,
|
||||||
solution: solutions,
|
solution: solutions,
|
||||||
...(sparepartData && sparepartData.length > 0 && { sparepart: sparepartData }), // Only add sparepart if there are spareparts
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (editingErrorCodeKey) {
|
if (editingErrorCodeKey) {
|
||||||
@@ -516,22 +527,16 @@ const AddBrandDevice = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{/* Sparepart Form Column */}
|
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Card size="small" title="Spareparts">
|
<Card size="small" title="Spareparts">
|
||||||
<Form
|
<Form
|
||||||
form={sparepartForm}
|
form={sparepartForm}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={{
|
|
||||||
sparepart_status_0: true,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SparepartForm
|
<SparepartForm
|
||||||
sparepartForm={sparepartForm}
|
sparepartForm={sparepartForm}
|
||||||
sparepartFields={sparepartFields}
|
selectedSparepartIds={selectedSparepartIds}
|
||||||
onAddSparepartField={handleAddSparepartField}
|
onSparepartChange={handleSparepartChange}
|
||||||
onRemoveSparepartField={handleRemoveSparepartField}
|
|
||||||
isReadOnly={isErrorCodeFormReadOnly}
|
isReadOnly={isErrorCodeFormReadOnly}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const EditBrandDevice = () => {
|
|||||||
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
||||||
const [solutionForm] = Form.useForm();
|
const [solutionForm] = Form.useForm();
|
||||||
const [sparepartForm] = Form.useForm();
|
const [sparepartForm] = Form.useForm();
|
||||||
|
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
|
||||||
|
|
||||||
const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic(
|
const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic(
|
||||||
errorCodeForm,
|
errorCodeForm,
|
||||||
@@ -80,19 +81,33 @@ const EditBrandDevice = () => {
|
|||||||
setSolutionsForExistingRecord,
|
setSolutionsForExistingRecord,
|
||||||
} = useSolutionLogic(solutionForm);
|
} = useSolutionLogic(solutionForm);
|
||||||
|
|
||||||
const {
|
// For spareparts, we'll use the local state directly since it's just an array of IDs
|
||||||
sparepartFields,
|
const handleSparepartChange = (values) => {
|
||||||
sparepartTypes,
|
setSelectedSparepartIds(values || []);
|
||||||
sparepartStatuses,
|
};
|
||||||
sparepartsToDelete,
|
|
||||||
handleAddSparepartField,
|
const resetSparepartFields = () => {
|
||||||
handleRemoveSparepartField,
|
setSelectedSparepartIds([]);
|
||||||
handleSparepartTypeChange,
|
};
|
||||||
handleSparepartStatusChange,
|
|
||||||
resetSparepartFields,
|
const getSparepartData = () => {
|
||||||
getSparepartData,
|
return selectedSparepartIds;
|
||||||
setSparepartsForExistingRecord,
|
};
|
||||||
} = useSparepartLogic(sparepartForm);
|
|
||||||
|
const setSparepartsForExistingRecord = (sparepartData) => {
|
||||||
|
if (!sparepartData) {
|
||||||
|
setSelectedSparepartIds([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(sparepartData)) {
|
||||||
|
setSelectedSparepartIds(sparepartData);
|
||||||
|
} else if (typeof sparepartData === 'object' && sparepartData.spareparts) {
|
||||||
|
setSelectedSparepartIds(sparepartData.spareparts || []);
|
||||||
|
} else {
|
||||||
|
setSelectedSparepartIds(sparepartData.map(sp => sp.sparepart_id || sp.brand_sparepart_id || sp.id).filter(id => id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchBrandData = async () => {
|
const fetchBrandData = async () => {
|
||||||
@@ -176,6 +191,14 @@ const EditBrandDevice = () => {
|
|||||||
setFormData(newFormData);
|
setFormData(newFormData);
|
||||||
brandForm.setFieldsValue(newFormData);
|
brandForm.setFieldsValue(newFormData);
|
||||||
setErrorCodes(existingErrorCodes);
|
setErrorCodes(existingErrorCodes);
|
||||||
|
|
||||||
|
// Set the selected sparepart IDs if available in the response
|
||||||
|
if (response.data.spareparts) {
|
||||||
|
// Extract the IDs from the spareparts objects
|
||||||
|
const sparepartIds = response.data.spareparts.map(sp => sp.sparepart_id);
|
||||||
|
setSelectedSparepartIds(sparepartIds);
|
||||||
|
setSparepartsForExistingRecord(sparepartIds);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
@@ -244,12 +267,6 @@ const EditBrandDevice = () => {
|
|||||||
path_solution: sol.path_solution || '',
|
path_solution: sol.path_solution || '',
|
||||||
is_active: sol.is_active !== false,
|
is_active: sol.is_active !== false,
|
||||||
})),
|
})),
|
||||||
sparepart: (ec.sparepart || []).map((sp) => ({
|
|
||||||
sparepart_name: sp.sparepart_name || sp.name || sp.label || '',
|
|
||||||
brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.brand_sparepart_description || '',
|
|
||||||
is_active: sp.is_active !== false,
|
|
||||||
path_foto: sp.path_foto || '',
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,19 +285,17 @@ const EditBrandDevice = () => {
|
|||||||
path_solution: sol.path_solution || '',
|
path_solution: sol.path_solution || '',
|
||||||
is_active: sol.is_active !== false,
|
is_active: sol.is_active !== false,
|
||||||
})),
|
})),
|
||||||
...(ec.sparepart && ec.sparepart.length > 0 && {
|
|
||||||
sparepart: ec.sparepart.map((sp) => ({
|
|
||||||
sparepart_name: sp.sparepart_name || sp.name || sp.label || '',
|
|
||||||
brand_sparepart_description: sp.brand_sparepart_description || sp.description || sp.brand_sparepart_description || '',
|
|
||||||
is_active: sp.is_active !== false,
|
|
||||||
path_foto: sp.path_foto || '',
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await updateBrand(id, finalFormData);
|
const sparepartData = getSparepartData();
|
||||||
|
const updatedFinalFormData = {
|
||||||
|
...finalFormData,
|
||||||
|
spareparts: sparepartData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await updateBrand(id, updatedFinalFormData);
|
||||||
|
|
||||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
localStorage.removeItem(`brand_device_edit_${id}_temp_data`);
|
localStorage.removeItem(`brand_device_edit_${id}_temp_data`);
|
||||||
@@ -329,7 +344,7 @@ const EditBrandDevice = () => {
|
|||||||
|
|
||||||
// Load spareparts to sparepart form
|
// Load spareparts to sparepart form
|
||||||
if (record.sparepart && record.sparepart.length > 0) {
|
if (record.sparepart && record.sparepart.length > 0) {
|
||||||
setSparepartsForExistingRecord(record.sparepart, sparepartForm);
|
setSparepartsForExistingRecord(record.sparepart);
|
||||||
} else {
|
} else {
|
||||||
resetSparepartFields();
|
resetSparepartFields();
|
||||||
}
|
}
|
||||||
@@ -354,7 +369,7 @@ const EditBrandDevice = () => {
|
|||||||
|
|
||||||
// Load spareparts to sparepart form
|
// Load spareparts to sparepart form
|
||||||
if (record.sparepart && record.sparepart.length > 0) {
|
if (record.sparepart && record.sparepart.length > 0) {
|
||||||
setSparepartsForExistingRecord(record.sparepart, sparepartForm);
|
setSparepartsForExistingRecord(record.sparepart);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formElement = document.querySelector('.ant-form');
|
const formElement = document.querySelector('.ant-form');
|
||||||
@@ -624,15 +639,11 @@ const EditBrandDevice = () => {
|
|||||||
<Form
|
<Form
|
||||||
form={sparepartForm}
|
form={sparepartForm}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={{
|
|
||||||
sparepart_status_0: true,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SparepartForm
|
<SparepartForm
|
||||||
sparepartForm={sparepartForm}
|
sparepartForm={sparepartForm}
|
||||||
sparepartFields={sparepartFields}
|
selectedSparepartIds={selectedSparepartIds}
|
||||||
onAddSparepartField={handleAddSparepartField}
|
onSparepartChange={handleSparepartChange}
|
||||||
onRemoveSparepartField={handleRemoveSparepartField}
|
|
||||||
isReadOnly={isErrorCodeFormReadOnly}
|
isReadOnly={isErrorCodeFormReadOnly}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
310
src/pages/master/brandDevice/component/SparepartCardSelect.jsx
Normal file
310
src/pages/master/brandDevice/component/SparepartCardSelect.jsx
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Card, Row, Col, Image, Typography, Tag, Space, Spin, Button, Empty } from 'antd';
|
||||||
|
import { CheckCircleOutlined, CloseCircleOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { getAllSparepart } from '../../../../api/sparepart';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
const SparepartCardSelect = ({
|
||||||
|
selectedSparepartIds = [],
|
||||||
|
onSparepartChange,
|
||||||
|
isLoading: externalLoading = false,
|
||||||
|
isReadOnly = false
|
||||||
|
}) => {
|
||||||
|
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) {
|
||||||
|
console.error('Error loading spareparts:', 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 = (sparepartId) => {
|
||||||
|
if (isReadOnly) return;
|
||||||
|
|
||||||
|
const newSelectedIds = selectedSparepartIds.includes(sparepartId)
|
||||||
|
? 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 span={8} key={sparepart.sparepart_id}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
hoverable
|
||||||
|
style={{
|
||||||
|
border: isSelected(sparepart.sparepart_id)
|
||||||
|
? '2px solid #23A55A'
|
||||||
|
: '1px solid #d9d9d9',
|
||||||
|
backgroundColor: isSelected(sparepart.sparepart_id)
|
||||||
|
? '#f6ffed'
|
||||||
|
: 'white',
|
||||||
|
cursor: isReadOnly ? 'default' : 'pointer',
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
onClick={() => handleSparepartToggle(sparepart.sparepart_id)}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'absolute', top: 8, right: 8 }}>
|
||||||
|
{isSelected(sparepart.sparepart_id) ? (
|
||||||
|
<CheckCircleOutlined
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#23A55A',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '50%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleOutlined
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#d9d9d9',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '50%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'center', marginBottom: 12 }}>
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 120,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
borderRadius: 8,
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{sparepart.sparepart_foto ? (
|
||||||
|
<Image
|
||||||
|
src={sparepart.sparepart_foto}
|
||||||
|
alt={sparepart.sparepart_name}
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
preview={false}
|
||||||
|
fallback="/assets/defaultSparepartImg.jpg"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
color: '#bfbfbf',
|
||||||
|
fontSize: 12
|
||||||
|
}}>
|
||||||
|
No Image
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginBottom: 4,
|
||||||
|
color: isSelected(sparepart.sparepart_id) ? '#23A55A' : 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sparepart.sparepart_name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
type="secondary"
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sparepart.sparepart_description || 'No description'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Space size="small" style={{ marginBottom: 4 }}>
|
||||||
|
<Tag color="blue" style={{ margin: 0 }}>
|
||||||
|
{sparepart.sparepart_code}
|
||||||
|
</Tag>
|
||||||
|
<Tag color="geekblue" style={{ margin: 0 }}>
|
||||||
|
{sparepart.sparepart_merk || 'N/A'}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{sparepart.sparepart_model && (
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#666'
|
||||||
|
}}>
|
||||||
|
Model: {sparepart.sparepart_model}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</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;
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Form, Select, Button, Switch, Typography, Space, Input, message } from 'antd';
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
|
||||||
import { getAllSparepart } from '../../../../api/sparepart';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const SparepartField = ({
|
|
||||||
fieldKey,
|
|
||||||
fieldName,
|
|
||||||
index,
|
|
||||||
sparepartType,
|
|
||||||
sparepartStatus,
|
|
||||||
isReadOnly = false,
|
|
||||||
canRemove = true,
|
|
||||||
onRemove,
|
|
||||||
spareparts = [],
|
|
||||||
onSparepartChange
|
|
||||||
}) => {
|
|
||||||
const [currentStatus, setCurrentStatus] = useState(sparepartStatus ?? true);
|
|
||||||
const [sparepartList, setSparepartList] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentStatus(sparepartStatus ?? true);
|
|
||||||
loadSpareparts();
|
|
||||||
}, [sparepartStatus]);
|
|
||||||
|
|
||||||
const loadSpareparts = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
// Get all spareparts from the API
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.set('limit', '100'); // Get all spareparts
|
|
||||||
|
|
||||||
const response = await getAllSparepart(params);
|
|
||||||
// Response structure should have { data: [...], statusCode: 200 }
|
|
||||||
if (response && (response.statusCode === 200 || response.data)) {
|
|
||||||
// If response has data array directly
|
|
||||||
const sparepartData = response.data?.data || response.data || [];
|
|
||||||
setSparepartList(sparepartData);
|
|
||||||
if (onSparepartChange) {
|
|
||||||
onSparepartChange(sparepartData);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For demo purposes, use mock data if API fails
|
|
||||||
setSparepartList([
|
|
||||||
{ brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' },
|
|
||||||
{ brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' },
|
|
||||||
{ brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' },
|
|
||||||
]);
|
|
||||||
if (onSparepartChange) {
|
|
||||||
onSparepartChange([
|
|
||||||
{ brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' },
|
|
||||||
{ brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' },
|
|
||||||
{ brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading spareparts:', error);
|
|
||||||
// Default mock data
|
|
||||||
const mockSpareparts = [
|
|
||||||
{ brand_sparepart_id: 1, sparepart_name: 'Compressor Oil Filter', brand_sparepart_description: 'Oil filter for compressor' },
|
|
||||||
{ brand_sparepart_id: 2, sparepart_name: 'Air Intake Filter', brand_sparepart_description: 'Air intake filter' },
|
|
||||||
{ brand_sparepart_id: 3, sparepart_name: 'Cooling Fan Motor', brand_sparepart_description: 'Motor for cooling fan' },
|
|
||||||
];
|
|
||||||
setSparepartList(mockSpareparts);
|
|
||||||
if (onSparepartChange) {
|
|
||||||
onSparepartChange(mockSpareparts);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sparepartOptions = sparepartList.map(sparepart => ({
|
|
||||||
label: sparepart.sparepart_name || sparepart.sparepart_name || `Sparepart ${sparepart.sparepart_id || sparepart.brand_sparepart_id}`,
|
|
||||||
value: sparepart.sparepart_id || sparepart.brand_sparepart_id,
|
|
||||||
description: sparepart.sparepart_description
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 16,
|
|
||||||
marginBottom: 16,
|
|
||||||
backgroundColor: isReadOnly ? '#f5f5f5' : 'white'
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
|
||||||
<Text strong>Sparepart #{index + 1}</Text>
|
|
||||||
<Space>
|
|
||||||
<Form.Item
|
|
||||||
name={[fieldName, 'sparepart_id']}
|
|
||||||
rules={[{ required: false, message: 'Sparepart wajib dipilih!' }]} /* Making it optional since sparepart is optional */
|
|
||||||
style={{ margin: 0, width: 200 }}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="Pilih sparepart"
|
|
||||||
loading={loading}
|
|
||||||
disabled={isReadOnly}
|
|
||||||
options={sparepartOptions}
|
|
||||||
showSearch
|
|
||||||
optionFilterProp="label"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
||||||
<Form.Item name={[fieldName, 'status']} valuePropName="checked" noStyle>
|
|
||||||
<Switch
|
|
||||||
disabled={isReadOnly}
|
|
||||||
onChange={(checked) => {
|
|
||||||
setCurrentStatus(checked);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundColor: currentStatus ? '#23A55A' : '#bfbfbf'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Text style={{ fontSize: 12, color: '#666' }}>
|
|
||||||
{currentStatus ? 'Active' : 'Inactive'}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{canRemove && !isReadOnly && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={onRemove}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sparepart Description */}
|
|
||||||
<Form.Item
|
|
||||||
name={[fieldName, 'description']}
|
|
||||||
label="Deskripsi"
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="Deskripsi sparepart"
|
|
||||||
rows={2}
|
|
||||||
disabled={isReadOnly}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SparepartField;
|
|
||||||
@@ -1,74 +1,24 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { Form, Card, Typography, Divider, Button } from 'antd';
|
import { Card, Divider, Typography } from 'antd';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import SparepartCardSelect from './SparepartCardSelect';
|
||||||
import SparepartField from './SparepartField';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const SparepartForm = ({
|
const SparepartForm = ({
|
||||||
sparepartForm,
|
sparepartForm,
|
||||||
sparepartFields,
|
selectedSparepartIds,
|
||||||
onAddSparepartField,
|
onSparepartChange,
|
||||||
onRemoveSparepartField,
|
isReadOnly = false
|
||||||
isReadOnly = false,
|
|
||||||
spareparts = [],
|
|
||||||
onSparepartChange
|
|
||||||
}) => {
|
}) => {
|
||||||
const [sparepartList, setSparepartList] = useState([]);
|
|
||||||
|
|
||||||
const handleSparepartChange = (list) => {
|
|
||||||
setSparepartList(list);
|
|
||||||
if (onSparepartChange) {
|
|
||||||
onSparepartChange(list);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form
|
<Card size="small" title="Spareparts">
|
||||||
form={sparepartForm}
|
<SparepartCardSelect
|
||||||
layout="vertical"
|
selectedSparepartIds={selectedSparepartIds}
|
||||||
initialValues={{
|
onSparepartChange={onSparepartChange}
|
||||||
sparepart_status_0: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Divider orientation="left">Sparepart Items</Divider>
|
|
||||||
|
|
||||||
{sparepartFields.map((field, index) => (
|
|
||||||
<SparepartField
|
|
||||||
key={field.key}
|
|
||||||
fieldKey={field.key}
|
|
||||||
fieldName={field.name}
|
|
||||||
index={index}
|
|
||||||
sparepartStatus={field.status}
|
|
||||||
onRemove={() => onRemoveSparepartField(field.key)}
|
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
canRemove={sparepartFields.length > 1}
|
|
||||||
spareparts={sparepartList}
|
|
||||||
onSparepartChange={handleSparepartChange}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</Card>
|
||||||
|
|
||||||
{!isReadOnly && (
|
|
||||||
<>
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={onAddSparepartField}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
+ Add Sparepart
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
<Text type="secondary">
|
|
||||||
* Sparepart is optional and can be added for each error code if needed.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user