lavoce #27
@@ -21,7 +21,6 @@ import IndexShift from './pages/master/shift/IndexShift';
|
|||||||
// Brand device
|
// Brand device
|
||||||
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
|
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
|
||||||
import EditBrandDevice from './pages/master/brandDevice/EditBrandDevice';
|
import EditBrandDevice from './pages/master/brandDevice/EditBrandDevice';
|
||||||
import AddEditErrorCode from './pages/master/brandDevice/AddEditErrorCode';
|
|
||||||
import ViewBrandDevice from './pages/master/brandDevice/ViewBrandDevice';
|
import ViewBrandDevice from './pages/master/brandDevice/ViewBrandDevice';
|
||||||
import ViewFilePage from './pages/master/brandDevice/ViewFilePage';
|
import ViewFilePage from './pages/master/brandDevice/ViewFilePage';
|
||||||
|
|
||||||
@@ -118,9 +117,6 @@ const App = () => {
|
|||||||
path="brand-device/view/temp/files/:fileName"
|
path="brand-device/view/temp/files/:fileName"
|
||||||
element={<ViewFilePage />}
|
element={<ViewFilePage />}
|
||||||
/>
|
/>
|
||||||
<Route path="brand-device/:brandId/error-code/add" element={<AddEditErrorCode />} />
|
|
||||||
<Route path="brand-device/:brandId/error-code/edit/:errorCodeId" element={<AddEditErrorCode />} />
|
|
||||||
<Route path="brand-device/add/error-code/edit/:errorCodeId" element={<AddEditErrorCode />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/report" element={<ProtectedRoute />}>
|
<Route path="/report" element={<ProtectedRoute />}>
|
||||||
|
|||||||
@@ -10,22 +10,18 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
Card,
|
Card,
|
||||||
Spin,
|
Spin,
|
||||||
Tag,
|
|
||||||
Space,
|
Space,
|
||||||
Input,
|
ConfigProvider,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { EyeOutlined, EditOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
|
import { EyeOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
import { getBrandById, createBrand, createErrorCode, getErrorCodesByBrandId, updateErrorCode, deleteErrorCode, deleteBrand } from '../../../api/master-brand';
|
import { getBrandById, createBrand, createErrorCode, getErrorCodesByBrandId, updateErrorCode, deleteErrorCode, deleteBrand } from '../../../api/master-brand';
|
||||||
import { getFileUrl } from '../../../api/file-uploads';
|
|
||||||
import { SendRequest } from '../../../components/Global/ApiRequest';
|
|
||||||
import BrandForm from './component/BrandForm';
|
import BrandForm from './component/BrandForm';
|
||||||
import ErrorCodeForm from './component/ErrorCodeForm';
|
import ErrorCodeForm from './component/ErrorCodeForm';
|
||||||
import SolutionForm from './component/SolutionForm';
|
import SolutionForm from './component/SolutionForm';
|
||||||
import SparepartSelect from './component/SparepartSelect';
|
import SparepartSelect from './component/SparepartSelect';
|
||||||
import ListErrorCode from './component/ListErrorCode';
|
import ListErrorCode from './component/ListErrorCode';
|
||||||
import FormActions from './component/FormActions';
|
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
const { Step } = Steps;
|
const { Step } = Steps;
|
||||||
@@ -129,7 +125,7 @@ const AddBrandDevice = () => {
|
|||||||
|
|
||||||
const isFileType = solution.type_solution && solution.type_solution !== 'text';
|
const isFileType = solution.type_solution && solution.type_solution !== 'text';
|
||||||
newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text';
|
newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text';
|
||||||
newSolutionStatuses[fieldKey] = solution.is_active !== false;
|
newSolutionStatuses[fieldKey] = solution.is_active;
|
||||||
|
|
||||||
let fileObject = null;
|
let fileObject = null;
|
||||||
if (isFileType && (solution.path_solution || solution.path_document)) {
|
if (isFileType && (solution.path_solution || solution.path_document)) {
|
||||||
@@ -149,7 +145,7 @@ const AddBrandDevice = () => {
|
|||||||
name: solution.solution_name || '',
|
name: solution.solution_name || '',
|
||||||
type: isFileType ? 'file' : 'text',
|
type: isFileType ? 'file' : 'text',
|
||||||
text: solution.text_solution || '',
|
text: solution.text_solution || '',
|
||||||
status: solution.is_active !== false,
|
status: solution.is_active,
|
||||||
file: fileObject,
|
file: fileObject,
|
||||||
fileUpload: fileObject,
|
fileUpload: fileObject,
|
||||||
path_solution: solution.path_solution || solution.path_document || null,
|
path_solution: solution.path_solution || solution.path_document || null,
|
||||||
@@ -311,7 +307,7 @@ const AddBrandDevice = () => {
|
|||||||
error_code_name: record.error_code_name || '',
|
error_code_name: record.error_code_name || '',
|
||||||
error_code_description: record.error_code_description || '',
|
error_code_description: record.error_code_description || '',
|
||||||
error_code_color: record.error_code_color || '#000000',
|
error_code_color: record.error_code_color || '#000000',
|
||||||
status: record.is_active !== false,
|
status: record.is_active,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (record.path_icon) {
|
if (record.path_icon) {
|
||||||
@@ -1129,13 +1125,23 @@ const AddBrandDevice = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<ConfigProvider
|
||||||
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
theme={{
|
||||||
<Step title="Brand Device Details" />
|
components: {
|
||||||
<Step title="Error Codes & Solutions" />
|
Switch: {
|
||||||
</Steps>
|
colorPrimary: '#23A55A',
|
||||||
<div style={{ position: 'relative' }}>
|
colorPrimaryHover: '#23A55A',
|
||||||
{loading && (
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
||||||
|
<Step title="Brand Device Details" />
|
||||||
|
<Step title="Error Codes & Solutions" />
|
||||||
|
</Steps>
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
{loading && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -1209,7 +1215,8 @@ const AddBrandDevice = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,467 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Typography,
|
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Spin,
|
|
||||||
Upload,
|
|
||||||
} from 'antd';
|
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
|
||||||
import { getErrorCodeById, createErrorCode, updateErrorCode } from '../../../api/master-brand';
|
|
||||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
|
||||||
import ErrorCodeForm from './component/ErrorCodeForm';
|
|
||||||
import SolutionForm from './component/SolutionForm';
|
|
||||||
import { useSolutionLogic } from './hooks/solution';
|
|
||||||
import SparepartSelect from './component/SparepartSelect';
|
|
||||||
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
|
|
||||||
|
|
||||||
const { Title } = Typography;
|
|
||||||
|
|
||||||
const AddEditErrorCode = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { brandId: routeBrandId, errorCodeId } = useParams();
|
|
||||||
const { setBreadcrumbItems } = useBreadcrumb();
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const currentBrandId = routeBrandId;
|
|
||||||
|
|
||||||
const isFromAddBrand = location.pathname.includes('/master/brand-device/') && location.pathname.includes('/error-code/') &&
|
|
||||||
(location.pathname.includes('/add') || (location.pathname.includes('/edit/') && !location.pathname.includes('/edit/')));
|
|
||||||
|
|
||||||
const [errorCodeForm] = Form.useForm();
|
|
||||||
const [solutionForm] = Form.useForm();
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
|
||||||
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
|
||||||
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
|
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
|
||||||
const [fileList, setFileList] = useState([]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
solutionFields,
|
|
||||||
solutionTypes,
|
|
||||||
solutionStatuses,
|
|
||||||
solutionsToDelete,
|
|
||||||
firstSolutionValid,
|
|
||||||
handleAddSolutionField,
|
|
||||||
handleRemoveSolutionField,
|
|
||||||
handleSolutionTypeChange,
|
|
||||||
handleSolutionStatusChange,
|
|
||||||
resetSolutionFields,
|
|
||||||
getSolutionData,
|
|
||||||
setSolutionsForExistingRecord,
|
|
||||||
} = useSolutionLogic(solutionForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const isEditMode = errorCodeId && errorCodeId !== 'add';
|
|
||||||
setIsEdit(isEditMode);
|
|
||||||
|
|
||||||
if (!isEditMode) {
|
|
||||||
resetSolutionFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
setBreadcrumbItems([
|
|
||||||
{
|
|
||||||
title: <span style={{ fontSize: '14px', fontWeight: 'bold' }}>• Master</span>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<span
|
|
||||||
style={{ fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}
|
|
||||||
onClick={() => navigate('/master/brand-device')}
|
|
||||||
>
|
|
||||||
Brand Device
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<span
|
|
||||||
style={{ fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}
|
|
||||||
onClick={() => navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`)}
|
|
||||||
>
|
|
||||||
Edit Brand Device
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<span style={{ fontSize: '14px', fontWeight: 'bold' }}>
|
|
||||||
{isEditMode ? 'Edit Error Code' : 'Add Error Code'}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isEditMode && errorCodeId) {
|
|
||||||
const tempId = errorCodeId.startsWith('existing_') ? errorCodeId : `existing_${errorCodeId}`;
|
|
||||||
loadExistingErrorCode(tempId);
|
|
||||||
}
|
|
||||||
}, [currentBrandId, errorCodeId, navigate, setBreadcrumbItems]);
|
|
||||||
|
|
||||||
const loadExistingErrorCode = async (tempId) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
let errorIdToUse = tempId;
|
|
||||||
if (tempId.startsWith('existing_')) {
|
|
||||||
errorIdToUse = tempId.replace('existing_', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorCodeResponse = await getErrorCodeById(errorIdToUse);
|
|
||||||
|
|
||||||
if (errorCodeResponse && errorCodeResponse.statusCode === 200) {
|
|
||||||
const errorData = errorCodeResponse.data;
|
|
||||||
|
|
||||||
if (errorData) {
|
|
||||||
errorCodeForm.setFieldsValue({
|
|
||||||
error_code: errorData.error_code,
|
|
||||||
error_code_name: errorData.error_code_name || '',
|
|
||||||
error_code_description: errorData.error_code_description || '',
|
|
||||||
error_code_color: errorData.error_code_color || '#000000',
|
|
||||||
status: errorData.is_active !== false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errorData.path_icon) {
|
|
||||||
setErrorCodeIcon({
|
|
||||||
name: errorData.path_icon.split('/').pop(),
|
|
||||||
uploadPath: errorData.path_icon,
|
|
||||||
url: errorData.path_icon,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorData.solution && errorData.solution.length > 0) {
|
|
||||||
setSolutionsForExistingRecord(errorData.solution, solutionForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorData.spareparts && errorData.spareparts.length > 0) {
|
|
||||||
const sparepartIds = errorData.spareparts.map(sp => sp.sparepart_id);
|
|
||||||
setSelectedSparepartIds(sparepartIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorCodeForm.setFieldsValue({
|
|
||||||
error_code: '',
|
|
||||||
error_code_name: '',
|
|
||||||
error_code_description: '',
|
|
||||||
error_code_color: '#000000',
|
|
||||||
status: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'warning',
|
|
||||||
title: 'Peringatan',
|
|
||||||
message: 'Error code not found. Creating new error code.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Error',
|
|
||||||
message: 'Failed to load error code data',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
try {
|
|
||||||
await errorCodeForm.validateFields();
|
|
||||||
|
|
||||||
const solutionData = getSolutionData();
|
|
||||||
|
|
||||||
|
|
||||||
if (!solutionData || solutionData.length === 0) {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'warning',
|
|
||||||
title: 'Perhatian',
|
|
||||||
message: 'Harap lengkapi minimal 1 solution',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidSolutions = solutionData.filter(solution => {
|
|
||||||
if (solution.type_solution === 'text') {
|
|
||||||
return !solution.text_solution || solution.text_solution.trim() === '';
|
|
||||||
} else if (solution.type_solution !== 'text') {
|
|
||||||
return !solution.path_solution || solution.path_solution.trim() === '';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (invalidSolutions.length > 0) {
|
|
||||||
const invalidNames = invalidSolutions.map(s => s.solution_name).join(', ');
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'warning',
|
|
||||||
title: 'Perhatian',
|
|
||||||
message: `Harap lengkapi solution berikut:\n${invalidSolutions.map(s =>
|
|
||||||
`- ${s.solution_name}: ${s.type_solution === 'text' ? 'Text solution wajib diisi' : 'File wajib diupload'}`
|
|
||||||
).join('\n')}`,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorCodeValues = errorCodeForm.getFieldsValue();
|
|
||||||
|
|
||||||
setConfirmLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
error_code_name: errorCodeValues.error_code_name,
|
|
||||||
error_code_description: errorCodeValues.error_code_description || '',
|
|
||||||
error_code_color: errorCodeValues.error_code_color || '#000000',
|
|
||||||
path_icon: errorCodeIcon?.path_icon || errorCodeIcon?.uploadPath || '',
|
|
||||||
is_active: errorCodeValues.status !== undefined ? errorCodeValues.status : true,
|
|
||||||
solution: solutionData || [],
|
|
||||||
spareparts: selectedSparepartIds || []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isEdit) {
|
|
||||||
payload.error_code = errorCodeValues.error_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let response;
|
|
||||||
|
|
||||||
if (isEdit && errorCodeId) {
|
|
||||||
let cleanErrorCodeId = errorCodeId;
|
|
||||||
if (errorCodeId.startsWith('existing_')) {
|
|
||||||
cleanErrorCodeId = errorCodeId.replace('existing_', '');
|
|
||||||
}
|
|
||||||
response = await updateErrorCode(currentBrandId, cleanErrorCodeId, payload);
|
|
||||||
} else {
|
|
||||||
response = await createErrorCode(currentBrandId, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
|
||||||
NotifOk({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Berhasil',
|
|
||||||
message: isEdit ? 'Error Code berhasil diupdate!' : 'Error Code berhasil ditambahkan!',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isFromAddBrand) {
|
|
||||||
navigate(`/master/brand-device/add`);
|
|
||||||
} else {
|
|
||||||
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`, {
|
|
||||||
state: { refreshErrorCodes: true }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Gagal',
|
|
||||||
message: response?.message || 'Gagal menyimpan error code',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Gagal',
|
|
||||||
message: error.message || 'Gagal menyimpan error code. Silakan coba lagi.',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setConfirmLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Error',
|
|
||||||
message: 'Gagal menyimpan error code. Silakan coba lagi.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
if (isFromAddBrand) {
|
|
||||||
navigate(`/master/brand-device/add`);
|
|
||||||
} else {
|
|
||||||
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleErrorCodeIconUpload = (iconData) => {
|
|
||||||
if (!iconData || !iconData.uploadPath) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedIconData = {
|
|
||||||
name: iconData.name,
|
|
||||||
uploadPath: iconData.uploadPath,
|
|
||||||
url: iconData.uploadPath,
|
|
||||||
};
|
|
||||||
|
|
||||||
setErrorCodeIcon(formattedIconData);
|
|
||||||
return formattedIconData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleErrorCodeIconRemove = () => {
|
|
||||||
setErrorCodeIcon(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSolutionFileUpload = (fileObject) => {
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
errorCodeForm.resetFields();
|
|
||||||
errorCodeForm.setFieldsValue({
|
|
||||||
status: true,
|
|
||||||
});
|
|
||||||
setErrorCodeIcon(null);
|
|
||||||
resetSolutionFields();
|
|
||||||
setSelectedSparepartIds([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
{/* Header */}
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 24
|
|
||||||
}}>
|
|
||||||
<Title level={4} style={{ margin: 0 }}>
|
|
||||||
{isEdit ? 'Edit Error Code' : 'Add Error Code'}
|
|
||||||
</Title>
|
|
||||||
<Button
|
|
||||||
icon={<ArrowLeftOutlined />}
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
Back to Brand Device
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div style={{ position: 'relative', minHeight: 500 }}>
|
|
||||||
{loading && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spin size="large" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Row gutter={[24, 24]}>
|
|
||||||
{/* Error Code Form */}
|
|
||||||
<Col xs={24} lg={8}>
|
|
||||||
<Card
|
|
||||||
title="Error Code Details"
|
|
||||||
size="small"
|
|
||||||
style={{ height: 'fit-content' }}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={errorCodeForm}
|
|
||||||
layout="vertical"
|
|
||||||
initialValues={{
|
|
||||||
status: true,
|
|
||||||
error_code_color: '#000000',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ErrorCodeForm
|
|
||||||
errorCodeForm={errorCodeForm}
|
|
||||||
isErrorCodeFormReadOnly={false}
|
|
||||||
errorCodeIcon={errorCodeIcon}
|
|
||||||
onErrorCodeIconUpload={handleErrorCodeIconUpload}
|
|
||||||
onErrorCodeIconRemove={handleErrorCodeIconRemove}
|
|
||||||
isEdit={isEdit}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
{/* Solutions Form */}
|
|
||||||
<Col xs={24} lg={8}>
|
|
||||||
<Card
|
|
||||||
title="Solutions"
|
|
||||||
size="small"
|
|
||||||
style={{ height: 'fit-content' }}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={solutionForm}
|
|
||||||
layout="vertical"
|
|
||||||
initialValues={{}}
|
|
||||||
>
|
|
||||||
<SolutionForm
|
|
||||||
solutionForm={solutionForm}
|
|
||||||
solutionFields={solutionFields}
|
|
||||||
solutionTypes={solutionTypes}
|
|
||||||
solutionStatuses={solutionStatuses}
|
|
||||||
firstSolutionValid={firstSolutionValid}
|
|
||||||
checkFirstSolutionValid={() => {
|
|
||||||
return checkFirstSolutionValid();
|
|
||||||
}}
|
|
||||||
onAddSolutionField={handleAddSolutionField}
|
|
||||||
onRemoveSolutionField={handleRemoveSolutionField}
|
|
||||||
onSolutionTypeChange={handleSolutionTypeChange}
|
|
||||||
onSolutionStatusChange={handleSolutionStatusChange}
|
|
||||||
onSolutionFileUpload={handleSolutionFileUpload}
|
|
||||||
onFileView={(fileData) => {
|
|
||||||
if (fileData && fileData.url) {
|
|
||||||
window.open(fileData.url, '_blank');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
isReadOnly={false}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
{/* Sparepart Selection */}
|
|
||||||
<Col xs={24} lg={8}>
|
|
||||||
<Card
|
|
||||||
title="Spareparts"
|
|
||||||
size="small"
|
|
||||||
style={{ height: 'fit-content' }}
|
|
||||||
>
|
|
||||||
<SparepartSelect
|
|
||||||
selectedSparepartIds={selectedSparepartIds}
|
|
||||||
onSparepartChange={setSelectedSparepartIds}
|
|
||||||
isReadOnly={false}
|
|
||||||
brandId={currentBrandId}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{/* Save Button */}
|
|
||||||
<div style={{ marginTop: 24, textAlign: 'right' }}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
loading={confirmLoading}
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#23A55A',
|
|
||||||
borderColor: '#23A55A',
|
|
||||||
}}
|
|
||||||
onClick={handleSave}
|
|
||||||
>
|
|
||||||
{isEdit ? 'Update Error Code' : 'Save Error Code'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddEditErrorCode;
|
|
||||||
@@ -13,17 +13,17 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
Space,
|
Space,
|
||||||
Input,
|
Input,
|
||||||
|
ConfigProvider
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
import { getBrandById, getErrorCodesByBrandId, getErrorCodeById, deleteErrorCode, updateErrorCode as updateErrorCodeAPI, createErrorCode as createErrorCodeAPI } from '../../../api/master-brand';
|
import { getBrandById, updateBrand, getErrorCodesByBrandId, getErrorCodeById, deleteErrorCode, updateErrorCode as updateErrorCodeAPI, createErrorCode as createErrorCodeAPI } from '../../../api/master-brand';
|
||||||
import { getFileUrl } from '../../../api/file-uploads';
|
import { getFileUrl } from '../../../api/file-uploads';
|
||||||
import { SendRequest } from '../../../components/Global/ApiRequest';
|
import { SendRequest } from '../../../components/Global/ApiRequest';
|
||||||
import BrandForm from './component/BrandForm';
|
import BrandForm from './component/BrandForm';
|
||||||
import ErrorCodeForm from './component/ErrorCodeForm';
|
import ErrorCodeForm from './component/ErrorCodeForm';
|
||||||
import SolutionForm from './component/SolutionForm';
|
import SolutionForm from './component/SolutionForm';
|
||||||
import FormActions from './component/FormActions';
|
|
||||||
import SparepartSelect from './component/SparepartSelect';
|
import SparepartSelect from './component/SparepartSelect';
|
||||||
import ListErrorCode from './component/ListErrorCode';
|
import ListErrorCode from './component/ListErrorCode';
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ const EditBrandDevice = () => {
|
|||||||
const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
|
const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
|
||||||
const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
|
const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
|
||||||
const [currentSolutionData, setCurrentSolutionData] = useState([]);
|
const [currentSolutionData, setCurrentSolutionData] = useState([]);
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
|
||||||
const getSolutionData = () => {
|
const getSolutionData = () => {
|
||||||
if (!solutionForm) return [];
|
if (!solutionForm) return [];
|
||||||
@@ -115,7 +116,7 @@ const EditBrandDevice = () => {
|
|||||||
|
|
||||||
const isFileType = solution.type_solution && solution.type_solution !== 'text';
|
const isFileType = solution.type_solution && solution.type_solution !== 'text';
|
||||||
newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text';
|
newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text';
|
||||||
newSolutionStatuses[fieldKey] = solution.is_active !== false;
|
newSolutionStatuses[fieldKey] = solution.is_active;
|
||||||
|
|
||||||
let fileObject = null;
|
let fileObject = null;
|
||||||
if (isFileType && (solution.path_solution || solution.path_document)) {
|
if (isFileType && (solution.path_solution || solution.path_document)) {
|
||||||
@@ -135,7 +136,7 @@ const EditBrandDevice = () => {
|
|||||||
name: solution.solution_name || '',
|
name: solution.solution_name || '',
|
||||||
type: isFileType ? 'file' : 'text',
|
type: isFileType ? 'file' : 'text',
|
||||||
text: solution.text_solution || '',
|
text: solution.text_solution || '',
|
||||||
status: solution.is_active !== false,
|
status: solution.is_active,
|
||||||
file: fileObject,
|
file: fileObject,
|
||||||
fileUpload: fileObject,
|
fileUpload: fileObject,
|
||||||
path_solution: solution.path_solution || solution.path_document || null,
|
path_solution: solution.path_solution || solution.path_document || null,
|
||||||
@@ -387,17 +388,64 @@ const EditBrandDevice = () => {
|
|||||||
|
|
||||||
const handleNextStep = async () => {
|
const handleNextStep = async () => {
|
||||||
try {
|
try {
|
||||||
await brandForm.validateFields();
|
setConfirmLoading(true);
|
||||||
const currentBrandId = id;
|
|
||||||
if (currentBrandId) {
|
const brandValues = brandForm.getFieldsValue();
|
||||||
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
|
|
||||||
|
if (!brandValues.brand_name || brandValues.brand_name.trim() === '') {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Perhatian',
|
||||||
|
message: 'Brand Name wajib diisi!',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!brandValues.brand_manufacture || brandValues.brand_manufacture.trim() === '') {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Perhatian',
|
||||||
|
message: 'Manufacturer wajib diisi!',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brandApiData = {
|
||||||
|
brand_name: brandValues.brand_name.trim(),
|
||||||
|
brand_type: brandValues.brand_type || '',
|
||||||
|
brand_manufacture: brandValues.brand_manufacture.trim(),
|
||||||
|
brand_model: brandValues.brand_model || '',
|
||||||
|
is_active: brandValues.is_active !== undefined ? brandValues.is_active : true
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await updateBrand(id, brandApiData);
|
||||||
|
|
||||||
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: 'Brand device berhasil diupdate.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentBrandId = id;
|
||||||
|
if (currentBrandId) {
|
||||||
|
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: response?.message || 'Gagal mengupdate brand device',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
icon: 'warning',
|
icon: 'error',
|
||||||
title: 'Perhatian',
|
title: 'Gagal',
|
||||||
message: 'Harap isi semua kolom wajib untuk brand device!',
|
message: error.message || 'Gagal mengupdate brand device',
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setConfirmLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -433,6 +481,7 @@ const EditBrandDevice = () => {
|
|||||||
|
|
||||||
const handleSaveErrorCode = async () => {
|
const handleSaveErrorCode = async () => {
|
||||||
try {
|
try {
|
||||||
|
setConfirmLoading(true);
|
||||||
const errorCodeValues = await errorCodeForm.validateFields();
|
const errorCodeValues = await errorCodeForm.validateFields();
|
||||||
const solutionData = getSolutionData();
|
const solutionData = getSolutionData();
|
||||||
|
|
||||||
@@ -542,6 +591,8 @@ const EditBrandDevice = () => {
|
|||||||
title: 'Perhatian',
|
title: 'Perhatian',
|
||||||
message: error.message || 'Harap isi semua kolom wajib!',
|
message: error.message || 'Harap isi semua kolom wajib!',
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setConfirmLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1165,6 +1216,7 @@ const EditBrandDevice = () => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={handleSaveErrorCode}
|
onClick={handleSaveErrorCode}
|
||||||
|
loading={confirmLoading}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#23A55A',
|
backgroundColor: '#23A55A',
|
||||||
borderColor: '#23A55A',
|
borderColor: '#23A55A',
|
||||||
@@ -1197,13 +1249,23 @@ const EditBrandDevice = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<ConfigProvider
|
||||||
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
theme={{
|
||||||
<Step title="Brand Device Details" />
|
components: {
|
||||||
<Step title="Error Codes & Solutions" />
|
Switch: {
|
||||||
</Steps>
|
colorPrimary: '#23A55A',
|
||||||
{renderStepContent()}
|
colorPrimaryHover: '#23A55A',
|
||||||
<Divider />
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
||||||
|
<Step title="Brand Device Details" />
|
||||||
|
<Step title="Error Codes & Solutions" />
|
||||||
|
</Steps>
|
||||||
|
{renderStepContent()}
|
||||||
|
<Divider />
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<div>
|
<div>
|
||||||
{currentStep === 1 && (
|
{currentStep === 1 && (
|
||||||
@@ -1220,7 +1282,7 @@ const EditBrandDevice = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleNextStep}
|
onClick={handleNextStep}
|
||||||
loading={loading}
|
loading={confirmLoading}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#23A55A',
|
backgroundColor: '#23A55A',
|
||||||
borderColor: '#23A55A',
|
borderColor: '#23A55A',
|
||||||
@@ -1243,7 +1305,8 @@ const EditBrandDevice = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Button, ConfigProvider } from 'antd';
|
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const FormActions = ({
|
|
||||||
currentStep,
|
|
||||||
onPreviousStep,
|
|
||||||
onNextStep,
|
|
||||||
onSave,
|
|
||||||
onCancel,
|
|
||||||
confirmLoading,
|
|
||||||
isEditMode = false,
|
|
||||||
showCancelButton = true
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
|
||||||
<ConfigProvider
|
|
||||||
theme={{
|
|
||||||
token: { colorBgContainer: '#E9F6EF' },
|
|
||||||
components: {
|
|
||||||
Button: {
|
|
||||||
defaultBg: 'white',
|
|
||||||
defaultColor: '#23A55A',
|
|
||||||
defaultBorderColor: '#23A55A',
|
|
||||||
defaultHoverColor: '#23A55A',
|
|
||||||
defaultHoverBorderColor: '#23A55A',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showCancelButton && (
|
|
||||||
<Button onClick={onCancel}>Batal</Button>
|
|
||||||
)}
|
|
||||||
{currentStep > 0 && (
|
|
||||||
<Button onClick={onPreviousStep} style={{ marginRight: 8 }}>
|
|
||||||
Kembali
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ConfigProvider>
|
|
||||||
|
|
||||||
<ConfigProvider
|
|
||||||
theme={{
|
|
||||||
components: {
|
|
||||||
Button: {
|
|
||||||
defaultBg: '#23a55a',
|
|
||||||
defaultColor: '#FFFFFF',
|
|
||||||
defaultBorderColor: '#23a55a',
|
|
||||||
defaultHoverBg: '#209652',
|
|
||||||
defaultHoverColor: '#FFFFFF',
|
|
||||||
defaultHoverBorderColor: '#23a55a',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentStep < 1 && (
|
|
||||||
<Button loading={confirmLoading} onClick={onNextStep}>
|
|
||||||
Lanjut
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{currentStep === 1 && (
|
|
||||||
<Button loading={confirmLoading} onClick={onSave}>
|
|
||||||
{isEditMode ? 'Update' : 'Simpan'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ConfigProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FormActions;
|
|
||||||
Reference in New Issue
Block a user