update ui brand device
This commit is contained in:
624
src/context/BrandFormContext.jsx
Normal file
624
src/context/BrandFormContext.jsx
Normal file
@@ -0,0 +1,624 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user