repair: sollution field, handle clear form

This commit is contained in:
2025-12-13 14:15:35 +07:00
parent 49ba00d886
commit b9cdfcb1e9
2 changed files with 101 additions and 552 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom';
import {
Divider,
@@ -13,7 +13,10 @@ import {
Space,
ConfigProvider,
} from 'antd';
import { EyeOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import {
EditOutlined,
DeleteOutlined
} from '@ant-design/icons';
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { getBrandById, createBrand, createErrorCode, getErrorCodesByBrandId, updateErrorCode, deleteErrorCode, deleteBrand } from '../../../api/master-brand';
@@ -55,8 +58,6 @@ const AddBrandDevice = () => {
const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
const [currentSolutionData, setCurrentSolutionData] = useState([]);
const [confirmLoading, setConfirmLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [temporaryBrandId, setTemporaryBrandId] = useState(null);
const [isTemporaryBrand, setIsTemporaryBrand] = useState(false);
const [isAddingNewErrorCode, setIsAddingNewErrorCode] = useState(false);
@@ -65,16 +66,35 @@ const AddBrandDevice = () => {
if (!solutionForm) return [];
try {
const values = solutionForm.getFieldsValue(true);
const solutions = [];
let solutions = [];
solutionFields.forEach(fieldKey => {
let solution = null;
if (values.solution_items) {
if (Array.isArray(values.solution_items)) {
solutions = values.solution_items.filter(Boolean);
} else if (typeof values.solution_items === 'object') {
solutions = Object.values(values.solution_items).filter(Boolean);
if (values.solution_items && values.solution_items[fieldKey]) {
solution = values.solution_items[fieldKey];
}
}
if (!solution || !solution.name || solution.name.trim() === '') {
return;
}
const solutionType = solutionTypes[fieldKey] || solution.type || 'text';
let isValid = true;
if (solutionType === 'text') {
isValid = solution.text && solution.text.trim() !== '';
} else if (solutionType === 'file') {
const hasPathSolution = solution.path_solution && solution.path_solution.trim() !== '';
const hasFileUpload = (solution.fileUpload && typeof solution.fileUpload === 'object' && Object.keys(solution.fileUpload).length > 0);
const hasFile = (solution.file && typeof solution.file === 'object' && Object.keys(solution.file).length > 0);
isValid = hasPathSolution || hasFileUpload || hasFile;
}
if (isValid) {
solutions.push(solution);
}
});
return solutions;
} catch (error) {
@@ -83,26 +103,27 @@ const AddBrandDevice = () => {
};
const resetSolutionFields = () => {
if (solutionForm && solutionForm.resetFields) {
solutionForm.resetFields();
solutionForm.setFieldsValue({
solution_items: {
0: {
name: '',
type: 'text',
text: '',
status: true,
fileUpload: null,
file: null,
path_solution: null,
fileName: null
}
}
});
}
setSolutionFields([0]);
setSolutionTypes({ 0: 'text' });
setSolutionStatuses({ 0: true });
if (solutionForm && solutionForm.resetFields) {
solutionForm.resetFields();
setTimeout(() => {
solutionForm.setFieldsValue({
solution_items: {
0: {
name: '',
type: 'text',
text: '',
status: true,
file: null,
fileUpload: null
}
}
});
}, 100);
}
setCurrentSolutionData([]);
};
@@ -300,7 +321,8 @@ const AddBrandDevice = () => {
const loadErrorCodeData = (record) => {
setIsErrorCodeFormReadOnly(false);
setEditingErrorCodeKey(record.error_code_id);
const editingKey = record.tempId || `existing_${record.error_code_id}`;
setEditingErrorCodeKey(editingKey);
errorCodeForm.setFieldsValue({
error_code: record.error_code,
@@ -327,49 +349,6 @@ const AddBrandDevice = () => {
}
};
const handleEditErrorCode = (record) => {
loadErrorCodeData(record);
};
const handleDeleteErrorCode = (record) => {
NotifConfirmDialog({
icon: 'question',
title: 'Konfirmasi Hapus',
message: `Apakah Anda yakin ingin menghapus error code "${record.error_code}"?`,
onConfirm: async () => {
try {
const errorCodeToDelete = record.error_code_id;
const response = await deleteErrorCode(brandInfo.brand_id || id, errorCodeToDelete);
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Error code berhasil dihapus!',
});
setTrigerFilter(prev => !prev);
} else {
NotifAlert({
icon: 'error',
title: 'Gagal',
message: response?.message || 'Gagal menghapus error code',
});
}
} catch (error) {
NotifAlert({
icon: 'error',
title: 'Gagal',
message: error.message || 'Gagal menghapus error code',
});
}
},
onCancel: () => { }
});
};
const handlePreviewErrorCode = (record) => {
};
const handleSearch = () => {
setSearchText(searchValue);
setTrigerFilter((prev) => !prev);
@@ -381,154 +360,7 @@ const AddBrandDevice = () => {
setTrigerFilter((prev) => !prev);
};
const handleBrandFormValuesChange = useCallback((changedValues, allValues) => {
}, []);
const getErrorCodesData = async (params) => {
try {
const search = params.get('search') || '';
const page = parseInt(params.get('page')) || currentPage;
const limit = parseInt(params.get('limit')) || pageSize;
let allErrorCodes = [];
let paginationData = {
current_page: page,
current_limit: limit,
total_limit: 0,
total_page: 0,
};
if (brandInfo.brand_id) {
const queryParams = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
...(search && { search })
});
const response = await getErrorCodesByBrandId(brandInfo.brand_id, queryParams);
if (response && response.statusCode === 200) {
const apiErrorData = response.data || [];
allErrorCodes = apiErrorData.map(ec => ({
...ec,
tempId: `existing_${ec.error_code_id}`,
status: 'existing'
}));
if (response.paging) {
paginationData = {
current_page: response.paging.current_page || page,
current_limit: response.paging.current_limit || limit,
total_limit: response.paging.total_limit || 0,
total_page: response.paging.total_page || 0,
};
}
}
}
allErrorCodes = [...allErrorCodes, ...tempErrorCodes.filter(ec => ec.status !== 'deleted')];
if (searchText) {
allErrorCodes = allErrorCodes.filter(ec =>
ec.error_code.toLowerCase().includes(searchText.toLowerCase()) ||
ec.error_code_name.toLowerCase().includes(searchText.toLowerCase())
);
paginationData.total_limit = allErrorCodes.length;
paginationData.total_page = Math.ceil(allErrorCodes.length / limit);
}
return {
data: allErrorCodes,
paging: paginationData
};
} catch (error) {
return {
data: [],
paging: {
current_page: 1,
current_limit: pageSize,
total_limit: 0,
total_page: 0,
}
};
}
};
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) => (
<Space>
{text}
</Space>
),
},
{
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) => (
<Space>
<Button
type="text"
icon={<EyeOutlined />}
onClick={() => showPreviewModal(record)}
size="small"
/>
<Button
type="text"
icon={<EditOutlined />}
onClick={() => showEditModal(record)}
size="small"
/>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => showDeleteDialog(record)}
size="small"
/>
</Space>
),
},
];
const queryParams = useMemo(() => {
const params = new URLSearchParams();
params.set('page', currentPage.toString());
params.set('limit', pageSize.toString());
if (searchValue) {
params.set('search', searchValue);
}
return params;
}, [searchValue, currentPage, pageSize]);
const handlePaginationChange = (page, size) => {
setCurrentPage(page);
setPageSize(size);
};
const resetErrorCodeForm = () => {
errorCodeForm.resetFields();
@@ -763,7 +595,6 @@ const AddBrandDevice = () => {
)}
<BrandForm
form={brandForm}
onValuesChange={handleBrandFormValuesChange}
isEdit={false}
brandInfo={brandInfo}
/>

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useEffect, useState } from 'react';
import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom';
import {
Divider,
@@ -15,10 +15,9 @@ import {
Input,
ConfigProvider
} from 'antd';
import { EyeOutlined, EditOutlined, DeleteOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { getBrandById, updateBrand, getErrorCodesByBrandId, getErrorCodeById, deleteErrorCode, updateErrorCode as updateErrorCodeAPI, createErrorCode as createErrorCodeAPI } from '../../../api/master-brand';
import { getBrandById, updateBrand, getErrorCodesByBrandId, deleteErrorCode, updateErrorCode as updateErrorCodeAPI, createErrorCode as createErrorCodeAPI } from '../../../api/master-brand';
import { getFileUrl } from '../../../api/file-uploads';
import { SendRequest } from '../../../components/Global/ApiRequest';
import BrandForm from './component/BrandForm';
@@ -63,16 +62,35 @@ const EditBrandDevice = () => {
if (!solutionForm) return [];
try {
const values = solutionForm.getFieldsValue(true);
const solutions = [];
let solutions = [];
solutionFields.forEach(fieldKey => {
let solution = null;
if (values.solution_items) {
if (Array.isArray(values.solution_items)) {
solutions = values.solution_items.filter(Boolean);
} else if (typeof values.solution_items === 'object') {
solutions = Object.values(values.solution_items).filter(Boolean);
if (values.solution_items && values.solution_items[fieldKey]) {
solution = values.solution_items[fieldKey];
}
}
if (!solution || !solution.name || solution.name.trim() === '') {
return;
}
const solutionType = solutionTypes[fieldKey] || solution.type || 'text';
let isValid = true;
if (solutionType === 'text') {
isValid = solution.text && solution.text.trim() !== '';
} else if (solutionType === 'file') {
const hasPathSolution = solution.path_solution && solution.path_solution.trim() !== '';
const hasFileUpload = (solution.fileUpload && typeof solution.fileUpload === 'object' && Object.keys(solution.fileUpload).length > 0);
const hasFile = (solution.file && typeof solution.file === 'object' && Object.keys(solution.file).length > 0);
isValid = hasPathSolution || hasFileUpload || hasFile;
}
if (isValid) {
solutions.push(solution);
}
});
return solutions;
} catch (error) {
@@ -81,18 +99,26 @@ const EditBrandDevice = () => {
};
const resetSolutionFields = () => {
setSolutionFields([0]);
setSolutionTypes({ 0: 'text' });
setSolutionStatuses({ 0: true });
if (solutionForm && solutionForm.resetFields) {
solutionForm.resetFields();
solutionForm.setFieldsValue({
solution_items: {
0: {
name: '',
type: 'text',
text: '',
status: true
setTimeout(() => {
solutionForm.setFieldsValue({
solution_items: {
0: {
name: '',
type: 'text',
text: '',
status: true,
file: null,
fileUpload: null
}
}
}
});
});
}, 100);
}
setCurrentSolutionData([]);
};
@@ -344,47 +370,9 @@ const EditBrandDevice = () => {
}
}, [location, navigate]);
const addErrorCode = (newErrorCode) => {
const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const errorCodeWithId = {
...newErrorCode,
tempId: uniqueId,
status: 'new'
};
setTempErrorCodes(prev => [...prev, errorCodeWithId]);
};
const updateErrorCode = (tempId, updatedData) => {
setTempErrorCodes(prev =>
prev.map(ec => ec.tempId === tempId ? { ...ec, ...updatedData, status: 'modified' } : ec)
);
setExistingErrorCodes(prev =>
prev.map(ec => ec.tempId === tempId ? { ...ec, ...updatedData, status: 'modified' } : ec)
);
};
const deleteLocalErrorCode = (tempId, permanent = false) => {
if (permanent) {
setTempErrorCodes(prev => prev.filter(ec => ec.tempId !== tempId));
setExistingErrorCodes(prev => prev.filter(ec => ec.tempId !== tempId));
} else {
setTempErrorCodes(prev =>
prev.map(ec => ec.tempId === tempId ? { ...ec, status: 'deleted' } : ec)
);
setExistingErrorCodes(prev =>
prev.map(ec => ec.tempId === tempId ? { ...ec, status: 'deleted' } : ec)
);
}
};
const getErrorCodeById = (tempId) => {
const inTemp = tempErrorCodes.find(ec => ec.tempId === tempId);
if (inTemp) return inTemp;
const inExisting = existingErrorCodes.find(ec => ec.tempId === tempId);
return inExisting;
};
const handleNextStep = async () => {
try {
@@ -454,6 +442,7 @@ const EditBrandDevice = () => {
};
const handleErrorCodeIconUpload = (iconData) => {
setErrorCodeIcon(iconData);
};
@@ -474,11 +463,6 @@ const EditBrandDevice = () => {
setSelectedSparepartIds([]);
};
const handleCreateNewErrorCode = () => {
resetErrorCodeForm();
setCurrentSolutionData([]);
};
const handleSaveErrorCode = async () => {
try {
setConfirmLoading(true);
@@ -566,16 +550,6 @@ const EditBrandDevice = () => {
message: editingErrorCodeKey ? 'Error code berhasil diupdate!' : 'Error code berhasil ditambahkan!',
});
setTempErrorCodes(prev => prev.filter(ec => {
if (ec.status === 'existing' || ec.tempId.startsWith('existing_')) {
return true;
}
return ec.tempId !== editingErrorCodeKey;
}));
setTrigerFilter(prev => !prev);
resetErrorCodeForm();
} else {
@@ -627,157 +601,6 @@ const EditBrandDevice = () => {
}
};
const handlePreviewErrorCode = (record) => {
const errorCode = getErrorCodeById(record.tempId || record.error_code_id);
loadErrorCodeData(errorCode, true);
};
const handleEditErrorCode = (record) => {
const errorCode = getErrorCodeById(record.tempId || record.error_code_id);
loadErrorCodeData(errorCode, false);
};
const handleDeleteErrorCode = async (record) => {
NotifConfirmDialog({
icon: 'question',
title: 'Konfirmasi Hapus',
message: `Apakah Anda yakin ingin menghapus error code "${record.error_code}"?`,
onConfirm: async () => {
try {
if (record.status === 'existing' && record.error_code_id) {
const response = await deleteErrorCode(id, record.error_code_id);
if (response && response.statusCode === 200) {
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Error code berhasil dihapus!',
});
setTrigerFilter(prev => !prev);
} else {
NotifAlert({
icon: 'error',
title: 'Error',
message: response?.message || 'Failed to delete error code',
});
}
} else {
const tempId = record.tempId;
deleteLocalErrorCode(tempId, true);
NotifOk({
icon: 'success',
title: 'Berhasil',
message: 'Error code berhasil dihapus!',
});
setTrigerFilter(prev => !prev);
}
} catch (error) {
NotifAlert({
icon: 'error',
title: 'Error',
message: error.message || 'Failed to delete error code',
});
}
},
onCancel: () => { }
});
};
const mergedErrorCodes = useMemo(() => {
const allErrorCodes = [
...existingErrorCodes.map(ec => ({ ...ec, status: 'existing' })),
...tempErrorCodes
];
const activeErrorCodes = allErrorCodes.filter(ec => ec.status !== 'deleted');
if (searchText) {
return activeErrorCodes.filter(ec =>
ec.error_code.toLowerCase().includes(searchText.toLowerCase()) ||
ec.error_code_name.toLowerCase().includes(searchText.toLowerCase())
);
}
return activeErrorCodes;
}, [existingErrorCodes, tempErrorCodes, searchText]);
const errorCodeColumns = (showPreviewModal, showEditModal, showDeleteDialog) => [
{
title: 'No',
key: 'no',
width: '5%',
align: 'center',
render: (_, __, index) => index + 1,
},
{
title: 'Error Code',
dataIndex: 'error_code',
key: 'error_code',
width: '20%',
render: (text, record) => (
<Space>
{text}
</Space>
),
},
{
title: 'Error Name',
dataIndex: 'error_code_name',
key: 'error_code_name',
width: '25%',
},
{
title: 'Status',
dataIndex: 'is_active',
key: 'is_active',
width: '10%',
align: 'center',
render: (_, { is_active }) => (
<Tag color={is_active ? 'green' : 'red'}>
{is_active ? 'Active' : 'Inactive'}
</Tag>
),
},
{
title: 'Action',
key: 'action',
align: 'center',
width: '15%',
render: (_, record) => (
<Space>
<Button
icon={<EyeOutlined />}
onClick={() => showPreviewModal(record)}
style={{
color: '#1890ff',
borderColor: '#1890ff',
}}
/>
<Button
icon={<EditOutlined />}
onClick={() => showEditModal(record)}
style={{
color: '#faad14',
borderColor: '#faad14',
}}
/>
<Button
danger
icon={<DeleteOutlined />}
onClick={() => showDeleteDialog(record)}
style={{
borderColor: '#ff4d4f',
}}
/>
</Space>
),
},
];
const handleBrandFormValuesChange = useCallback((changedValues, allValues) => {
setBrandInfo(allValues);
}, [setBrandInfo]);
const handleSearch = () => {
setTrigerFilter((prev) => !prev);
};
@@ -787,113 +610,6 @@ const EditBrandDevice = () => {
setTrigerFilter((prev) => !prev);
};
const getErrorCodesData = async (params) => {
try {
const criteria = params.get('criteria') || '';
const page = parseInt(params.get('page')) || 1;
const limit = parseInt(params.get('limit')) || 10;
const currentBrandId = id;
if (!currentBrandId) {
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(),
...(criteria && { criteria })
});
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 {
return {
...ec,
tempId: `existing_${ec.error_code_id}`,
status: 'existing',
solution: ec.solution || [],
spareparts: ec.spareparts || []
};
}
});
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) {
return {
data: [],
pagination: {
current_page: 1,
current_limit: 10,
total_limit: 0,
total_page: 0,
}
};
}
};
const renderStepContent = () => {
if (currentStep === 0) {
return (
@@ -919,7 +635,6 @@ const EditBrandDevice = () => {
)}
<BrandForm
form={brandForm}
onValuesChange={handleBrandFormValuesChange}
isEdit={true}
/>
</div>
@@ -1186,7 +901,10 @@ const EditBrandDevice = () => {
{editingErrorCodeKey && (
<Button
size="large"
onClick={handleCreateNewErrorCode}
onClick={() => {
resetErrorCodeForm();
resetSolutionFields();
}}
style={{
backgroundColor: '#fff',
borderColor: '#d9d9d9',