diff --git a/src/App.jsx b/src/App.jsx
index 8493d06..4581a26 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -4,6 +4,7 @@ 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';
@@ -21,6 +22,7 @@ import IndexShift from './pages/master/shift/IndexShift';
// Brand device
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
import EditBrandDevice from './pages/master/brandDevice/EditBrandDevice';
+import AddEditErrorCode from './pages/master/brandDevice/AddEditErrorCode';
import ViewBrandDevice from './pages/master/brandDevice/ViewBrandDevice';
import ViewFilePage from './pages/master/brandDevice/ViewFilePage';
@@ -91,25 +93,37 @@ const App = () => {
} />
} />
} />
- } />
- } />
- } />
- } />
- }
- />
- }
- />
- }
- />
} />
} />
} />
+
+ {/* Brand Device Routes with BrandFormProvider */}
+
+
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+
+
+ } />
}>
diff --git a/src/api/master-brand.jsx b/src/api/master-brand.jsx
index 942e4e3..99db0ba 100644
--- a/src/api/master-brand.jsx
+++ b/src/api/master-brand.jsx
@@ -47,4 +47,23 @@ const deleteBrand = async (id) => {
return response.data;
};
-export { getAllBrands, getBrandById, createBrand, updateBrand, deleteBrand };
+const getErrorCodesByBrandId = async (brandId, queryParams) => {
+ const query = queryParams ? `?${queryParams.toString()}` : '';
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `error-code/brand/${brandId}${query}`,
+ });
+
+ return response.data;
+};
+
+const getErrorCodeById = async (id) => {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `error-code/${id}`,
+ });
+
+ return response.data;
+};
+
+export { getAllBrands, getBrandById, createBrand, updateBrand, deleteBrand, getErrorCodesByBrandId, getErrorCodeById };
diff --git a/src/context/BrandFormContext.jsx b/src/context/BrandFormContext.jsx
new file mode 100644
index 0000000..66a1765
--- /dev/null
+++ b/src/context/BrandFormContext.jsx
@@ -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 (
+
+ {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 00603cf..00d1ddc 100644
--- a/src/pages/master/brandDevice/AddBrandDevice.jsx
+++ b/src/pages/master/brandDevice/AddBrandDevice.jsx
@@ -1,5 +1,5 @@
-import { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useEffect, useState, useCallback, useMemo } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
import {
Divider,
Typography,
@@ -10,53 +10,54 @@ import {
Col,
Card,
Spin,
- Table,
Tag,
Space,
+ Input,
} from 'antd';
-import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
+import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
+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 BrandForm from './component/BrandForm';
-import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm';
-import SolutionForm from './component/SolutionForm';
-import FormActions from './component/FormActions';
-import ListErrorCode from './component/ListErrorCode';
-import { useErrorCodeLogic } from './hooks/errorCode';
import { useSolutionLogic } from './hooks/solution';
-import { EditOutlined, DeleteOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons';
-import { useBrandDeviceLogic } from './hooks/useBrandDeviceLogic';
+import { useBrandForm } from '../../../context/BrandFormContext';
const { Title } = Typography;
const { Step } = Steps;
const AddBrandDevice = () => {
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
const { setBreadcrumbItems } = useBreadcrumb();
const [brandForm] = Form.useForm();
- const [errorCodeForm] = Form.useForm();
- const [confirmLoading, setConfirmLoading] = useState(false);
- const [currentStep, setCurrentStep] = useState(0);
- const [loading, setLoading] = useState(false);
- const [formData, setFormData] = useState({
- brand_name: '',
- brand_type: '',
- brand_model: '',
- brand_manufacture: '',
- is_active: true,
- });
- const [errorCodes, setErrorCodes] = useState([]);
- const [pendingErrorCodes, setPendingErrorCodes] = useState([]);
- const [errorCodeIcon, setErrorCodeIcon] = useState(null);
const [solutionForm] = Form.useForm();
+ const [errorCodeIcon, setErrorCodeIcon] = useState(null);
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
- const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null);
- const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [confirmLoading, setConfirmLoading] = useState(false);
+ const [searchValue, setSearchValue] = useState('');
+ const [searchText, setSearchText] = useState('');
+ const [trigerFilter, setTrigerFilter] = useState(false);
- const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic(
- errorCodeForm,
- []
- );
+ // 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);
const {
solutionFields,
@@ -64,7 +65,6 @@ const AddBrandDevice = () => {
solutionStatuses,
solutionsToDelete,
firstSolutionValid,
- checkFirstSolutionValid,
handleAddSolutionField,
handleRemoveSolutionField,
handleSolutionTypeChange,
@@ -74,6 +74,354 @@ const AddBrandDevice = () => {
setSolutionsForExistingRecord,
} = useSolutionLogic(solutionForm);
+ // Navigation functions
+ const handleNextStep = async () => {
+ try {
+ await brandForm.validateFields();
+ setCurrentStep(1);
+ } catch (error) {
+ NotifAlert({
+ icon: 'warning',
+ title: 'Perhatian',
+ message: 'Harap isi semua kolom wajib untuk brand device!',
+ });
+ }
+ };
+
+ const handlePrevStep = () => {
+ setCurrentStep(0);
+ };
+
+ const handleCancel = () => {
+ navigate('/master/brand-device');
+ };
+
+ const handleAddErrorCode = () => {
+ navigate(`/master/brand-device/add/error-code/add`);
+ };
+
+ 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 handleDeleteErrorCode = (record) => {
+ NotifConfirmDialog({
+ 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);
+ },
+ onCancel: () => {}
+ });
+ };
+
+ const handlePreviewErrorCode = (record) => {
+ console.log('Preview error code:', record);
+ };
+
+ const handleSearch = () => {
+ setSearchText(searchValue);
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearchClear = () => {
+ setSearchValue('');
+ setSearchText('');
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleBrandFormValuesChange = useCallback((changedValues, allValues) => {
+ setBrandInfo(allValues);
+ }, [setBrandInfo]);
+
+ const getErrorCodesData = async (params) => {
+ try {
+ const search = params.get('search') || '';
+ const page = parseInt(params.get('page')) || 1;
+ const limit = parseInt(params.get('limit')) || 10;
+
+ const allErrorCodes = tempErrorCodes.filter(ec => ec.status !== 'deleted');
+
+ let filteredData = allErrorCodes;
+
+ if (searchText) {
+ filteredData = allErrorCodes.filter(ec =>
+ ec.error_code.toLowerCase().includes(searchText.toLowerCase()) ||
+ ec.error_code_name.toLowerCase().includes(searchText.toLowerCase())
+ );
+ }
+
+ const startIndex = 0;
+ const endIndex = startIndex + limit;
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ data: paginatedData,
+ pagination: {
+ current_page: page,
+ current_limit: limit,
+ total_limit: filteredData.length,
+ total_page: Math.ceil(filteredData.length / limit),
+ }
+ };
+ } catch (error) {
+ console.error('Error getting error codes data:', error);
+ return {
+ data: [],
+ pagination: {
+ current_page: 1,
+ current_limit: 10,
+ total_limit: 0,
+ total_page: 0,
+ }
+ };
+ }
+ };
+
+ // Error code columns
+ 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}
+ {record.status === 'new' && New}
+
+ ),
+ },
+ {
+ title: 'Error Name',
+ dataIndex: 'error_code_name',
+ key: 'error_code_name',
+ width: '25%',
+ },
+ {
+ title: 'Description',
+ dataIndex: 'error_code_description',
+ key: 'error_code_description',
+ width: '30%',
+ ellipsis: true,
+ },
+ {
+ title: 'Actions',
+ key: 'actions',
+ width: '20%',
+ render: (_, record) => (
+
+ }
+ onClick={() => showPreviewModal(record)}
+ size="small"
+ />
+ }
+ onClick={() => showEditModal(record)}
+ size="small"
+ />
+ }
+ onClick={() => showDeleteDialog(record)}
+ size="small"
+ />
+
+ ),
+ },
+ ];
+
+ // Query params for table
+ const queryParams = useMemo(() => {
+ const params = new URLSearchParams();
+ params.set('page', '1');
+ params.set('limit', '10');
+ if (searchValue) {
+ params.set('search', searchValue);
+ }
+ return params;
+ }, [searchValue]);
+
+ const handleFinish = async () => {
+ setConfirmLoading(true);
+ try {
+ // Validate form using context
+ const validation = validateForm();
+ if (!validation.isValid) {
+ return;
+ }
+
+ const submissionData = prepareSubmissionData(1);
+
+ const response = await createBrand(submissionData);
+
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: response.message || 'Brand Device berhasil ditambahkan.',
+ });
+ resetForm();
+ navigate('/master/brand-device');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response?.message || 'Gagal menambahkan Brand Device',
+ });
+ }
+ } catch (error) {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: error.message || 'Gagal menyimpan data. Silakan coba lagi.',
+ });
+ } finally {
+ setConfirmLoading(false);
+ }
+ };
+
+ const renderStepContent = () => {
+ if (currentStep === 0) {
+ return (
+
+ {loading && (
+
+
+
+ )}
+
+
+ );
+ }
+
+ if (currentStep === 1) {
+ return (
+
+
+
+
+
+ {
+ const value = e.target.value;
+ setSearchText(value);
+ setSearchValue(value);
+ if (value === '') {
+ setTrigerFilter((prev) => !prev);
+ }
+ }}
+ onSearch={handleSearch}
+ allowClear={{
+ clearIcon: ✕,
+ }}
+ enterButton={
+ }
+ style={{
+ backgroundColor: '#23A55A',
+ borderColor: '#23A55A',
+ }}
+ >
+ Search
+
+ }
+ size="large"
+ />
+
+
+
+
+ }
+ onClick={handleAddErrorCode}
+ size="large"
+ >
+ Add Error Code
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ return null;
+ };
+
useEffect(() => {
setBreadcrumbItems([
{
@@ -99,390 +447,6 @@ const AddBrandDevice = () => {
]);
}, [setBreadcrumbItems, navigate]);
- const handleCancel = () => {
- navigate('/master/brand-device');
- };
-
- const handleNextStep = async () => {
- try {
- const currentFormData = await brandForm.validateFields();
-
- setFormData({
- brand_name: currentFormData.brand_name,
- brand_type: currentFormData.brand_type || '',
- brand_model: currentFormData.brand_model || '',
- brand_manufacture: currentFormData.brand_manufacture || '',
- is_active: currentFormData.is_active,
- });
-
- setCurrentStep(1);
- } catch (error) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Harap isi semua kolom wajib untuk brand device!',
- });
- }
- };
-
- const handleFinish = async () => {
- setConfirmLoading(true);
- try {
- const transformedErrorCodes = pendingErrorCodes.length > 0 ? pendingErrorCodes.map(ec => ({
- 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 || '#ad4141ff',
- path_icon: ec.path_icon || '',
- is_active: ec.status !== undefined ? ec.status : true,
- solution: (ec.solution || []).map(sol => ({
- solution_name: sol.solution_name,
- type_solution: sol.type_solution,
- text_solution: sol.text_solution || '',
- path_solution: sol.path_solution || '',
- is_active: sol.is_active
- }))
- })) : [];
-
- const brandData = {
- brand_name: formData.brand_name,
- brand_type: formData.brand_type || '',
- brand_model: formData.brand_model || '',
- brand_manufacture: formData.brand_manufacture || '',
- is_active: formData.is_active,
- spareparts: selectedSparepartIds,
- error_code: transformedErrorCodes,
- };
-
- const response = await createBrand(brandData);
-
- if (response && (response.statusCode === 200 || response.statusCode === 201)) {
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: response.message || 'Brand Device berhasil ditambahkan.',
- });
- navigate('/master/brand-device');
- } else {
- NotifAlert({
- icon: 'error',
- title: 'Gagal',
- message: response?.message || 'Gagal menambahkan Brand Device',
- });
- }
- } catch (error) {
- NotifAlert({
- icon: 'error',
- title: 'Gagal',
- message: error.message || 'Gagal menyimpan data. Silakan coba lagi.',
- });
- } finally {
- setConfirmLoading(false);
- }
- };
-
- const handlePreviewErrorCode = (record) => {
- 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,
- status: record.status,
- });
- setErrorCodeIcon(record.errorCodeIcon || null);
- setIsErrorCodeFormReadOnly(true);
- setEditingErrorCodeKey(record.key);
-
- if (record.solution && record.solution.length > 0) {
- setSolutionsForExistingRecord(record.solution, solutionForm);
- } else {
- resetSolutionFields();
- }
- };
-
- const handleEditErrorCode = (record) => {
- 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,
- status: record.status,
- });
- setErrorCodeIcon(record.errorCodeIcon || null);
- setIsErrorCodeFormReadOnly(false);
- setEditingErrorCodeKey(record.key);
-
- if (record.solution && record.solution.length > 0) {
- setSolutionsForExistingRecord(record.solution, solutionForm);
- }
-
- const formElement = document.querySelector('.ant-form');
- if (formElement) {
- formElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
- }
- };
-
- const handleAddErrorCode = async () => {
- try {
- const errorCodeValues = await errorCodeForm.validateFields();
- const solutionData = getSolutionData();
-
- // Validate error code fields
- if (!errorCodeValues.error_code || !errorCodeValues.error_code_name) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Error code dan error name wajib diisi!',
- });
- return;
- }
-
- // Validate solution data
- if (!solutionData || solutionData.length === 0) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap error code harus memiliki minimal 1 solution!',
- });
- return;
- }
-
- // Validate each solution has name
- const invalidSolution = solutionData.find(sol => !sol.solution_name || sol.solution_name.trim() === '');
- if (invalidSolution) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap solution harus memiliki nama!',
- });
- return;
- }
-
- const newErrorCode = {
- 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 ? true : errorCodeValues.status,
- solution: solutionData,
- errorCodeIcon: errorCodeIcon,
- key: editingErrorCodeKey || `temp-${Date.now()}`,
- };
-
- let updatedPendingErrorCodes;
- if (editingErrorCodeKey) {
- updatedPendingErrorCodes = pendingErrorCodes.map((item) => {
- if (item.key === editingErrorCodeKey) {
- return {
- ...item,
- ...newErrorCode,
- error_code_id: item.error_code_id || newErrorCode.error_code_id,
- };
- }
- return item;
- });
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: 'Error code berhasil diupdate!',
- });
- } else {
- updatedPendingErrorCodes = [...pendingErrorCodes, newErrorCode];
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: 'Error code berhasil ditambahkan!',
- });
- }
-
- setPendingErrorCodes(updatedPendingErrorCodes);
- setErrorCodes(updatedPendingErrorCodes);
-
- setTimeout(() => {
- resetErrorCodeForm();
- }, 100);
- } catch (error) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Harap isi semua kolom wajib (error code + minimal 1 solution)!',
- });
- }
- };
-
- const resetErrorCodeForm = () => {
- errorCodeForm.resetFields();
- errorCodeForm.setFieldsValue({
- status: true,
- });
- setErrorCodeIcon(null);
- resetSolutionFields();
- setIsErrorCodeFormReadOnly(false);
- setEditingErrorCodeKey(null);
- };
-
- const handleDeleteErrorCode = async (key) => {
- if (errorCodes.length <= 1) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap brand harus memiliki minimal 1 error code!',
- });
- return;
- }
-
- const updatedErrorCodes = errorCodes.filter((item) => item.key !== key);
- setErrorCodes(updatedErrorCodes);
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: 'Error code berhasil dihapus!',
- });
- };
-
- const handleCreateNewErrorCode = () => {
- resetErrorCodeForm();
- resetSolutionFields();
- setErrorCodeIcon(null);
- setIsErrorCodeFormReadOnly(false);
- setEditingErrorCodeKey(null);
- };
-
- const handleErrorCodeIconUpload = (iconData) => {
- setErrorCodeIcon(iconData);
- };
-
- const handleErrorCodeIconRemove = () => {
- setErrorCodeIcon(null);
- };
-
- const renderStepContent = () => {
- if (currentStep === 0) {
- return (
-
- setFormData((prev) => ({ ...prev, ...allValues }))
- }
- isEdit={false}
- selectedSparepartIds={selectedSparepartIds}
- onSparepartChange={setSelectedSparepartIds}
- showSparepartSection={true}
- />
- );
- }
-
- if (currentStep === 1) {
- return (
- <>
-
-
-
- {isErrorCodeFormReadOnly
- ? editingErrorCodeKey
- ? 'View Error Code'
- : 'Error Code Form'
- : editingErrorCodeKey
- ? 'Edit Error Code'
- : 'Error Code'}
-
- }
- size="small"
- >
-
-
-
-
-
- Solutions
-
- }
- size="small"
- >
-
-
-
-
-
- Error Codes ({errorCodes.length})
-
- }
- size="small"
- >
-
-
-
-
-
-
-
- >
- );
- }
- return null;
- };
-
return (
@@ -524,15 +488,48 @@ const AddBrandDevice = () => {
- setCurrentStep(currentStep - 1)}
- onNextStep={handleNextStep}
- onSave={handleFinish}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- isEditMode={false}
- />
+
+
+
+ {currentStep === 1 && (
+
+ )}
+
+
+ {currentStep === 0 && (
+
+ )}
+ {currentStep === 1 && (
+
+ )}
+
+
);
};
diff --git a/src/pages/master/brandDevice/AddEditErrorCode.jsx b/src/pages/master/brandDevice/AddEditErrorCode.jsx
new file mode 100644
index 0000000..99af522
--- /dev/null
+++ b/src/pages/master/brandDevice/AddEditErrorCode.jsx
@@ -0,0 +1,588 @@
+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: • Master
+ },
+ {
+ title: (
+ navigate('/master/brand-device')}
+ >
+ Brand Device
+
+ ),
+ },
+ {
+ title: (
+ navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`)}
+ >
+ Edit Brand Device
+
+ ),
+ },
+ {
+ title: (
+
+ {isEditMode ? 'Edit Error Code' : 'Add Error Code'}
+
+ ),
+ },
+ ]);
+
+ 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 (
+
+ {/* Header */}
+
+
+ {isEdit ? 'Edit Error Code' : 'Add Error Code'}
+
+ }
+ onClick={handleCancel}
+ >
+ Back to Brand Device
+
+
+
+ {/* Content */}
+
+ {loading && (
+
+
+
+ )}
+
+
+ {/* Error Code Form */}
+
+
+
+
+
+
+ {/* Solutions Form */}
+
+
+
+
+
+
+ {/* Sparepart Selection */}
+
+
+
+
+
+
+
+ {/* Save Button */}
+
+
+
+
+
+
+ );
+};
+
+export default AddEditErrorCode;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx
index b69325f..7a79e97 100644
--- a/src/pages/master/brandDevice/EditBrandDevice.jsx
+++ b/src/pages/master/brandDevice/EditBrandDevice.jsx
@@ -1,5 +1,5 @@
-import { useEffect, useState } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import { useEffect, useState, useCallback, useMemo } from 'react';
+import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom';
import {
Divider,
Typography,
@@ -10,10 +10,16 @@ import {
Col,
Card,
Spin,
+ Tag,
+ Space,
+ Input,
} from 'antd';
-import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
+import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
+import TableList from '../../../components/Global/TableList';
+import { ConfigProvider } from 'antd';
+import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
-import { getBrandById, updateBrand } from '../../../api/master-brand';
+import { getBrandById, updateBrand, getErrorCodesByBrandId } from '../../../api/master-brand';
import { getFileUrl } from '../../../api/file-uploads';
import BrandForm from './component/BrandForm';
import ErrorCodeSimpleForm from './component/ErrorCodeSimpleForm';
@@ -22,6 +28,8 @@ import FormActions from './component/FormActions';
import ListErrorCode from './component/ListErrorCode';
import { useSolutionLogic } from './hooks/solution';
import { useBrandDeviceLogic } from './hooks/useBrandDeviceLogic';
+import { useBrandForm } from '../../../context/BrandFormContext';
+import SingleSparepartSelect from './component/SingleSparepartSelect';
const { Title } = Typography;
const { Step } = Steps;
@@ -29,41 +37,53 @@ 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 [formData, setFormData] = useState({
- brand_name: '',
- brand_type: '',
- brand_model: '',
- brand_manufacture: '',
- is_active: true,
- });
+ const [loading, setLoading] = useState(false);
+ const [confirmLoading, setConfirmLoading] = useState(false);
- // Custom hooks for edit mode
+ // Context integration
const {
- confirmLoading,
- setConfirmLoading,
- currentStep,
- setCurrentStep,
- loading,
- setLoading,
- errorCodes,
- setErrorCodes,
- pendingErrorCodes,
- setPendingErrorCodes,
- editingErrorCodeKey,
- setEditingErrorCodeKey,
- isErrorCodeFormReadOnly,
- setIsErrorCodeFormReadOnly,
- handleAddErrorCode,
- handleDeleteErrorCode,
- } = useBrandDeviceLogic(true, id);
+ routeBrandId,
+ setRouteBrandId,
+ initializeFromRoute,
+ navigateToErrorCodes,
+ editErrorCode,
+ isLoading: contextLoading
+ } = useBrandForm();
+
+ // Use step from query parameter or context
+ 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 [searchValue, setSearchValue] = useState('');
+
+ const {
+ brandId,
+ brandInfo,
+ setBrandInfo,
+ setBrandId,
+ tempErrorCodes,
+ existingErrorCodes,
+ addErrorCode,
+ updateErrorCode,
+ deleteErrorCode,
+ setExistingErrorCodes,
+ prepareSubmissionData,
+ validateForm,
+ resetForm,
+ } = useBrandForm();
-
const {
solutionFields,
solutionTypes,
@@ -88,6 +108,8 @@ const EditBrandDevice = () => {
return;
}
+ const tab = searchParams.get('tab') || 'brand';
+
setBreadcrumbItems([
{
title: • Master
@@ -117,52 +139,40 @@ const EditBrandDevice = () => {
if (response && response.statusCode === 200) {
const brandData = response.data;
- const newFormData = {
+
+ const brandInfoData = {
brand_name: brandData.brand_name,
brand_type: brandData.brand_type || '',
- brand_model: brandData.brand_model || '',
brand_manufacture: brandData.brand_manufacture || '',
- is_active: brandData.is_active,
+ brand_model: brandData.brand_model || '',
+ is_active: brandData.is_active
};
- const existingErrorCodes = brandData.error_code
- ? brandData.error_code.map((ec) => ({
- key: `existing-${ec.error_code_id}`,
- error_code_id: ec.error_code_id,
- 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 || '',
- status: ec.is_active,
- solution: ec.solution || [],
- errorCodeIcon: ec.path_icon
- ? {
- name: ec.path_icon.split('/').pop(),
- uploadPath: ec.path_icon,
- url: (() => {
- const pathParts = ec.path_icon.split('/');
- const folder = pathParts[0];
- const filename = pathParts.slice(1).join('/');
- return getFileUrl(folder, filename);
- })(),
- type_solution: 'image',
- }
- : null,
- }))
- : [];
+ setBrandInfo(brandInfoData);
+ setBrandId(brandData.brand_id);
+ brandForm.setFieldsValue(brandInfoData);
- setFormData(newFormData);
- brandForm.setFieldsValue(newFormData);
- setErrorCodes(existingErrorCodes);
- setPendingErrorCodes(existingErrorCodes);
-
- if (brandData.spareparts && brandData.spareparts.length > 0) {
- const sparepartIds = brandData.spareparts.map(sp => sp.sparepart_id);
- setSelectedSparepartIds(sparepartIds);
- } else {
- setSelectedSparepartIds([]);
+ if (brandData.brand_id) {
+ try {
+ const errorCodesResponse = await getErrorCodesByBrandId(brandId || 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) {
+ console.error('Error fetching error codes:', error);
+ }
}
+
+ setCurrentStep(tab === 'brand' ? 0 : 1);
} else {
NotifAlert({
icon: 'error',
@@ -182,21 +192,35 @@ const EditBrandDevice = () => {
};
fetchBrandData();
- }, [id, setBreadcrumbItems, navigate, brandForm]);
+ }, [id, navigate]);
+
+ useEffect(() => {
+ const tab = searchParams.get('tab') || 'brand';
+ setCurrentStep(tab === 'brand' ? 0 : 1);
+ }, [searchParams]);
+
+ // Initialize context with route parameters
+ useEffect(() => {
+ if (id) {
+ initializeFromRoute(id);
+ setBrandId(id);
+ }
+ }, [id, initializeFromRoute, setBrandId]);
+
+ useEffect(() => {
+ if (currentStep === 1 && brandId) {
+ setTrigerFilter(prev => !prev);
+ }
+ }, [currentStep, brandId]);
const handleNextStep = async () => {
try {
- const currentFormData = await brandForm.validateFields();
-
- setFormData({
- brand_name: currentFormData.brand_name,
- brand_type: currentFormData.brand_type || '',
- brand_model: currentFormData.brand_model || '',
- brand_manufacture: currentFormData.brand_manufacture || '',
- is_active: currentFormData.is_active,
- });
-
- setCurrentStep(1);
+ await brandForm.validateFields();
+ const currentBrandId = brandId || id;
+ if (currentBrandId) {
+ navigateToErrorCodes(currentBrandId);
+ navigate(`/master/brand-device/edit/${currentBrandId}?tab=error-codes`);
+ }
} catch (error) {
NotifAlert({
icon: 'warning',
@@ -211,46 +235,17 @@ const EditBrandDevice = () => {
};
const handleFinish = async () => {
- const currentFormData = formData;
-
- if (!currentFormData.brand_name || currentFormData.brand_name.trim() === '' ||
- !currentFormData.brand_manufacture || currentFormData.brand_manufacture.trim() === '') {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Harap lengkapi semua field wajib diisi (Brand Name dan Manufacturer)!',
- });
+ const validation = validateForm();
+ if (!validation.isValid) {
return;
}
setConfirmLoading(true);
try {
- const brandUpdateData = {
- brand_name: currentFormData.brand_name,
- brand_type: currentFormData.brand_type || '',
- brand_model: currentFormData.brand_model || '',
- brand_manufacture: currentFormData.brand_manufacture || '',
- is_active: currentFormData.is_active,
- spareparts: selectedSparepartIds,
- error_code: pendingErrorCodes.length > 0 ? pendingErrorCodes.map(ec => ({
- 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.status !== undefined ? ec.status : true,
- what_action_to_take: ec.what_action_to_take || '',
- solution: (ec.solution || []).map(sol => ({
- solution_name: sol.solution_name,
- type_solution: sol.type_solution,
- text_solution: sol.text_solution || '',
- path_solution: sol.path_solution || '',
- is_active: sol.is_active
- }))
- })) : []
- };
+ const userId = JSON.parse(localStorage.getItem('user') || '{}').user_id || 1;
+ const submissionData = prepareSubmissionData(userId);
- const response = await updateBrand(id, brandUpdateData);
+ const response = await updateBrand(id, submissionData);
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
NotifOk({
@@ -258,6 +253,7 @@ const EditBrandDevice = () => {
title: 'Berhasil',
message: response.message || 'Brand Device dan Error Codes berhasil diupdate.',
});
+ resetForm();
navigate('/master/brand-device');
} else {
NotifAlert({
@@ -294,22 +290,18 @@ const EditBrandDevice = () => {
resetSolutionFields();
setIsErrorCodeFormReadOnly(false);
setEditingErrorCodeKey(null);
+ setSelectedSparepartIds([]);
};
const handleCreateNewErrorCode = () => {
resetErrorCodeForm();
- resetSolutionFields();
- setIsErrorCodeFormReadOnly(false);
- setEditingErrorCodeKey(null);
};
- // Local wrapper for handleAddErrorCode from useBrandDeviceLogic
- const handleAddErrorCodeLocal = async () => {
+ const handleSaveErrorCode = async () => {
try {
const errorCodeValues = await errorCodeForm.validateFields();
const solutionData = getSolutionData();
- // Validate error code fields
if (!errorCodeValues.error_code || !errorCodeValues.error_code_name) {
NotifAlert({
icon: 'warning',
@@ -319,7 +311,6 @@ const EditBrandDevice = () => {
return;
}
- // Validate solution data
if (!solutionData || solutionData.length === 0) {
NotifAlert({
icon: 'warning',
@@ -329,17 +320,6 @@ const EditBrandDevice = () => {
return;
}
- // Validate each solution has name
- const invalidSolution = solutionData.find(sol => !sol.solution_name || sol.solution_name.trim() === '');
- if (invalidSolution) {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap solution harus memiliki nama!',
- });
- return;
- }
-
const newErrorCode = {
error_code: errorCodeValues.error_code,
error_code_name: errorCodeValues.error_code_name,
@@ -348,29 +328,18 @@ const EditBrandDevice = () => {
path_icon: errorCodeIcon?.uploadPath || '',
is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status,
solution: solutionData,
- errorCodeIcon: errorCodeIcon,
- key: editingErrorCodeKey || `temp-${Date.now()}`,
+ spareparts: selectedSparepartIds
};
- let updatedPendingErrorCodes;
if (editingErrorCodeKey) {
- updatedPendingErrorCodes = pendingErrorCodes.map((item) => {
- if (item.key === editingErrorCodeKey) {
- return {
- ...item,
- ...newErrorCode,
- error_code_id: item.error_code_id || newErrorCode.error_code_id,
- };
- }
- return item;
- });
+ updateErrorCode(editingErrorCodeKey, newErrorCode);
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Error code berhasil diupdate!',
});
} else {
- updatedPendingErrorCodes = [...pendingErrorCodes, newErrorCode];
+ addErrorCode(newErrorCode);
NotifOk({
icon: 'success',
title: 'Berhasil',
@@ -378,183 +347,452 @@ const EditBrandDevice = () => {
});
}
- setPendingErrorCodes(updatedPendingErrorCodes);
- setErrorCodes(updatedPendingErrorCodes);
-
- setTimeout(() => {
- resetErrorCodeForm();
- }, 100);
+ resetErrorCodeForm();
} catch (error) {
NotifAlert({
icon: 'warning',
title: 'Perhatian',
- message: 'Harap isi semua kolom wajib (error code + minimal 1 solution)!',
+ message: 'Harap isi semua kolom wajib!',
});
}
};
const handlePreviewErrorCode = (record) => {
- 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,
- status: record.status,
- });
- setErrorCodeIcon(record.errorCodeIcon || null);
- setIsErrorCodeFormReadOnly(true);
- setEditingErrorCodeKey(record.key);
+ const errorCode = getErrorCodeById(record.tempId || record.error_code_id);
+ if (errorCode) {
+ errorCodeForm.setFieldsValue({
+ 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,
+ status: errorCode.is_active,
+ });
+ setErrorCodeIcon(errorCode.path_icon ? {
+ name: errorCode.path_icon.split('/').pop(),
+ uploadPath: errorCode.path_icon,
+ } : null);
+ setIsErrorCodeFormReadOnly(true);
+ setEditingErrorCodeKey(errorCode.tempId);
- if (record.solution && record.solution.length > 0) {
- setSolutionsForExistingRecord(record.solution, solutionForm);
- } else {
- resetSolutionFields();
+ if (errorCode.solution && errorCode.solution.length > 0) {
+ setSolutionsForExistingRecord(errorCode.solution, solutionForm);
+ } else {
+ resetSolutionFields();
+ }
+
+ if (errorCode.spareparts && errorCode.spareparts.length > 0) {
+ setSelectedSparepartIds(errorCode.spareparts);
+ }
}
};
const handleEditErrorCode = (record) => {
- 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,
- status: record.status,
- });
- setErrorCodeIcon(record.errorCodeIcon || null);
- setIsErrorCodeFormReadOnly(false);
- setEditingErrorCodeKey(record.key);
+ const errorCode = getErrorCodeById(record.tempId || record.error_code_id);
+ if (errorCode) {
+ errorCodeForm.setFieldsValue({
+ 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,
+ status: errorCode.is_active,
+ });
+ setErrorCodeIcon(errorCode.path_icon ? {
+ name: errorCode.path_icon.split('/').pop(),
+ uploadPath: errorCode.path_icon,
+ } : null);
+ setIsErrorCodeFormReadOnly(false);
+ setEditingErrorCodeKey(errorCode.tempId);
- if (record.solution && record.solution.length > 0) {
- setSolutionsForExistingRecord(record.solution, solutionForm);
+ if (errorCode.solution && errorCode.solution.length > 0) {
+ setSolutionsForExistingRecord(errorCode.solution, solutionForm);
+ }
+
+ if (errorCode.spareparts && errorCode.spareparts.length > 0) {
+ setSelectedSparepartIds(errorCode.spareparts);
+ }
+ }
+ };
+
+ const handleEditErrorCodeNavigate = (record) => {
+ const errorCodeId = record.status === 'existing' ? record.error_code_id : record.tempId;
+ const currentBrandId = brandId || id;
+ if (errorCodeId && currentBrandId) {
+ navigate(`/master/brand-device/${currentBrandId}/error-code/edit/${errorCodeId}`);
+ }
+ };
+
+ const handleDeleteErrorCode = (record) => {
+ NotifConfirmDialog({
+ icon: 'question',
+ title: 'Konfirmasi Hapus',
+ message: `Apakah Anda yakin ingin menghapus error code "${record.error_code}"?`,
+ onConfirm: () => {
+ // Use soft delete (mark as deleted) instead of permanent delete
+ 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);
+ },
+ onCancel: () => {}
+ });
+ };
+
+ const mergedErrorCodes = useMemo(() => {
+ const allErrorCodes = [
+ ...existingErrorCodes.map(ec => ({ ...ec, status: 'existing' })),
+ ...tempErrorCodes
+ ];
+
+ if (searchText) {
+ return allErrorCodes.filter(ec =>
+ ec.error_code.toLowerCase().includes(searchText.toLowerCase()) ||
+ ec.error_code_name.toLowerCase().includes(searchText.toLowerCase())
+ );
}
- const formElement = document.querySelector('.ant-form');
- if (formElement) {
- formElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ return allErrorCodes;
+ }, [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}
+ {record.status === 'new' && New}
+ {record.status === 'modified' && Modified}
+
+ ),
+ },
+ {
+ title: 'Error Name',
+ dataIndex: 'error_code_name',
+ key: 'error_code_name',
+ width: '25%',
+ },
+ {
+ title: 'Description',
+ dataIndex: 'error_code_description',
+ key: 'error_code_description',
+ width: '25%',
+ render: (text) => text || '-',
+ },
+ {
+ title: 'Solutions',
+ dataIndex: 'solution',
+ key: 'solutions',
+ width: '10%',
+ align: 'center',
+ render: (solutions) => {
+ const count = Array.isArray(solutions) ? solutions.length : 0;
+ return count;
+ },
+ },
+ {
+ 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) => (
+
+ }
+ onClick={() => showPreviewModal(record)}
+ style={{
+ color: '#1890ff',
+ borderColor: '#1890ff',
+ }}
+ />
+ }
+ onClick={() => showEditModal(record)}
+ style={{
+ color: '#faad14',
+ borderColor: '#faad14',
+ }}
+ />
+ }
+ onClick={() => showDeleteDialog(record)}
+ style={{
+ borderColor: '#ff4d4f',
+ }}
+ />
+
+ ),
+ },
+ ];
+
+ const handleBrandFormValuesChange = useCallback((changedValues, allValues) => {
+ setBrandInfo(allValues);
+ }, [setBrandInfo]);
+
+ const handleSearch = () => {
+ setSearchText(searchValue);
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearchClear = () => {
+ setSearchValue('');
+ setSearchText('');
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const getErrorCodesData = async (params) => {
+ try {
+ const search = params.get('search') || '';
+ const page = parseInt(params.get('page')) || 1;
+ const limit = parseInt(params.get('limit')) || 10;
+ const currentBrandId = brandId || id;
+
+ if (!currentBrandId) {
+ console.warn('Brand ID is not available');
+ return {
+ data: [],
+ pagination: {
+ current_page: page,
+ current_limit: limit,
+ total_limit: 0,
+ total_page: 0,
+ }
+ };
+ }
+
+ const queryParams = new URLSearchParams({
+ page: page.toString(),
+ limit: limit.toString(),
+ ...(search && { search })
+ });
+
+ const response = await getErrorCodesByBrandId(currentBrandId, queryParams);
+
+ if (response && response.statusCode === 200) {
+ const apiData = response.data || [];
+
+ const contextExistingMap = new Map();
+ existingErrorCodes.forEach(ec => {
+ if (ec.status === 'modified' || ec.status === 'deleted') {
+ contextExistingMap.set(ec.error_code_id, ec);
+ }
+ });
+
+ const existingCodes = apiData.map(ec => {
+ const contextModified = contextExistingMap.get(ec.error_code_id);
+ if (contextModified) {
+ return {
+ ...contextModified,
+ tempId: `existing_${ec.error_code_id}`,
+ solution: contextModified.solution || ec.solution || [],
+ spareparts: contextModified.spareparts || ec.spareparts || []
+ };
+ } else {
+ // Use original API data
+ return {
+ ...ec,
+ tempId: `existing_${ec.error_code_id}`,
+ status: 'existing',
+ solution: ec.solution || [],
+ spareparts: ec.spareparts || []
+ };
+ }
+ });
+
+ // Filter out deleted error codes
+ const activeExistingCodes = existingCodes.filter(ec => ec.status !== 'deleted');
+
+ const allErrorCodes = [...activeExistingCodes, ...tempErrorCodes.filter(ec => ec.status !== 'deleted')];
+
+ let filteredData = allErrorCodes;
+
+ if (search) {
+ filteredData = allErrorCodes.filter(ec =>
+ ec.error_code.toLowerCase().includes(search.toLowerCase()) ||
+ ec.error_code_name.toLowerCase().includes(search.toLowerCase())
+ );
+ }
+
+ const startIndex = 0;
+ const endIndex = startIndex + limit;
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ data: paginatedData,
+ pagination: {
+ current_page: page,
+ current_limit: limit,
+ total_limit: filteredData.length,
+ total_page: Math.ceil(filteredData.length / limit),
+ }
+ };
+ }
+
+ return {
+ data: [],
+ pagination: {
+ current_page: page,
+ current_limit: limit,
+ total_limit: 0,
+ total_page: 0,
+ }
+ };
+ } catch (error) {
+ console.error('Error fetching error codes:', error);
+ return {
+ data: [],
+ pagination: {
+ current_page: 1,
+ current_limit: 10,
+ total_limit: 0,
+ total_page: 0,
+ }
+ };
}
};
const renderStepContent = () => {
if (currentStep === 0) {
return (
-
- setFormData((prev) => ({ ...prev, ...allValues }))
- }
- isEdit={true}
- selectedSparepartIds={selectedSparepartIds}
- onSparepartChange={setSelectedSparepartIds}
- showSparepartSection={true}
- />
+
+ {loading && (
+
+
+
+ )}
+
+
);
}
if (currentStep === 1) {
+ const queryParams = new URLSearchParams();
+ if (searchText) {
+ queryParams.set('search', searchText);
+ }
+
return (
- <>
-
-
-
- {isErrorCodeFormReadOnly
- ? editingErrorCodeKey
- ? 'View Error Code'
- : 'Error Code Form'
- : editingErrorCodeKey
- ? 'Edit Error Code'
- : 'Error Code'}
-
- }
- size="small"
- >
-
-
-
-
-
- Solutions
-
- }
- size="small"
- >
-
-
-
-
-
- Error Codes ({errorCodes.length})
-
- }
- size="small"
- >
-
-
-
-
-
+ onSearch={handleSearch}
+ allowClear={{
+ clearIcon: ✕,
+ }}
+ enterButton={
+ }
+ style={{
+ backgroundColor: '#23A55A',
+ borderColor: '#23A55A',
+ }}
+ >
+ Search
+
+ }
+ size="large"
+ />
+
+
+
+
+ }
+ onClick={() => navigate(`/master/brand-device/${brandId || id}/error-code/add`)}
+ size="large"
+ >
+ Add Error Code
+
+
+
+
+
+
+
+
- >
+
);
}
return null;
@@ -569,47 +807,50 @@ const EditBrandDevice = () => {
-
- {loading && (
-
-
-
- )}
-
- {renderStepContent()}
+ {renderStepContent()}
+
+
+
+
+ {currentStep === 1 && (
+
+ )}
+
+
+ {currentStep === 0 && (
+
+ )}
+ {currentStep === 1 && (
+
+ )}
-
-
setCurrentStep(currentStep - 1)}
- onNextStep={handleNextStep}
- onSave={handleFinish}
- onCancel={handleCancel}
- confirmLoading={confirmLoading}
- isEditMode={true}
- />
);
};
diff --git a/src/pages/master/brandDevice/component/BrandForm.jsx b/src/pages/master/brandDevice/component/BrandForm.jsx
index 129cc50..c88bc75 100644
--- a/src/pages/master/brandDevice/component/BrandForm.jsx
+++ b/src/pages/master/brandDevice/component/BrandForm.jsx
@@ -1,21 +1,14 @@
-import React, { useState } from 'react';
-import { Form, Input, Row, Col, Typography, Switch, Button, Card, Divider } from 'antd';
-import { PlusOutlined } from '@ant-design/icons';
-import SingleSparepartSelect from './SingleSparepartSelect';
+import React from 'react';
+import { Form, Input, Row, Col, Typography, Switch } from 'antd';
const { Text } = Typography;
const BrandForm = ({
form,
- formData,
onValuesChange,
isEdit = false,
- selectedSparepartIds = [],
- onSparepartChange,
- showSparepartSection = false
}) => {
- const isActive = Form.useWatch('is_active', form) ?? formData.is_active ?? true;
- const [showSparepart, setShowSparepart] = useState(showSparepartSection);
+ const isActive = Form.useWatch('is_active', form) ?? true;
return (
@@ -23,7 +16,13 @@ const BrandForm = ({
layout="vertical"
form={form}
onValuesChange={onValuesChange}
- initialValues={formData}
+ initialValues={{
+ brand_name: '',
+ brand_type: '',
+ brand_model: '',
+ brand_manufacture: '',
+ is_active: true,
+ }}
>
@@ -83,39 +82,6 @@ const BrandForm = ({
-
-
-
- {/* Add Sparepart Button */}
-
- }
- onClick={() => setShowSparepart(!showSparepart)}
- style={{
- width: '100%',
- borderStyle: 'dashed',
- marginBottom: showSparepart ? 16 : 0
- }}
- >
- {showSparepart ? 'Hide Sparepart' : 'Add Sparepart'}
-
-
-
- {/* Sparepart Selection Section */}
- {showSparepart && (
-
-
-
- )}
);
};
diff --git a/src/pages/master/brandDevice/component/CustomSparepartCard.jsx b/src/pages/master/brandDevice/component/CustomSparepartCard.jsx
new file mode 100644
index 0000000..f3c10b5
--- /dev/null
+++ b/src/pages/master/brandDevice/component/CustomSparepartCard.jsx
@@ -0,0 +1,483 @@
+import React, { useState } from 'react';
+import { Card, Typography, Tag, Button, Modal, Row, Col, Space } from 'antd';
+import { EyeOutlined, DeleteOutlined, CheckOutlined } from '@ant-design/icons';
+import dayjs from 'dayjs';
+
+const { Text, Title } = Typography;
+
+const CustomSparepartCard = ({
+ sparepart,
+ isSelected = false,
+ isReadOnly = false,
+ showPreview = true,
+ showDelete = false,
+ onPreview,
+ onDelete,
+ onCardClick,
+ loading = false,
+ size = 'small',
+ style = {},
+}) => {
+ const [previewModalVisible, setPreviewModalVisible] = useState(false);
+
+ // Construct image source with proper fallback
+ const getImageSrc = () => {
+ if (sparepart.sparepart_foto) {
+ if (sparepart.sparepart_foto.startsWith('http')) {
+ return sparepart.sparepart_foto;
+ } else {
+ const fileName = sparepart.sparepart_foto.split('/').pop();
+ if (fileName === 'defaultSparepartImg.jpg') {
+ return `/assets/defaultSparepartImg.jpg`;
+ } else {
+ const token = localStorage.getItem('token');
+ const baseURL = import.meta.env.VITE_API_SERVER || '';
+ return `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`;
+ }
+ }
+ }
+ return 'https://via.placeholder.com/150';
+ };
+
+ const handlePreview = () => {
+ if (onPreview) {
+ onPreview(sparepart);
+ } else {
+ setPreviewModalVisible(true);
+ }
+ };
+
+ const handleCardClick = () => {
+ if (!isReadOnly && onCardClick) {
+ onCardClick(sparepart);
+ }
+ };
+
+ const getCardActions = () => {
+ const actions = [];
+
+ // Preview button
+ if (showPreview) {
+ actions.push(
+ }
+ title="Lihat Detail"
+ style={{ color: '#1890ff' }}
+ onClick={(e) => {
+ e.stopPropagation();
+ handlePreview();
+ }}
+ />
+ );
+ }
+
+ // Delete button without confirmation
+ if (showDelete && !isReadOnly) {
+ actions.push(
+ }
+ title="Hapus"
+ style={{ color: '#ff4d4f' }}
+ onClick={(e) => {
+ e.stopPropagation();
+ onDelete?.(sparepart);
+ }}
+ />
+ );
+ }
+
+ return actions;
+ };
+
+ // Get card styling based on size
+ const getCardStyle = () => {
+ const baseStyle = {
+ borderRadius: '12px',
+ overflow: 'hidden',
+ border: isSelected ? '2px solid #1890ff' : '1px solid #E0E0E0',
+ cursor: isReadOnly ? 'default' : 'pointer',
+ position: 'relative',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
+ transition: 'all 0.3s ease'
+ };
+
+ switch (size) {
+ case 'small':
+ return {
+ ...baseStyle,
+ height: '180px',
+ minHeight: '180px'
+ };
+ case 'large':
+ return {
+ ...baseStyle,
+ height: '280px',
+ minHeight: '280px'
+ };
+ default:
+ return {
+ ...baseStyle,
+ height: '220px',
+ minHeight: '220px'
+ };
+ }
+ };
+
+ return (
+ <>
+
+
+ {/* Image Section */}
+
+ {sparepart.sparepart_item_type && (
+
+ {sparepart.sparepart_item_type}
+
+ )}
+
+
})
{
+ e.target.src = 'https://via.placeholder.com/75';
+ }}
+ />
+
+
+ {/* Selection Indicator */}
+ {isSelected && (
+
+
+
+ )}
+
+
+ {/* Content Section */}
+
+
+
+ {sparepart.sparepart_name || sparepart.name || 'Unnamed'}
+
+
+ {size !== 'small' && (
+ <>
+
+ Stock: {sparepart.sparepart_stock || sparepart.sparepart_stok || '0'} {sparepart.sparepart_unit || 'pcs'}
+
+
+
+
+ {sparepart.sparepart_code || 'No code'}
+
+
+ >
+ )}
+
+ {size === 'small' && (
+
+ {sparepart.sparepart_code || 'No code'}
+
+ )}
+
+ {(sparepart.sparepart_merk || sparepart.sparepart_model) && (
+
+ {sparepart.sparepart_merk && (
+
Brand: {sparepart.sparepart_merk}
+ )}
+ {sparepart.sparepart_model && (
+
Model: {sparepart.sparepart_model}
+ )}
+
+ )}
+
+
+
+ {sparepart.updated_at && dayjs(sparepart.updated_at).format('DD MMM YYYY')}
+
+
+
+
+
+ {/* Preview Modal */}
+ setPreviewModalVisible(false)}
+ footer={[
+
+ ]}
+ width={700}
+ centered
+ bodyStyle={{ padding: '24px' }}
+ >
+
+
+
+
+
})
{
+ e.target.src = 'https://via.placeholder.com/200x200/d9d9d9/666666?text=No+Image';
+ }}
+ />
+
+
+ {sparepart.sparepart_item_type && (
+
+ {sparepart.sparepart_item_type}
+
+ )}
+
+
+
+
+
+
+ {sparepart.sparepart_name || 'Unnamed'}
+
+
+
+
+
+
+ Code:
+
+
+ {sparepart.sparepart_code || 'N/A'}
+
+
+
+
+
+
+ Status:
+
+
+ {sparepart.is_active ? 'Active' : 'Inactive'}
+
+
+
+
+
+ {sparepart.sparepart_description && (
+
+
+ Description:
+
+
+ {sparepart.sparepart_description}
+
+
+ )}
+
+
+
+ Stock:
+
+
+ {sparepart.sparepart_stock || sparepart.sparepart_stok || '0'}
+ {sparepart.sparepart_unit ? ` ${sparepart.sparepart_unit}` : ' units'}
+
+
+
+
+ {sparepart.sparepart_merk && (
+
+
+
+ Brand:
+
+
+ {sparepart.sparepart_merk}
+
+
+
+ )}
+ {sparepart.sparepart_model && (
+
+
+
+ Model:
+
+
+ {sparepart.sparepart_model}
+
+
+
+ )}
+ {sparepart.sparepart_unit && (
+
+
+
+ Unit:
+
+
+ {sparepart.sparepart_unit}
+
+
+
+ )}
+
+
+ {sparepart.updated_at && (
+
+
+ Last updated: {dayjs(sparepart.updated_at).format('DD MMMM YYYY, HH:mm')}
+
+
+ )}
+
+
+
+
+ >
+ );
+};
+
+export default CustomSparepartCard;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/component/ErrorCodeTable.jsx b/src/pages/master/brandDevice/component/ErrorCodeTable.jsx
new file mode 100644
index 0000000..b75e327
--- /dev/null
+++ b/src/pages/master/brandDevice/component/ErrorCodeTable.jsx
@@ -0,0 +1,297 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Button, Col, Row, Space, Input, ConfigProvider, Card, Tag, Spin, Modal, Form, Typography } from 'antd';
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ SearchOutlined,
+ EyeOutlined,
+ SolutionOutlined,
+ ToolOutlined,
+} from '@ant-design/icons';
+import { NotifAlert, NotifConfirmDialog, NotifOk } from '../../../../components/Global/ToastNotif';
+import TableList from '../../../../components/Global/TableList';
+
+const { Title } = Typography;
+
+const columns = (onView, onEdit, onDelete) => [
+ {
+ title: 'No',
+ key: 'no',
+ width: '5%',
+ align: 'center',
+ render: (_, __, index) => index + 1,
+ },
+ {
+ title: 'Error Code',
+ dataIndex: 'error_code',
+ key: 'error_code',
+ width: '15%',
+ render: (text, record) => (
+
+ {record.path_icon && (
+

+ )}
+
+ {text}
+
+
+ ),
+ },
+ {
+ title: 'Error Name',
+ dataIndex: 'error_code_name',
+ key: 'error_code_name',
+ width: '25%',
+ },
+ {
+ title: 'Description',
+ dataIndex: 'error_code_description',
+ key: 'error_code_description',
+ width: '20%',
+ render: (text) => text || '-',
+ ellipsis: true,
+ },
+ {
+ title: 'Solutions',
+ key: 'solutions_count',
+ width: '10%',
+ align: 'center',
+ render: (_, record) => (
+
+ {record.solution ? record.solution.length : 0} Solutions
+
+ ),
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ key: 'status',
+ width: '12%',
+ align: 'center',
+ render: (_, record) => {
+ const statusColor = record.is_active ? 'green' : 'red';
+ const statusText = record.is_active ? 'Active' : 'Inactive';
+
+ // Show modification status if applicable
+ if (record.status === 'new') {
+ return (
+
+ NEW
+ {statusText}
+
+ );
+ } else if (record.status === 'modified') {
+ return (
+
+ MODIFIED
+ {statusText}
+
+ );
+ } else if (record.status === 'deleted') {
+ return (
+
+ DELETED
+ Inactive
+
+ );
+ } else {
+ return (
+
+ {statusText}
+
+ );
+ }
+ },
+ },
+ {
+ title: 'Action',
+ key: 'action',
+ align: 'center',
+ width: '15%',
+ render: (_, record) => (
+
+ }
+ onClick={() => onView(record)}
+ size="small"
+ style={{
+ color: '#1890ff',
+ borderColor: '#1890ff',
+ }}
+ title="View Details"
+ />
+ }
+ onClick={() => onEdit(record)}
+ size="small"
+ style={{
+ color: '#faad14',
+ borderColor: '#faad14',
+ }}
+ title="Edit Error Code"
+ />
+ }
+ onClick={() => onDelete(record)}
+ size="small"
+ title="Delete Error Code"
+ />
+
+ ),
+ },
+];
+
+const ErrorCodeTable = memo(function ErrorCodeTable({
+ errorCodes = [],
+ loading = false,
+ brandId,
+ onAddErrorCode,
+ onEditErrorCode,
+ onDeleteErrorCode,
+ onViewErrorCode,
+ trigger,
+}) {
+ const [trigerFilter, setTrigerFilter] = useState(false);
+ const [formDataFilter, setFormDataFilter] = useState({ search: '' });
+ const [searchValue, setSearchValue] = useState('');
+
+ // Trigger table refresh when parent component data changes
+ useEffect(() => {
+ if (trigger !== undefined) {
+ setTrigerFilter(prev => !prev);
+ }
+ }, [trigger]);
+
+ // Simulate API data for error codes
+ const getErrorCodesData = async (params) => {
+ console.log('📋 ErrorCodeTable getErrorCodesData called with:', params);
+ console.log('📋 Available errorCodes:', errorCodes);
+
+ // This would be your actual API call
+ const filteredData = errorCodes.filter(code =>
+ !params.search ||
+ code.error_code.toLowerCase().includes(params.search.toLowerCase()) ||
+ code.error_code_name.toLowerCase().includes(params.search.toLowerCase())
+ );
+
+ console.log('📋 Filtered result:', filteredData);
+
+ return {
+ data: filteredData,
+ total: filteredData.length,
+ };
+ };
+
+ const handleSearch = () => {
+ setFormDataFilter({ search: searchValue });
+ setTrigerFilter(prev => !prev);
+ };
+
+ const handleSearchClear = () => {
+ setSearchValue('');
+ setFormDataFilter({ search: '' });
+ setTrigerFilter(prev => !prev);
+ };
+
+
+
+ return (
+
+
+
+
+
+ {
+ const value = e.target.value;
+ setSearchValue(value);
+ if (value === '') {
+ setFormDataFilter({ search: '' });
+ setTrigerFilter(prev => !prev);
+ }
+ }}
+ onSearch={handleSearch}
+ allowClear={{
+ clearIcon: ✕,
+ }}
+ enterButton={
+ }
+ style={{
+ backgroundColor: '#23A55A',
+ borderColor: '#23A55A',
+ }}
+ >
+ Search
+
+ }
+ size="large"
+ style={{ marginBottom: 16 }}
+ />
+
+
+
+ }
+ onClick={onAddErrorCode}
+ size="large"
+ >
+ Add Error Code
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+export default ErrorCodeTable;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx
index 4c80945..1705c0c 100644
--- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx
+++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx
@@ -250,7 +250,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
}}
size="large"
>
- Add Brand Device
+ Add data
diff --git a/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx b/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx
index 42037b1..5bb6971 100644
--- a/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx
+++ b/src/pages/master/brandDevice/component/SingleSparepartSelect.jsx
@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
-import { Select, Card, Typography, Tag, Spin, Empty, Button, Image, Row, Col, Modal } from 'antd';
+import { Select, Typography, Tag, Spin, Empty, Button, Row, Col } from 'antd';
import { PlusOutlined, DeleteOutlined, CheckOutlined, EyeOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { getAllSparepart } from '../../../../api/sparepart';
-import dayjs from 'dayjs';
+import CustomSparepartCard from './CustomSparepartCard';
const { Text, Title } = Typography;
const { Option } = Select;
@@ -16,7 +16,6 @@ const SingleSparepartSelect = ({
const [loading, setLoading] = useState(false);
const [selectedSpareparts, setSelectedSpareparts] = useState([]);
const [dropdownOpen, setDropdownOpen] = useState(false);
- const [previewModal, setPreviewModal] = useState({ visible: false, sparepart: null });
useEffect(() => {
fetchSpareparts();
@@ -102,219 +101,23 @@ const SingleSparepartSelect = ({
onSparepartChange(newSelectedIds);
};
- const handlePreviewSparepart = (sparepart) => {
- setPreviewModal({ visible: true, sparepart });
- };
-
- const handlePreviewModalClose = () => {
- setPreviewModal({ visible: false, sparepart: null });
- };
-
const renderSparepartCard = (sparepart, isSelected = false) => {
const isAlreadySelected = selectedSpareparts.some(sp => sp.sparepart_id === sparepart.sparepart_id);
- let imgSrc;
- if (sparepart.sparepart_foto) {
- if (sparepart.sparepart_foto.startsWith('http')) {
- imgSrc = sparepart.sparepart_foto;
- } else {
- const fileName = sparepart.sparepart_foto.split('/').pop();
- if (fileName === 'defaultSparepartImg.jpg') {
- imgSrc = `/assets/defaultSparepartImg.jpg`;
- } else {
- const token = localStorage.getItem('token');
- const baseURL = import.meta.env.VITE_API_SERVER || '';
- imgSrc = `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`;
- }
- }
- } else {
- imgSrc = 'https://via.placeholder.com/150';
- }
-
return (
-
-
+ handleSparepartSelect(sparepart.sparepart_id) : undefined}
+ onDelete={() => handleRemoveSparepart(sparepart.sparepart_id)}
style={{
- borderRadius: '8px',
- overflow: 'hidden',
- border: isSelected ? '2px solid #1890ff' : isAlreadySelected ? '2px solid #52c41a' : '1px solid #E0E0E0',
+ border: isAlreadySelected ? '2px solid #52c41a' : undefined,
}}
- bodyStyle={{ padding: 0 }}
- onClick={!isSelected && !isReadOnly && !isAlreadySelected ? () => handleSparepartSelect(sparepart.sparepart_id) : undefined}
- actions={[
- // Preview action (selalu available)
- {
- e.stopPropagation();
- handlePreviewSparepart(sparepart);
- }}
- />,
- // Delete action (hanya untuk selected items)
- isSelected && !isReadOnly && (
- {
- e.stopPropagation();
- handleRemoveSparepart(sparepart.sparepart_id);
- }}
- />
- ),
- ].filter(Boolean)}
- >
-
-
-
- {sparepart.sparepart_item_type && (
-
- {sparepart.sparepart_item_type}
-
- )}
-
-
-

{
- e.target.src = 'https://via.placeholder.com/150';
- }}
- />
-
- {isAlreadySelected && (
-
-
-
- )}
-
-
-
-
-
- {/* Title dengan proper hierarchy */}
-
- {sparepart.sparepart_name || sparepart.name || 'Unnamed'}
-
-
- {/* Stock Information */}
-
- Available Stock: {sparepart.sparepart_stock || '0'}
-
-
-
-
- {/* Code */}
-
-
- {sparepart.sparepart_code || 'No code'}
-
-
-
- {/* Brand/Model/Unit Information */}
- {(sparepart.sparepart_merk || sparepart.sparepart_model || sparepart.sparepart_unit) && (
-
- {sparepart.sparepart_merk && (
-
Brand: {sparepart.sparepart_merk}
- )}
- {sparepart.sparepart_model && (
-
Model: {sparepart.sparepart_model}
- )}
- {sparepart.sparepart_unit && (
-
Unit: {sparepart.sparepart_unit}
- )}
-
- )}
-
- {/* Last Updated */}
- {sparepart.updated_at && (
-
- Last updated: {dayjs(sparepart.updated_at).format('DD MMM YYYY')}
-
- )}
-
-
-
-
+ />
);
};
@@ -360,7 +163,7 @@ const SingleSparepartSelect = ({
Selected Spareparts ({selectedSpareparts.length})
-
+
{selectedSpareparts.map(sparepart => renderSparepartCard(sparepart, true))}
@@ -373,175 +176,6 @@ const SingleSparepartSelect = ({
)}
-
- {/* Preview Modal */}
-
- Close
-
- ]}
- width={800}
- centered
- bodyStyle={{ padding: '24px' }}
- >
- {previewModal.sparepart && (
-
-
-
-
- {previewModal.sparepart.sparepart_foto ? (
-
)
{
- e.target.src = 'https://via.placeholder.com/200x200/d9d9d9/666666?text=No+Image';
- }}
- />
- ) : (
-
- No Image
-
- )}
-
-
- {previewModal.sparepart.sparepart_item_type && (
-
- {previewModal.sparepart.sparepart_item_type}
-
- )}
-
-
-
-
-
-
- {previewModal.sparepart.sparepart_name || 'Unnamed'}
-
-
-
-
- Code:
-
-
- {previewModal.sparepart.sparepart_code || 'N/A'}
-
-
-
- {previewModal.sparepart.sparepart_description && (
-
-
- Description:
-
-
- {previewModal.sparepart.sparepart_description}
-
-
- )}
-
-
-
- Status:
-
-
- {previewModal.sparepart.is_active ? 'Active' : 'Inactive'}
-
-
-
-
-
- Stock:
-
-
- {previewModal.sparepart.sparepart_stock || '0'}
- {previewModal.sparepart_unit ? ` ${previewModal.sparepart.unit}` : ' units'}
-
-
-
-
- {previewModal.sparepart.sparepart_merk && (
-
-
-
- Brand:
-
-
- {previewModal.sparepart.sparepart_merk}
-
-
-
- )}
- {previewModal.sparepart.sparepart_model && (
-
-
-
- Model:
-
-
- {previewModal.sparepart.sparepart_model}
-
-
-
- )}
- {previewModal.sparepart.sparepart_unit && (
-
-
-
- Unit:
-
-
- {previewModal.sparepart.sparepart_unit}
-
-
-
- )}
-
-
- {previewModal.sparepart.updated_at && (
-
-
- Last updated: {dayjs(previewModal.sparepart.updated_at).format('DD MMMM YYYY, HH:mm')}
-
-
- )}
-
-
-
- )}
-
>
);
};
diff --git a/src/pages/master/brandDevice/component/SolutionForm.jsx b/src/pages/master/brandDevice/component/SolutionForm.jsx
index 1e7734e..f6a7a9f 100644
--- a/src/pages/master/brandDevice/component/SolutionForm.jsx
+++ b/src/pages/master/brandDevice/component/SolutionForm.jsx
@@ -10,18 +10,31 @@ const SolutionForm = ({
solutionFields,
solutionTypes,
solutionStatuses,
- fileList,
- solutionsToDelete,
firstSolutionValid,
onAddSolutionField,
onRemoveSolutionField,
onSolutionTypeChange,
onSolutionStatusChange,
+ checkFirstSolutionValid,
onSolutionFileUpload,
onFileView,
+ fileList,
isReadOnly = false,
- onAddSolution,
}) => {
+ // Debug props
+ console.log('🔍 SolutionForm props:', {
+ solutionFields,
+ solutionTypes,
+ solutionStatuses,
+ firstSolutionValid,
+ onAddSolutionField: typeof onAddSolutionField,
+ onRemoveSolutionField: typeof onRemoveSolutionField,
+ checkFirstSolutionValid: typeof checkFirstSolutionValid,
+ onSolutionFileUpload: typeof onSolutionFileUpload,
+ onFileView: typeof onFileView,
+ fileList: fileList ? fileList.length : 0
+ });
+
return (