Files
cod-fe/src/context/BrandFormContext.jsx

624 lines
18 KiB
JavaScript

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 (
<BrandFormContext.Provider value={value}>
{children}
</BrandFormContext.Provider>
);
};
export const useBrandForm = () => {
const context = useContext(BrandFormContext);
if (!context) {
throw new Error('useBrandForm must be used within a BrandFormProvider');
}
return context;
};
export default BrandFormContext;