From 5703ff0e8d5b9305531a323f77fcc7e9af60e5f4 Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Mon, 8 Dec 2025 16:45:49 +0700 Subject: [PATCH] repair: add edit brand device --- src/App.jsx | 48 +- src/api/master-brand.jsx | 42 +- src/context/BrandFormContext.jsx | 624 ------------------ .../master/brandDevice/AddBrandDevice.jsx | 324 +++++++-- .../master/brandDevice/AddEditErrorCode.jsx | 403 +++++------ .../master/brandDevice/EditBrandDevice.jsx | 177 ++--- .../brandDevice/component/BrandForm.jsx | 1 - .../component/ErrorCodeSimpleForm.jsx | 191 +----- .../brandDevice/component/ErrorCodeTable.jsx | 297 --------- .../component/FileUploadHandler.jsx | 422 ++++++++++-- .../brandDevice/component/ListBrandDevice.jsx | 17 - .../brandDevice/component/ListErrorCode.jsx | 84 --- .../brandDevice/component/SolutionField.jsx | 193 +++--- .../brandDevice/component/SolutionForm.jsx | 129 ++-- .../master/brandDevice/hooks/solution.js | 142 +++- .../brandDevice/hooks/useBrandDeviceLogic.jsx | 298 --------- 16 files changed, 1194 insertions(+), 2198 deletions(-) delete mode 100644 src/context/BrandFormContext.jsx delete mode 100644 src/pages/master/brandDevice/component/ErrorCodeTable.jsx delete mode 100644 src/pages/master/brandDevice/component/ListErrorCode.jsx delete mode 100644 src/pages/master/brandDevice/hooks/useBrandDeviceLogic.jsx diff --git a/src/App.jsx b/src/App.jsx index 4581a26..6314408 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,7 +4,6 @@ import SignIn from './pages/auth/SignIn'; import SignUp from './pages/auth/Signup'; import { ProtectedRoute } from './ProtectedRoute'; import NotFound from './pages/blank/NotFound'; -import { BrandFormProvider } from './context/BrandFormContext'; // Dashboard import Home from './pages/home/Home'; @@ -97,33 +96,26 @@ const App = () => { } /> } /> - {/* Brand Device Routes with BrandFormProvider */} - - - } /> - } /> - } /> - } /> - } - /> - } - /> - } - /> - } /> - } /> - } /> - } /> - - - } /> + {/* Brand Device Routes */} + } /> + } /> + } /> + } /> + } + /> + } + /> + } + /> + } /> + } /> + } /> }> diff --git a/src/api/master-brand.jsx b/src/api/master-brand.jsx index 99db0ba..908d8ef 100644 --- a/src/api/master-brand.jsx +++ b/src/api/master-brand.jsx @@ -66,4 +66,44 @@ const getErrorCodeById = async (id) => { return response.data; }; -export { getAllBrands, getBrandById, createBrand, updateBrand, deleteBrand, getErrorCodesByBrandId, getErrorCodeById }; +const createErrorCode = async (brandId, queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `error-code/brand/${brandId}`, + params: queryParams, + }); + + return response.data; +}; + +const updateErrorCode = async (brandId, errorCodeId, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `error-code/brand/${brandId}/${errorCodeId}`, + params: queryParams, + }); + + return response.data; +}; + +const deleteErrorCode = async (brandId, errorCode) => { + const response = await SendRequest({ + method: 'delete', + prefix: `error-code/brand/${brandId}/${errorCode}`, + }); + + return response.data; +}; + +export { + getAllBrands, + getBrandById, + createBrand, + updateBrand, + deleteBrand, + getErrorCodesByBrandId, + getErrorCodeById, + createErrorCode, + updateErrorCode, + deleteErrorCode +}; diff --git a/src/context/BrandFormContext.jsx b/src/context/BrandFormContext.jsx deleted file mode 100644 index 66a1765..0000000 --- a/src/context/BrandFormContext.jsx +++ /dev/null @@ -1,624 +0,0 @@ -import React, { createContext, useContext, useReducer } from 'react'; -import { NotifAlert } from '../components/Global/ToastNotif'; - -// Initial state -const initialState = { - brandId: null, - routeBrandId: null, - errorCodeId: null, - currentStep: 1, - brandInfo: { - brand_name: '', - brand_type: '', - brand_manufacture: '', - brand_model: '', - is_active: true - }, - tempErrorCodes: [], - existingErrorCodes: [], - currentErrorCode: null, - isLoading: false, - error: null, - lastSaved: null -}; - -// Action types -const SET_BRAND_INFO = 'SET_BRAND_INFO'; -const SET_BRAND_ID = 'SET_BRAND_ID'; -const SET_ROUTE_BRAND_ID = 'SET_ROUTE_BRAND_ID'; -const SET_ERROR_CODE_ID = 'SET_ERROR_CODE_ID'; -const SET_CURRENT_STEP = 'SET_CURRENT_STEP'; -const UPDATE_BRAND_FIELD = 'UPDATE_BRAND_FIELD'; -const ADD_ERROR_CODE = 'ADD_ERROR_CODE'; -const UPDATE_ERROR_CODE = 'UPDATE_ERROR_CODE'; -const DELETE_ERROR_CODE = 'DELETE_ERROR_CODE'; -const MARK_AS_DELETED = 'MARK_AS_DELETED'; -const SET_TEMP_ERROR_CODES = 'SET_TEMP_ERROR_CODES'; -const SET_EXISTING_ERROR_CODES = 'SET_EXISTING_ERROR_CODES'; -const MERGE_ERROR_CODES = 'MERGE_ERROR_CODES'; -const SET_CURRENT_ERROR_CODE = 'SET_CURRENT_ERROR_CODE'; -const SET_LOADING = 'SET_LOADING'; -const SET_ERROR = 'SET_ERROR'; -const RESET_FORM = 'RESET_FORM'; -const SET_LAST_SAVED = 'SET_LAST_SAVED'; - -// Reducer function -const brandFormReducer = (state, action) => { - switch (action.type) { - case SET_BRAND_INFO: - return { - ...state, - brandInfo: action.payload - }; - - case SET_BRAND_ID: - return { - ...state, - brandId: action.payload - }; - - case SET_ROUTE_BRAND_ID: - return { - ...state, - routeBrandId: action.payload - }; - - case SET_ERROR_CODE_ID: - return { - ...state, - errorCodeId: action.payload - }; - - case SET_CURRENT_STEP: - return { - ...state, - currentStep: action.payload - }; - - case UPDATE_BRAND_FIELD: - return { - ...state, - brandInfo: { - ...state.brandInfo, - [action.payload.field]: action.payload.value - } - }; - - case ADD_ERROR_CODE: - const newErrorCode = { - tempId: Date.now(), - error_code: '', - error_code_name: '', - error_code_description: '', - error_code_color: '#000000ff', - path_icon: '', - is_active: true, - solutions: [], - spareparts: [], - status: 'new', - created_at: new Date().toISOString() - }; - return { - ...state, - tempErrorCodes: [...state.tempErrorCodes, newErrorCode] - }; - - case UPDATE_ERROR_CODE: - const { tempId, data } = action.payload; - - if (tempId.startsWith('existing_')) { - return { - ...state, - existingErrorCodes: state.existingErrorCodes.map(ec => - `existing_${ec.error_code_id}` === tempId - ? { - ...ec, - ...data, - status: 'modified', - updated_at: new Date().toISOString() - } - : ec - ) - }; - } else { - // Update in tempErrorCodes - return { - ...state, - tempErrorCodes: state.tempErrorCodes.map(ec => - ec.tempId === tempId - ? { - ...ec, - ...data, - status: ec.status === 'new' ? 'new' : 'modified', - updated_at: new Date().toISOString() - } - : ec - ) - }; - } - - case DELETE_ERROR_CODE: - return { - ...state, - tempErrorCodes: state.tempErrorCodes.filter(ec => ec.tempId !== action.payload) - }; - - case MARK_AS_DELETED: - const deleteTempId = action.payload; - - // Check if it's an existing error code (format: existing_${error_code_id}) - if (deleteTempId.startsWith('existing_')) { - return { - ...state, - existingErrorCodes: state.existingErrorCodes.map(ec => - `existing_${ec.error_code_id}` === deleteTempId - ? { - ...ec, - status: 'deleted', - is_active: false, - deleted_at: new Date().toISOString() - } - : ec - ) - }; - } else { - return { - ...state, - tempErrorCodes: state.tempErrorCodes.map(ec => - ec.tempId === deleteTempId - ? { - ...ec, - status: 'deleted', - is_active: false, - deleted_at: new Date().toISOString() - } - : ec - ) - }; - } - - case SET_TEMP_ERROR_CODES: - return { - ...state, - tempErrorCodes: action.payload - }; - - case SET_EXISTING_ERROR_CODES: - return { - ...state, - existingErrorCodes: action.payload - }; - - case MERGE_ERROR_CODES: - return { - ...state, - existingErrorCodes: action.payload.existing || [], - tempErrorCodes: action.payload.temporary || [] - }; - - case SET_LOADING: - return { - ...state, - isLoading: action.payload - }; - - case SET_ERROR: - return { - ...state, - error: action.payload - }; - - case RESET_FORM: - return { ...initialState, lastSaved: state.lastSaved }; - - case SET_CURRENT_ERROR_CODE: - return { - ...state, - currentErrorCode: action.payload - }; - - case SET_LAST_SAVED: - return { - ...state, - lastSaved: action.payload - }; - - default: - return state; - } -}; - -// Create context -const BrandFormContext = createContext(); - -export const BrandFormProvider = ({ children }) => { - const [state, dispatch] = useReducer(brandFormReducer, initialState); - - // Actions - const actions = { - setBrandId: (brandId) => { - dispatch({ type: SET_BRAND_ID, payload: brandId }); - }, - - setRouteBrandId: (routeBrandId) => { - dispatch({ type: SET_ROUTE_BRAND_ID, payload: routeBrandId }); - }, - - setErrorCodeId: (errorCodeId) => { - dispatch({ type: SET_ERROR_CODE_ID, payload: errorCodeId }); - }, - - setCurrentStep: (step) => { - dispatch({ type: SET_CURRENT_STEP, payload: step }); - }, - - getCurrentBrandId: () => { - return state.brandId || state.routeBrandId; - }, - - setBrandInfo: (brandInfo) => { - dispatch({ type: SET_BRAND_INFO, payload: brandInfo }); - }, - - updateBrandField: (field, value) => { - dispatch({ type: UPDATE_BRAND_FIELD, payload: { field, value } }); - }, - - addErrorCode: (errorCodeData = {}) => { - const defaultErrorCode = { - tempId: Date.now(), - error_code: '', - error_code_name: '', - error_code_description: '', - error_code_color: '#000000ff', - path_icon: '', - is_active: true, - solutions: [], - spareparts: [], - status: 'new', - created_at: new Date().toISOString() - }; - const newErrorCode = { ...defaultErrorCode, ...errorCodeData }; - dispatch({ type: ADD_ERROR_CODE, payload: newErrorCode }); - }, - - updateErrorCode: (tempId, data) => { - dispatch({ type: UPDATE_ERROR_CODE, payload: { tempId, data } }); - }, - - deleteErrorCode: (tempId, isPermanent = false) => { - if (isPermanent) { - dispatch({ type: DELETE_ERROR_CODE, payload: tempId }); - } else { - dispatch({ type: MARK_AS_DELETED, payload: tempId }); - } - }, - - markAsDeleted: (tempId) => { - dispatch({ type: MARK_AS_DELETED, payload: tempId }); - }, - - setTempErrorCodes: (errorCodes) => { - dispatch({ type: SET_TEMP_ERROR_CODES, payload: errorCodes }); - }, - - setExistingErrorCodes: (errorCodes) => { - dispatch({ type: SET_EXISTING_ERROR_CODES, payload: errorCodes }); - }, - - mergeErrorCodes: (existing, temporary) => { - dispatch({ type: MERGE_ERROR_CODES, payload: { existing, temporary } }); - }, - - setLoading: (isLoading) => { - dispatch({ type: SET_LOADING, payload: isLoading }); - }, - - setError: (error) => { - dispatch({ type: SET_ERROR, payload: error }); - }, - - resetForm: () => { - dispatch({ type: RESET_FORM }); - }, - - setLastSaved: (timestamp) => { - dispatch({ type: SET_LAST_SAVED, payload: timestamp }); - }, - - setCurrentErrorCode: (errorCode) => { - dispatch({ type: SET_CURRENT_ERROR_CODE, payload: errorCode }); - }, - - // Initialize context with route parameters - initializeFromRoute: (routeBrandId, errorCodeId = null) => { - dispatch({ type: SET_ROUTE_BRAND_ID, payload: routeBrandId }); - if (errorCodeId) { - dispatch({ type: SET_ERROR_CODE_ID, payload: errorCodeId }); - } - }, - - // Navigate to specific step with parameter handling - navigateToStep: (step, brandId = null, errorCodeId = null) => { - dispatch({ type: SET_CURRENT_STEP, payload: step }); - if (brandId) { - dispatch({ type: SET_BRAND_ID, payload: brandId }); - } - if (errorCodeId) { - dispatch({ type: SET_ERROR_CODE_ID, payload: errorCodeId }); - } - }, - - // Utility functions - getErrorCodeByTempId: (tempId) => { - return state.tempErrorCodes.find(ec => ec.tempId === tempId); - }, - - getActiveErrorCodes: () => { - return state.tempErrorCodes.filter(ec => ec.status !== 'deleted'); - }, - - getModifiedErrorCodes: () => { - return state.tempErrorCodes.filter(ec => - ec.status === 'new' || ec.status === 'modified' || ec.status === 'deleted' - ); - }, - - hasChanges: () => { - return state.tempErrorCodes.some(ec => - ec.status === 'new' || ec.status === 'modified' || ec.status === 'deleted' - ); - }, - - validateForm: () => { - const errors = {}; - - // Validate brand info - if (!state.brandInfo.brand_name?.trim()) { - errors.brand = 'Brand name is required'; - NotifAlert({ - icon: 'warning', - title: 'Validasi Gagal', - message: 'Brand name wajib diisi!', - }); - return { isValid: false, errors }; - } - - if (!state.brandInfo.brand_manufacture?.trim()) { - errors.brand_manufacture = 'Brand manufacture is required'; - NotifAlert({ - icon: 'warning', - title: 'Validasi Gagal', - message: 'Brand manufacture wajib diisi!', - }); - return { isValid: false, errors }; - } - - const allActiveErrorCodes = [ - ...state.existingErrorCodes, - ...state.tempErrorCodes.filter(ec => ec.status !== 'deleted') - ]; - - if (allActiveErrorCodes.length === 0) { - errors.errorCodes = 'At least one error code is required'; - NotifAlert({ - icon: 'warning', - title: 'Validasi Gagal', - message: 'Brand harus memiliki minimal 1 error code!', - }); - return { isValid: false, errors }; - } - - // Validate each error code - allActiveErrorCodes.forEach((ec, index) => { - if (!ec.error_code?.trim()) { - errors[`errorCode_${ec.tempId || ec.error_code_id}`] = 'Error code is required'; - } - if (!ec.error_code_name?.trim()) { - errors[`errorCodeName_${ec.tempId || ec.error_code_id}`] = 'Error code name is required'; - } - - let solutionsToCheck = []; - if (ec.solution && Array.isArray(ec.solution)) { - solutionsToCheck = ec.solution; - } else if (ec.solutions && Array.isArray(ec.solutions)) { - solutionsToCheck = ec.solutions; - } - - if (solutionsToCheck.length > 0) { - const activeSolutions = solutionsToCheck.filter(sol => sol.status !== 'deleted'); - if (activeSolutions.length === 0) { - errors[`solutions_${ec.tempId || ec.error_code_id}`] = 'Setiap error code harus memiliki minimal 1 solution'; - } - } else { - errors[`solutions_${ec.tempId || ec.error_code_id}`] = 'Setiap error code harus memiliki minimal 1 solution'; - } - - if (ec.spareparts && Array.isArray(ec.spareparts)) { - ec.spareparts.forEach((sp, spIndex) => { - if (sp === undefined || sp === null || sp === '') { - console.error(`❌ Error Code ${index}, Sparepart ${spIndex}: sparepart_id is undefined, null, or empty`); - } - }); - } - }); - - if (Object.keys(errors).length > 0) { - NotifAlert({ - icon: 'warning', - title: 'Validasi Gagal', - message: 'Perbaiki error yang ada sebelum melanjutkan!', - }); - } - - return { isValid: Object.keys(errors).length === 0, errors }; - }, - - prepareSubmissionData: (userId) => { - const allErrorCodes = [ - ...state.existingErrorCodes.map(ec => ({ - ...ec, - tempId: `existing_${ec.error_code_id}`, - status: 'existing' - })), - ...state.tempErrorCodes - ]; - - const finalErrorCodes = allErrorCodes - .filter(ec => ec.status !== 'deleted') - .map(ec => { - const cleanedCode = { - error_code: ec.error_code || '', - error_code_name: ec.error_code_name || '', - error_code_description: ec.error_code_description || '', - error_code_color: ec.error_code_color || '#000000', - path_icon: ec.path_icon || '', - is_active: ec.is_active === true ? 1 : 0, - solution: [], - spareparts: [] - }; - - if (ec.error_code_id && ec.status === 'existing') { - cleanedCode.error_code_id = parseInt(ec.error_code_id); - } - - // Handle both solution and solutions fields for compatibility - let solutions = []; - if (ec.solution && Array.isArray(ec.solution)) { - solutions = ec.solution; - } else if (ec.solutions && Array.isArray(ec.solutions)) { - solutions = ec.solutions; - } - - if (solutions.length > 0) { - cleanedCode.solution = solutions - .filter(sol => sol && sol.solution_name) - .map(sol => ({ - solution_name: sol.solution_name || '', - type_solution: sol.type_solution || 'text', - text_solution: sol.text_solution || '', - path_solution: sol.path_solution || '', - is_active: sol.is_active === true ? 1 : 0 - })); - } - - if (ec.spareparts && Array.isArray(ec.spareparts)) { - cleanedCode.spareparts = ec.spareparts - .filter(sp => sp !== undefined && sp !== null && sp !== '' && sp !== 0) - .map(sp => { - let sparepartId = 0; - if (typeof sp === 'object' && sp !== null) { - sparepartId = sp.sparepart_id || sp.id || sp.sparepartId || 0; - } else if (typeof sp === 'string' || typeof sp === 'number') { - sparepartId = parseInt(sp) || 0; - } - return parseInt(sparepartId) || 0; - }) - .filter(id => id > 0); - } - - return cleanedCode; - }); - - const submissionData = { - brand_name: state.brandInfo.brand_name || '', - brand_type: state.brandInfo.brand_type || '', - brand_manufacture: state.brandInfo.brand_manufacture || '', - brand_model: state.brandInfo.brand_model || '', - is_active: state.brandInfo.is_active === true ? 1 : 0, - error_code: finalErrorCodes, - updated_by: parseInt(userId) || 1 - }; - - // console.log(' Prepared flat submission data:', JSON.stringify(submissionData, null, 2)); - - return submissionData; - }, - - getAllErrorCodes: () => { - return [ - ...state.existingErrorCodes.map(ec => ({ - ...ec, - tempId: `existing_${ec.error_code_id}`, - status: 'existing' - })), - ...state.tempErrorCodes - ]; - }, - - getErrorCodeById: (id) => { - const existingCode = state.existingErrorCodes.find(ec => ec.error_code_id == id); - if (existingCode) { - return { - ...existingCode, - tempId: `existing_${existingCode.error_code_id}`, - status: 'existing' - }; - } - - return state.tempErrorCodes.find(ec => ec.tempId == id); - }, - - // Enhanced error code management - loadErrorCodesForBrand: async (brandId) => { - dispatch({ type: SET_LOADING, payload: true }); - try { - const { getErrorCodesByBrandId } = await import('../api/master-brand'); - const response = await getErrorCodesByBrandId(brandId); - - if (response && response.data) { - dispatch({ type: SET_EXISTING_ERROR_CODES, payload: response.data }); - } - return response; - } catch (error) { - dispatch({ type: SET_ERROR, payload: error.message }); - throw error; - } finally { - dispatch({ type: SET_LOADING, payload: false }); - } - }, - - // Smart navigation helper - navigateToErrorCodes: (brandId) => { - const currentId = brandId || state.brandId || state.routeBrandId; - if (currentId) { - dispatch({ type: SET_BRAND_ID, payload: currentId }); - dispatch({ type: SET_CURRENT_STEP, payload: 2 }); - return currentId; - } - return null; - }, - - editErrorCode: (errorCodeId, brandId) => { - const currentBrandId = brandId || state.brandId || state.routeBrandId; - if (currentBrandId && errorCodeId) { - dispatch({ type: SET_BRAND_ID, payload: currentBrandId }); - dispatch({ type: SET_ERROR_CODE_ID, payload: errorCodeId }); - dispatch({ type: SET_CURRENT_STEP, payload: 3 }); - return { brandId: currentBrandId, errorCodeId }; - } - return null; - } - }; - - const value = { - ...state, - ...actions - }; - - return ( - - {children} - - ); -}; - -export const useBrandForm = () => { - const context = useContext(BrandFormContext); - if (!context) { - throw new Error('useBrandForm must be used within a BrandFormProvider'); - } - return context; -}; - -export default BrandFormContext; \ No newline at end of file diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx index 00d1ddc..88310a8 100644 --- a/src/pages/master/brandDevice/AddBrandDevice.jsx +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -19,10 +19,9 @@ import TableList from '../../../components/Global/TableList'; import { ConfigProvider } from 'antd'; import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; -import { createBrand } from '../../../api/master-brand'; +import { createBrand, createErrorCode, getErrorCodesByBrandId, updateErrorCode, deleteErrorCode } from '../../../api/master-brand'; import BrandForm from './component/BrandForm'; import { useSolutionLogic } from './hooks/solution'; -import { useBrandForm } from '../../../context/BrandFormContext'; const { Title } = Typography; const { Step } = Steps; @@ -32,32 +31,24 @@ const AddBrandDevice = () => { const [searchParams] = useSearchParams(); const { setBreadcrumbItems } = useBreadcrumb(); const [brandForm] = Form.useForm(); - const [solutionForm] = Form.useForm(); - const [errorCodeIcon, setErrorCodeIcon] = useState(null); - const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); const [loading, setLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const [searchValue, setSearchValue] = useState(''); const [searchText, setSearchText] = useState(''); const [trigerFilter, setTrigerFilter] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [createdBrandId, setCreatedBrandId] = useState(null); + const [brandData, setBrandData] = useState({}); + const [tempErrorCodes, setTempErrorCodes] = useState([]); - // Context integration - const { - brandId, - brandInfo, - setBrandInfo, - tempErrorCodes, - addErrorCode, - updateErrorCode, - deleteErrorCode, - prepareSubmissionData, - validateForm, - resetForm, - } = useBrandForm(); - - // Use step from query parameter or context - const tab = searchParams.get('tab'); - const [currentStep, setCurrentStep] = useState(tab === 'error-codes' ? 1 : 0); + // Error code management states + const [errorCodeForm] = Form.useForm(); + const [solutionForm] = Form.useForm(); + const [errorCodeIcon, setErrorCodeIcon] = useState(null); + const [selectedSparepartIds, setSelectedSparepartIds] = useState([]); + const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null); + const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false); + const [isAddingNewErrorCode, setIsAddingNewErrorCode] = useState(false); const { solutionFields, @@ -77,14 +68,54 @@ const AddBrandDevice = () => { // Navigation functions const handleNextStep = async () => { try { - await brandForm.validateFields(); - setCurrentStep(1); + setConfirmLoading(true); + const brandValues = await brandForm.validateFields(); + const userId = JSON.parse(localStorage.getItem('user') || '{}').user_id || 1; + + // Prepare brand data for API + const brandApiData = { + brand_name: brandValues.brand_name, + brand_type: brandValues.brand_type || '', + brand_manufacture: brandValues.brand_manufacture || '', + brand_model: brandValues.brand_model || '', + is_active: brandValues.is_active !== undefined ? brandValues.is_active : true + }; + + // Create brand via API + const response = await createBrand(brandApiData); + + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + const createdBrand = response.data; + setCreatedBrandId(createdBrand.brand_id); + setBrandData(createdBrand); + + + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Brand device berhasil dibuat. Silakan tambahkan error codes.', + }); + + setCurrentStep(1); + // Trigger refresh untuk error codes table di fase 2 + setTimeout(() => { + setTrigerFilter(prev => !prev); + }, 100); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Gagal membuat brand device', + }); + } } catch (error) { NotifAlert({ - icon: 'warning', - title: 'Perhatian', - message: 'Harap isi semua kolom wajib untuk brand device!', + icon: 'error', + title: 'Gagal', + message: error.message || 'Gagal membuat brand device', }); + } finally { + setConfirmLoading(false); } }; @@ -97,13 +128,50 @@ const AddBrandDevice = () => { }; const handleAddErrorCode = () => { - navigate(`/master/brand-device/add/error-code/add`); + if (createdBrandId) { + resetErrorCodeForm(); + setIsAddingNewErrorCode(true); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(null); + } else { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Brand device harus dibuat terlebih dahulu.', + }); + } }; - const handleEditErrorCodeNavigate = (record) => { - const errorCodeId = record.status === 'existing' ? record.error_code_id : record.tempId; - if (errorCodeId) { - navigate(`/master/brand-device/add/error-code/edit/${errorCodeId}`); + const handleEditErrorCode = (record) => { + if (createdBrandId) { + setIsAddingNewErrorCode(false); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(record.error_code_id); + + // Load error code data into form + errorCodeForm.setFieldsValue({ + error_code: record.error_code, + error_code_name: record.error_code_name || '', + error_code_description: record.error_code_description || '', + error_code_color: record.error_code_color || '#000000', + status: record.is_active !== false, + }); + + if (record.path_icon) { + setErrorCodeIcon({ + name: record.path_icon.split('/').pop(), + uploadPath: record.path_icon, + url: record.path_icon, + }); + } + + if (record.solution && record.solution.length > 0) { + setSolutionsForExistingRecord(record.solution, solutionForm); + } + + if (record.spareparts && record.spareparts.length > 0) { + setSelectedSparepartIds(record.spareparts); + } } }; @@ -112,15 +180,33 @@ const AddBrandDevice = () => { icon: 'question', title: 'Konfirmasi Hapus', message: `Apakah Anda yakin ingin menghapus error code "${record.error_code}"?`, - onConfirm: () => { - const tempId = record.tempId || `existing_${record.error_code_id}`; - deleteErrorCode(tempId, false); // false = soft delete - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil dihapus!', - }); - setTrigerFilter(prev => !prev); + onConfirm: async () => { + try { + const errorCodeToDelete = record.error_code_id; + const response = await deleteErrorCode(createdBrandId, errorCodeToDelete); + + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Error code berhasil dihapus!', + }); + setTrigerFilter(prev => !prev); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Gagal menghapus error code', + }); + } + } catch (error) { + console.error('Error deleting error code:', error); + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: error.message || 'Gagal menghapus error code', + }); + } }, onCancel: () => {} }); @@ -142,8 +228,8 @@ const AddBrandDevice = () => { }; const handleBrandFormValuesChange = useCallback((changedValues, allValues) => { - setBrandInfo(allValues); - }, [setBrandInfo]); + setBrandData(allValues); + }, []); const getErrorCodesData = async (params) => { try { @@ -151,12 +237,33 @@ const AddBrandDevice = () => { const page = parseInt(params.get('page')) || 1; const limit = parseInt(params.get('limit')) || 10; - const allErrorCodes = tempErrorCodes.filter(ec => ec.status !== 'deleted'); + let allErrorCodes = []; - let filteredData = allErrorCodes; + // Get error codes from API if brand is created + if (createdBrandId) { + const queryParams = new URLSearchParams({ + page: page.toString(), + limit: limit.toString(), + ...(search && { search }) + }); + const response = await getErrorCodesByBrandId(createdBrandId, queryParams); + if (response && response.statusCode === 200) { + const apiErrorData = response.data || []; + allErrorCodes = apiErrorData.map(ec => ({ + ...ec, + tempId: `existing_${ec.error_code_id}`, + status: 'existing' + })); + } + } + + // Add temp error codes + allErrorCodes = [...allErrorCodes, ...tempErrorCodes.filter(ec => ec.status !== 'deleted')]; + + // Filter by search text if needed if (searchText) { - filteredData = allErrorCodes.filter(ec => + allErrorCodes = allErrorCodes.filter(ec => ec.error_code.toLowerCase().includes(searchText.toLowerCase()) || ec.error_code_name.toLowerCase().includes(searchText.toLowerCase()) ); @@ -164,15 +271,15 @@ const AddBrandDevice = () => { const startIndex = 0; const endIndex = startIndex + limit; - const paginatedData = filteredData.slice(startIndex, endIndex); + const paginatedData = allErrorCodes.slice(startIndex, endIndex); return { data: paginatedData, pagination: { current_page: page, current_limit: limit, - total_limit: filteredData.length, - total_page: Math.ceil(filteredData.length / limit), + total_limit: allErrorCodes.length, + total_page: Math.ceil(allErrorCodes.length / limit), } }; } catch (error) { @@ -264,39 +371,120 @@ const AddBrandDevice = () => { return params; }, [searchValue]); - const handleFinish = async () => { - setConfirmLoading(true); + const resetErrorCodeForm = () => { + errorCodeForm.resetFields(); + errorCodeForm.setFieldsValue({ + status: true, + }); + setErrorCodeIcon(null); + resetSolutionFields(); + setIsErrorCodeFormReadOnly(false); + setEditingErrorCodeKey(null); + setSelectedSparepartIds([]); + setIsAddingNewErrorCode(false); + }; + + const handleSaveErrorCode = async () => { try { - // Validate form using context - const validation = validateForm(); - if (!validation.isValid) { + await errorCodeForm.validateFields(); + + const solutionValues = solutionForm.getFieldsValue(); + const commaPath = `solution_items,${solutionFields[0]?.key || 0}`; + const dotPath = `solution_items.${solutionFields[0]?.key || 0}`; + const firstSolution = solutionValues[commaPath] || solutionValues[dotPath]; + + 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 submissionData = prepareSubmissionData(1); + const errorCodeValues = errorCodeForm.getFieldsValue(); + const solutionData = getSolutionData(); - const response = await createBrand(submissionData); + 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 ? errorCodeValues.status : true, + solution: solutionData || [], + spareparts: selectedSparepartIds || [] + }; + + // For create, include error_code field (required) + if (isAddingNewErrorCode) { + payload.error_code = errorCodeValues.error_code; + } + + let response; + + if (isAddingNewErrorCode) { + response = await createErrorCode(createdBrandId, payload); + } else { + response = await updateErrorCode(createdBrandId, editingErrorCodeKey, payload); + } if (response && (response.statusCode === 200 || response.statusCode === 201)) { NotifOk({ icon: 'success', title: 'Berhasil', - message: response.message || 'Brand Device berhasil ditambahkan.', + message: isAddingNewErrorCode ? 'Error Code berhasil ditambahkan!' : 'Error Code berhasil diupdate!', }); - resetForm(); - navigate('/master/brand-device'); + + resetErrorCodeForm(); + setTrigerFilter(prev => !prev); } else { NotifAlert({ icon: 'error', title: 'Gagal', - message: response?.message || 'Gagal menambahkan Brand Device', + message: response?.message || 'Gagal menyimpan error code', }); } + } catch (error) { + console.error('Error saving error code:', error); + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: error.message || 'Gagal menyimpan error code. Silakan coba lagi.', + }); + } + }; + + + const handleErrorCodeIconRemove = () => { + setErrorCodeIcon(null); + }; + + + const handleFinish = async () => { + setConfirmLoading(true); + try { + // Fase 2 completion - brand sudah dibuat di fase 1 + NotifOk({ + icon: 'success', + title: 'Brand Device Tersimpan', + message: 'Brand device telah berhasil disimpan dengan error codes yang ditambahkan.', + }); + navigate('/master/brand-device'); } catch (error) { NotifAlert({ icon: 'error', title: 'Gagal', - message: error.message || 'Gagal menyimpan data. Silakan coba lagi.', + message: error.message || 'Terjadi kesalahan. Silakan coba lagi.', }); } finally { setConfirmLoading(false); @@ -391,7 +579,7 @@ const AddBrandDevice = () => { >