import { useEffect, useState, useCallback, useMemo } from 'react'; import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom'; import { Divider, Typography, Button, Steps, Form, Row, Col, Card, Spin, Tag, Space, Input, ConfigProvider } from 'antd'; import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons'; import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { getBrandById, updateBrand, getErrorCodesByBrandId, getErrorCodeById, deleteErrorCode, updateErrorCode as updateErrorCodeAPI, createErrorCode as createErrorCodeAPI } from '../../../api/master-brand'; import { getFileUrl } from '../../../api/file-uploads'; import { SendRequest } from '../../../components/Global/ApiRequest'; import BrandForm from './component/BrandForm'; import ErrorCodeForm from './component/ErrorCodeForm'; import SolutionForm from './component/SolutionForm'; import SparepartSelect from './component/SparepartSelect'; import ListErrorCode from './component/ListErrorCode'; const { Title } = Typography; const { Step } = Steps; const EditBrandDevice = () => { const navigate = useNavigate(); const { id } = useParams(); const [searchParams] = useSearchParams(); const location = useLocation(); const { setBreadcrumbItems } = useBreadcrumb(); const [brandForm] = Form.useForm(); const [errorCodeForm] = Form.useForm(); const [solutionForm] = Form.useForm(); const [errorCodeIcon, setErrorCodeIcon] = useState(null); const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); const [loading, setLoading] = useState(false); const tab = searchParams.get('tab'); const [currentStep, setCurrentStep] = useState(tab === 'error-codes' ? 1 : 0); const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null); const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false); const [searchText, setSearchText] = useState(''); const [apiErrorCodes, setApiErrorCodes] = useState([]); const [trigerFilter, setTrigerFilter] = useState(false); const [brandInfo, setBrandInfo] = useState({}); const [tempErrorCodes, setTempErrorCodes] = useState([]); const [existingErrorCodes, setExistingErrorCodes] = useState([]); const [selectedErrorCode, setSelectedErrorCode] = useState(null); const [solutionFields, setSolutionFields] = useState([0]); const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' }); const [solutionStatuses, setSolutionStatuses] = useState({ 0: true }); const [currentSolutionData, setCurrentSolutionData] = useState([]); const [confirmLoading, setConfirmLoading] = useState(false); const getSolutionData = () => { if (!solutionForm) return []; try { const values = solutionForm.getFieldsValue(true); let solutions = []; if (values.solution_items) { if (Array.isArray(values.solution_items)) { solutions = values.solution_items.filter(Boolean); } else if (typeof values.solution_items === 'object') { solutions = Object.values(values.solution_items).filter(Boolean); } } return solutions; } catch (error) { return []; } }; const resetSolutionFields = () => { if (solutionForm && solutionForm.resetFields) { solutionForm.resetFields(); solutionForm.setFieldsValue({ solution_items: { 0: { name: '', type: 'text', text: '', status: true } } }); } setCurrentSolutionData([]); }; const setSolutionsForExistingRecord = (solutions, targetForm) => { if (!targetForm || !solutions || solutions.length === 0) { return; } targetForm.resetFields(); const solutionItems = {}; const newSolutionFields = []; const newSolutionTypes = {}; const newSolutionStatuses = {}; solutions.forEach((solution, index) => { const fieldKey = index; newSolutionFields.push(fieldKey); const isFileType = solution.type_solution && solution.type_solution !== 'text'; newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text'; newSolutionStatuses[fieldKey] = solution.is_active; let fileObject = null; if (isFileType && (solution.path_solution || solution.path_document)) { fileObject = { uploadPath: solution.path_solution || solution.path_document, path_solution: solution.path_solution || solution.path_document, name: solution.file_upload_name || (solution.path_solution || solution.path_document).split('/').pop() || 'File', type_solution: solution.type_solution, isExisting: true, size: 0, url: solution.path_solution || solution.path_document }; } solutionItems[fieldKey] = { brand_code_solution_id: solution.brand_code_solution_id, name: solution.solution_name || '', type: isFileType ? 'file' : 'text', text: solution.text_solution || '', status: solution.is_active, file: fileObject, fileUpload: fileObject, path_solution: solution.path_solution || solution.path_document || null, fileName: solution.file_upload_name || null }; }); setSolutionFields(newSolutionFields); setSolutionTypes(newSolutionTypes); setSolutionStatuses(newSolutionStatuses); targetForm.resetFields(); setTimeout(() => { targetForm.setFieldsValue({ solution_items: solutionItems }); setTimeout(() => { Object.keys(solutionItems).forEach(key => { const solution = solutionItems[key]; targetForm.setFieldValue(['solution_items', key, 'name'], solution.name); targetForm.setFieldValue(['solution_items', key, 'type'], solution.type); targetForm.setFieldValue(['solution_items', key, 'text'], solution.text); targetForm.setFieldValue(['solution_items', key, 'file'], solution.file); targetForm.setFieldValue(['solution_items', key, 'fileUpload'], solution.fileUpload); targetForm.setFieldValue(['solution_items', key, 'status'], solution.status); targetForm.setFieldValue(['solution_items', key, 'path_solution'], solution.path_solution); targetForm.setFieldValue(['solution_items', key, 'fileName'], solution.fileName); }); const finalValues = targetForm.getFieldsValue(); }, 100); }, 100); }; const handleAddSolutionField = () => { const newKey = Math.max(...solutionFields, 0) + 1; setSolutionFields(prev => [...prev, newKey]); setSolutionTypes(prev => ({ ...prev, [newKey]: 'text' })); setSolutionStatuses(prev => ({ ...prev, [newKey]: true })); }; const handleRemoveSolutionField = (fieldKey) => { if (solutionFields.length > 1) { setSolutionFields(prev => prev.filter(key => key !== fieldKey)); const newTypes = { ...solutionTypes }; const newStatuses = { ...solutionStatuses }; delete newTypes[fieldKey]; delete newStatuses[fieldKey]; setSolutionTypes(newTypes); setSolutionStatuses(newStatuses); const currentValues = solutionForm.getFieldsValue(); if (currentValues.solution_items && currentValues.solution_items[fieldKey]) { delete currentValues.solution_items[fieldKey]; solutionForm.setFieldsValue(currentValues); } } }; const handleSolutionTypeChange = (fieldKey, type) => { setSolutionTypes(prev => ({ ...prev, [fieldKey]: type })); if (type === 'file') { solutionForm.setFieldValue(['solution_items', fieldKey, 'text'], ''); } if (type === 'text') { solutionForm.setFieldValue(['solution_items', fieldKey, 'file'], null); solutionForm.setFieldValue(['solution_items', fieldKey, 'fileUpload'], null); } }; const handleSolutionStatusChange = (fieldKey, status) => { setSolutionStatuses(prev => ({ ...prev, [fieldKey]: status })); }; useEffect(() => { errorCodeForm.setFieldsValue({ status: true, }); const fetchBrandData = async () => { const token = localStorage.getItem('token'); if (!token) { navigate('/signin'); return; } const tab = searchParams.get('tab') || 'brand'; setBreadcrumbItems([ { title: • Master }, { title: ( navigate('/master/brand-device')} > Brand Device ), }, { title: ( Edit Brand Device ), }, ]); try { setLoading(true); const response = await getBrandById(id); if (response && response.statusCode === 200) { const brandData = response.data; const brandInfoData = { brand_code: brandData.brand_code, brand_name: brandData.brand_name, brand_type: brandData.brand_type || '', brand_manufacture: brandData.brand_manufacture || '', brand_model: brandData.brand_model || '', is_active: brandData.is_active }; setBrandInfo(brandInfoData); brandForm.setFieldsValue(brandInfoData); if (brandData.brand_id) { try { const errorCodesResponse = await getErrorCodesByBrandId(id || brandData.brand_id); if (errorCodesResponse && errorCodesResponse.statusCode === 200) { const apiErrorData = errorCodesResponse.data || []; const existingCodes = apiErrorData.map(ec => ({ ...ec, tempId: `existing_${ec.error_code_id}`, status: 'existing', solution: ec.solution || [], spareparts: ec.spareparts || [] })); setExistingErrorCodes(existingCodes); setApiErrorCodes(existingCodes); } } catch (error) { } } setCurrentStep(tab === 'brand' ? 0 : 1); } else { NotifAlert({ icon: 'error', title: 'Error', message: response?.message || 'Failed to fetch brand device data', }); } } catch (error) { NotifAlert({ icon: 'error', title: 'Error', message: error.message || 'Failed to fetch brand device data', }); } finally { setLoading(false); } }; fetchBrandData(); }, [id, navigate]); useEffect(() => { const tab = searchParams.get('tab') || 'brand'; setCurrentStep(tab === 'brand' ? 0 : 1); }, [searchParams]); useEffect(() => { if (currentStep === 1 && id) { setTrigerFilter(prev => !prev); } }, [currentStep, id]); // Auto refresh error codes when returning from add/edit error code useEffect(() => { if (location.state?.refreshErrorCodes) { // Trigger refresh of error codes list setTrigerFilter(prev => !prev); // Clear the state to prevent infinite refresh const state = { ...location.state }; delete state.refreshErrorCodes; navigate(location.pathname + location.search, { replace: true, state }); } }, [location, navigate]); const addErrorCode = (newErrorCode) => { // Generate unique tempId with timestamp and random number const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const errorCodeWithId = { ...newErrorCode, tempId: uniqueId, status: 'new' }; setTempErrorCodes(prev => [...prev, errorCodeWithId]); }; const updateErrorCode = (tempId, updatedData) => { setTempErrorCodes(prev => prev.map(ec => ec.tempId === tempId ? { ...ec, ...updatedData, status: 'modified' } : ec) ); setExistingErrorCodes(prev => prev.map(ec => ec.tempId === tempId ? { ...ec, ...updatedData, status: 'modified' } : ec) ); }; const deleteLocalErrorCode = (tempId, permanent = false) => { if (permanent) { setTempErrorCodes(prev => prev.filter(ec => ec.tempId !== tempId)); setExistingErrorCodes(prev => prev.filter(ec => ec.tempId !== tempId)); } else { setTempErrorCodes(prev => prev.map(ec => ec.tempId === tempId ? { ...ec, status: 'deleted' } : ec) ); setExistingErrorCodes(prev => prev.map(ec => ec.tempId === tempId ? { ...ec, status: 'deleted' } : ec) ); } }; const getErrorCodeById = (tempId) => { const inTemp = tempErrorCodes.find(ec => ec.tempId === tempId); if (inTemp) return inTemp; const inExisting = existingErrorCodes.find(ec => ec.tempId === tempId); return inExisting; }; const handleNextStep = async () => { try { setConfirmLoading(true); const brandValues = brandForm.getFieldsValue(); 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) { NotifAlert({ icon: 'error', title: 'Gagal', message: error.message || 'Gagal mengupdate brand device', }); } finally { setConfirmLoading(false); } }; const handleCancel = () => { navigate('/master/brand-device'); }; const handleErrorCodeIconUpload = (iconData) => { setErrorCodeIcon(iconData); }; const handleErrorCodeIconRemove = () => { setErrorCodeIcon(null); }; const resetErrorCodeForm = () => { errorCodeForm.resetFields(); errorCodeForm.setFieldsValue({ status: true, }); setErrorCodeIcon(null); resetSolutionFields(); setIsErrorCodeFormReadOnly(false); setEditingErrorCodeKey(null); setSelectedSparepartIds([]); }; const handleCreateNewErrorCode = () => { resetErrorCodeForm(); setCurrentSolutionData([]); }; const handleSaveErrorCode = async () => { try { setConfirmLoading(true); const errorCodeValues = await errorCodeForm.validateFields(); const solutionData = getSolutionData(); if (!errorCodeValues.error_code || !errorCodeValues.error_code_name) { NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Error code dan error name wajib diisi!', }); return; } if (!solutionData || solutionData.length === 0) { NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Setiap error code harus memiliki minimal 1 solution!', }); return; } const formattedSolutions = solutionData.map(solution => { const solutionType = solution.type || 'text'; let typeSolution = solutionType === 'text' ? 'text' : 'image'; if (solution.file && solution.file.type_solution) { typeSolution = solution.file.type_solution; } else if (solution.fileUpload && solution.fileUpload.type_solution) { typeSolution = solution.fileUpload.type_solution; } const formattedSolution = { solution_name: solution.name, type_solution: typeSolution, is_active: solution.status !== false, }; if (typeSolution === 'text') { formattedSolution.text_solution = solution.text || ''; formattedSolution.path_solution = ''; } else { formattedSolution.text_solution = ''; formattedSolution.path_solution = solution.path_solution || solution.file?.uploadPath || solution.fileUpload?.uploadPath || ''; } if (formattedSolution.brand_code_solution_id) { delete formattedSolution.brand_code_solution_id; } return formattedSolution; }); 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?.uploadPath || '', is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status, solution: formattedSolutions, spareparts: selectedSparepartIds || [] }; if (!editingErrorCodeKey || !editingErrorCodeKey.startsWith('existing_')) { payload.error_code = errorCodeValues.error_code; } let response; if (editingErrorCodeKey && editingErrorCodeKey.startsWith('existing_')) { const errorCodeId = editingErrorCodeKey.replace('existing_', ''); response = await updateErrorCodeAPI(id, errorCodeId, payload); } else { response = await createErrorCodeAPI(id, payload); } if (response && (response.statusCode === 200 || response.statusCode === 201)) { NotifOk({ icon: 'success', title: 'Berhasil', message: editingErrorCodeKey ? 'Error code berhasil diupdate!' : 'Error code berhasil ditambahkan!', }); // Clear temp error codes after successful save to prevent duplication setTempErrorCodes(prev => prev.filter(ec => { // Keep existing error codes that weren't just saved if (ec.status === 'existing' || ec.tempId.startsWith('existing_')) { return true; } // Remove the newly saved error code to prevent duplication return ec.tempId !== editingErrorCodeKey; })); setTrigerFilter(prev => !prev); resetErrorCodeForm(); } else { NotifAlert({ icon: 'error', title: 'Gagal', message: response?.message || 'Gagal menyimpan error code', }); } } catch (error) { NotifAlert({ icon: 'warning', title: 'Perhatian', message: error.message || 'Harap isi semua kolom wajib!', }); } finally { setConfirmLoading(false); } }; const loadErrorCodeData = (errorCode, isPreview = false) => { if (errorCode) { const formValues = { error_code: errorCode.error_code, error_code_name: errorCode.error_code_name, error_code_description: errorCode.error_code_description || '', error_code_color: errorCode.error_code_color && errorCode.error_code_color !== '' ? errorCode.error_code_color : '#000000', status: errorCode.is_active, }; errorCodeForm.setFieldsValue(formValues); if (errorCode.path_icon && errorCode.path_icon !== '') { const iconData = { name: errorCode.path_icon.split('/').pop(), uploadPath: errorCode.path_icon, }; setErrorCodeIcon(iconData); } else { setErrorCodeIcon(null); } setIsErrorCodeFormReadOnly(isPreview); const editingKey = errorCode.tempId || `existing_${errorCode.error_code_id}`; setEditingErrorCodeKey(editingKey); } }; const handlePreviewErrorCode = (record) => { const errorCode = getErrorCodeById(record.tempId || record.error_code_id); loadErrorCodeData(errorCode, true); }; const handleEditErrorCode = (record) => { const errorCode = getErrorCodeById(record.tempId || record.error_code_id); loadErrorCodeData(errorCode, false); }; const handleDeleteErrorCode = async (record) => { NotifConfirmDialog({ icon: 'question', title: 'Konfirmasi Hapus', message: `Apakah Anda yakin ingin menghapus error code "${record.error_code}"?`, onConfirm: async () => { try { if (record.status === 'existing' && record.error_code_id) { const response = await deleteErrorCode(id, record.error_code_id); if (response && response.statusCode === 200) { NotifOk({ icon: 'success', title: 'Berhasil', message: 'Error code berhasil dihapus!', }); setTrigerFilter(prev => !prev); } else { NotifAlert({ icon: 'error', title: 'Error', message: response?.message || 'Failed to delete error code', }); } } else { const tempId = record.tempId; deleteLocalErrorCode(tempId, true); NotifOk({ icon: 'success', title: 'Berhasil', message: 'Error code berhasil dihapus!', }); setTrigerFilter(prev => !prev); } } catch (error) { NotifAlert({ icon: 'error', title: 'Error', message: error.message || 'Failed to delete error code', }); } }, onCancel: () => { } }); }; const mergedErrorCodes = useMemo(() => { const allErrorCodes = [ ...existingErrorCodes.map(ec => ({ ...ec, status: 'existing' })), ...tempErrorCodes ]; const activeErrorCodes = allErrorCodes.filter(ec => ec.status !== 'deleted'); if (searchText) { return activeErrorCodes.filter(ec => ec.error_code.toLowerCase().includes(searchText.toLowerCase()) || ec.error_code_name.toLowerCase().includes(searchText.toLowerCase()) ); } return activeErrorCodes; }, [existingErrorCodes, tempErrorCodes, searchText]); const errorCodeColumns = (showPreviewModal, showEditModal, showDeleteDialog) => [ { title: 'No', key: 'no', width: '5%', align: 'center', render: (_, __, index) => index + 1, }, { title: 'Error Code', dataIndex: 'error_code', key: 'error_code', width: '20%', render: (text, record) => ( {text} ), }, { title: 'Error Name', dataIndex: 'error_code_name', key: 'error_code_name', width: '25%', }, { title: 'Status', dataIndex: 'is_active', key: 'is_active', width: '10%', align: 'center', render: (_, { is_active }) => ( {is_active ? 'Active' : 'Inactive'} ), }, { title: 'Action', key: 'action', align: 'center', width: '15%', render: (_, record) => ( )} {/* Save Button - Always show on right */}
); } return null; }; return ( {renderStepContent()}
{currentStep === 1 && ( )}
{currentStep === 0 && ( )} {currentStep === 1 && ( )}
); }; export default EditBrandDevice;