Files
cod-fe/src/pages/master/brandDevice/AddEditErrorCode.jsx

588 lines
23 KiB
JavaScript

import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import {
Card,
Typography,
Button,
Form,
Row,
Col,
Spin,
Upload,
} from 'antd';
import { ArrowLeftOutlined, UploadOutlined } from '@ant-design/icons';
import { getBrandById, getErrorCodeById, updateBrand, getErrorCodesByBrandId } from '../../../api/master-brand';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { useBrandForm } from '../../../context/BrandFormContext';
import { uploadFile } from '../../../api/file-uploads';
import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm';
import SolutionForm from './component/SolutionForm';
import { useSolutionLogic } from './hooks/solution';
import SingleSparepartSelect from './component/SingleSparepartSelect';
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
const { Title } = Typography;
const AddEditErrorCode = () => {
const navigate = useNavigate();
const { brandId: routeBrandId, errorCodeId } = useParams();
const { setBreadcrumbItems } = useBreadcrumb();
// Use BrandForm context
const {
brandId: contextBrandId,
routeBrandId: contextRouteBrandId,
setRouteBrandId,
setErrorCodeId,
initializeFromRoute,
tempErrorCodes,
existingErrorCodes,
addErrorCode,
updateErrorCode,
setCurrentErrorCode
} = useBrandForm();
// Use brandId from context first, fallback to route
const currentBrandId = contextBrandId || routeBrandId;
// Forms
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);
// Initialize context with route parameters
if (routeBrandId) {
initializeFromRoute(routeBrandId, errorCodeId);
}
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) {
// For existing error codes, construct the proper tempId format
const tempId = errorCodeId.startsWith('existing_') ? errorCodeId : `existing_${errorCodeId}`;
loadExistingErrorCode(tempId);
}
}, [currentBrandId, errorCodeId, navigate, setBreadcrumbItems]);
const loadExistingErrorCode = async (tempId) => {
try {
setLoading(true);
// console.log(' Looking for error code with tempId:', tempId);
// console.log(' Available error codes in context:', tempErrorCodes);
// Find error code in tempErrorCodes first
let existingErrorCode = tempErrorCodes.find(ec => ec.tempId === tempId);
// If not found, check in existingErrorCodes with format existing_${error_code_id}
if (!existingErrorCode && tempId.startsWith('existing_')) {
const errorId = tempId.replace('existing_', '');
existingErrorCode = existingErrorCodes.find(ec => ec.error_code_id == errorId);
if (existingErrorCode) {
existingErrorCode = {
...existingErrorCode,
tempId: tempId
};
}
}
// console.log(' Found error code in context:', existingErrorCode);
if (existingErrorCode) {
errorCodeForm.setFieldsValue({
error_code: existingErrorCode.error_code,
error_code_name: existingErrorCode.error_code_name || '',
error_code_description: existingErrorCode.error_code_description || '',
error_code_color: existingErrorCode.error_code_color || '#000000',
status: existingErrorCode.is_active !== false,
});
if (existingErrorCode.path_icon) {
setErrorCodeIcon({
name: existingErrorCode.path_icon.split('/').pop(),
uploadPath: existingErrorCode.path_icon,
url: existingErrorCode.path_icon,
});
}
if (existingErrorCode.solution && existingErrorCode.solution.length > 0) {
// console.log('🔍 Setting solutions from context:', existingErrorCode.solution);
setSolutionsForExistingRecord(existingErrorCode.solution, solutionForm);
}
if (existingErrorCode.spareparts && existingErrorCode.spareparts.length > 0) {
// console.log('🔍 Setting spareparts from context:', existingErrorCode.spareparts);
setSelectedSparepartIds(existingErrorCode.spareparts);
}
} else {
// console.log('🔍 Error code not found in context, trying API...');
let errorIdToUse = tempId;
// Extract the actual error_code_id from tempId format
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,
});
}
// Set solutions from API data (include file data)
if (errorData.solution && errorData.solution.length > 0) {
setSolutionsForExistingRecord(errorData.solution, solutionForm);
}
// Set spareparts from API data
if (errorData.spareparts && errorData.spareparts.length > 0) {
const sparepartIds = errorData.spareparts.map(sp => sp.sparepart_id);
setSelectedSparepartIds(sparepartIds);
}
// Don't add to context - this is existing data from API
// The context should already have this error code from the brand data loading
}
} else {
// console.log('🔍 API Response error or not found:', errorCodeResponse);
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) {
console.error('Failed to load error code:', error);
NotifAlert({
icon: 'error',
title: 'Error',
message: 'Failed to load error code data',
});
} finally {
setLoading(false);
}
};
const handleSave = async () => {
try {
await errorCodeForm.validateFields();
const solutionValues = solutionForm.getFieldsValue();
const firstSolutionPath = `solution_items,${solutionFields[0]?.key || 0}`;
const firstSolution = solutionValues[firstSolutionPath];
let isValid = false;
if (firstSolution && firstSolution.name && firstSolution.name.trim() !== '') {
const firstSolutionType = solutionTypes[solutionFields[0]?.key || 0];
if (firstSolutionType === 'text') {
isValid = firstSolution.text && firstSolution.text.trim() !== '';
} else {
isValid = true;
}
}
if (!isValid) {
NotifAlert({
icon: 'warning',
title: 'Perhatian',
message: 'Harap lengkapi minimal 1 solution',
});
return;
}
const errorCodeValues = errorCodeForm.getFieldsValue();
const solutionData = getSolutionData();
// Determine the correct tempId for editing
let updateTempId;
if (isEdit) {
// For existing error codes, find the correct tempId
if (errorCodeId && !errorCodeId.startsWith('pending-')) {
// Look for existing error code in context
const existingEc = existingErrorCodes.find(ec => ec.error_code_id == errorCodeId);
if (existingEc && existingEc.tempId) {
updateTempId = existingEc.tempId;
} else {
updateTempId = `existing_${errorCodeId}`;
}
} else {
updateTempId = errorCodeId;
}
} else {
updateTempId = Date.now().toString();
}
const currentErrorCode = {
tempId: updateTempId,
error_code_id: isEdit && errorCodeId && !errorCodeId.startsWith('pending-') ? errorCodeId : null,
error_code: errorCodeValues.error_code || '',
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?.uploadPath || '',
is_active: errorCodeValues.status !== undefined ? errorCodeValues.status : true,
solution: solutionData || [],
spareparts: selectedSparepartIds || [],
errorCodeIcon: errorCodeIcon,
};
if (isEdit) {
updateErrorCode(updateTempId, currentErrorCode);
} else {
addErrorCode(currentErrorCode);
}
NotifOk({
icon: 'success',
title: 'Berhasil',
message: isEdit ? 'Error Code berhasil diupdate!' : 'Error Code berhasil ditambahkan!',
});
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
} catch (error) {
console.error('Error saving error code:', error);
NotifAlert({
icon: 'error',
title: 'Error',
message: 'Gagal menyimpan error code. Silakan coba lagi.',
});
}
};
const handleCancel = () => {
navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
};
const handleErrorCodeIconUpload = async (file) => {
if (!file) return null;
try {
const folder = 'images';
const response = await uploadFile(file, folder);
if (response && response.statusCode === 200) {
const iconData = {
name: file.name,
uploadPath: response.data.path_document,
url: response.data.path_document,
};
setErrorCodeIcon(iconData);
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Error code icon uploaded successfully',
});
return iconData;
} else {
NotifAlert({
icon: 'error',
title: 'Gagal',
message: response?.message || 'Failed to upload error code icon',
});
return null;
}
} catch (error) {
console.error('Error uploading icon:', error);
NotifAlert({
icon: 'error',
title: 'Gagal',
message: 'Failed to upload error code icon',
});
return null;
}
};
const handleErrorCodeIconRemove = () => {
setErrorCodeIcon(null);
};
const handleSolutionFileUpload = async (file, solutionKey) => {
if (!file) return null;
try {
// Determine folder based on file type
const fileExtension = file.name.split('.').pop().toLowerCase();
const folder = ['pdf'].includes(fileExtension) ? 'pdf' : 'images';
const response = await uploadFile(file, folder);
if (response && response.statusCode === 200) {
const fileData = {
name: file.name,
uploadPath: response.data.path_document,
url: response.data.path_document,
size: file.size,
type: file.type,
};
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Solution file uploaded successfully',
});
return fileData;
} else {
NotifAlert({
icon: 'error',
title: 'Gagal',
message: response?.message || 'Failed to upload solution file',
});
return null;
}
} catch (error) {
console.error('Error uploading solution file:', error);
NotifAlert({
icon: 'error',
title: 'Gagal',
message: 'Failed to upload solution file',
});
return null;
}
};
const handleSolutionFileView = (fileData) => {
if (fileData && fileData.url) {
window.open(fileData.url, '_blank');
}
};
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',
}}
>
<ErrorCodeSimpleForm
errorCodeForm={errorCodeForm}
isErrorCodeFormReadOnly={false}
errorCodeIcon={errorCodeIcon}
onErrorCodeIconUpload={handleErrorCodeIconUpload}
onErrorCodeIconRemove={handleErrorCodeIconRemove}
/>
</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={{
solution_items: [{
status: true,
type: 'text',
}]
}}
>
<SolutionForm
solutionForm={solutionForm}
solutionFields={solutionFields}
solutionTypes={solutionTypes}
solutionStatuses={solutionStatuses}
firstSolutionValid={firstSolutionValid}
checkFirstSolutionValid={() => {
// console.log('🔍 checkFirstSolutionValid function:', typeof checkFirstSolutionValid);
return checkFirstSolutionValid();
}}
onAddSolutionField={handleAddSolutionField}
onRemoveSolutionField={handleRemoveSolutionField}
onSolutionTypeChange={handleSolutionTypeChange}
onSolutionStatusChange={handleSolutionStatusChange}
onSolutionFileUpload={handleSolutionFileUpload}
onFileView={handleSolutionFileView}
fileList={fileList}
isReadOnly={false}
/>
</Form>
</Card>
</Col>
{/* Sparepart Selection */}
<Col xs={24} lg={8}>
<Card
title="Spareparts"
size="small"
style={{ height: 'fit-content' }}
>
<SingleSparepartSelect
selectedSparepartIds={selectedSparepartIds}
onSparepartChange={setSelectedSparepartIds}
isReadOnly={false}
brandId={currentBrandId}
/>
</Card>
</Col>
</Row>
{/* Save Button */}
<div style={{ marginTop: 24, textAlign: 'right' }}>
<Button
type="primary"
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
onClick={handleSave}
>
{isEdit ? 'Update Error Code' : 'Save Error Code'}
</Button>
</div>
</div>
</Card>
);
};
export default AddEditErrorCode;