diff --git a/src/pages/master/brandDevice/component/SparepartCardSelect.jsx b/src/pages/master/brandDevice/component/SparepartCardSelect.jsx
index 16016cd..43c0827 100644
--- a/src/pages/master/brandDevice/component/SparepartCardSelect.jsx
+++ b/src/pages/master/brandDevice/component/SparepartCardSelect.jsx
@@ -1,15 +1,17 @@
import React, { useState, useEffect } from 'react';
-import { Card, Row, Col, Image, Typography, Tag, Space, Spin, Button, Empty } from 'antd';
+import { Card, Row, Col, Image, Typography, Tag, Space, Spin, Button, Empty, message } from 'antd';
import { CheckCircleOutlined, CloseCircleOutlined, SearchOutlined } from '@ant-design/icons';
import { getAllSparepart } from '../../../../api/sparepart';
+import { addSparepartToBrand, removeSparepartFromBrand } from '../../../../api/master-brand';
const { Text, Title } = Typography;
-const SparepartCardSelect = ({
- selectedSparepartIds = [],
+const SparepartCardSelect = ({
+ selectedSparepartIds = [],
onSparepartChange,
isLoading: externalLoading = false,
- isReadOnly = false
+ isReadOnly = false,
+ brandId = null
}) => {
const [spareparts, setSpareparts] = useState([]);
const [loading, setLoading] = useState(false);
@@ -62,7 +64,6 @@ const SparepartCardSelect = ({
]);
}
} catch (error) {
- console.error('Error loading spareparts:', error);
// Default mock data
setSpareparts([
{
@@ -105,14 +106,45 @@ const SparepartCardSelect = ({
sp.sparepart_model?.toLowerCase().includes(searchTerm.toLowerCase())
);
- const handleSparepartToggle = (sparepartId) => {
+ const handleSparepartToggle = async (sparepartId) => {
if (isReadOnly) return;
-
- const newSelectedIds = selectedSparepartIds.includes(sparepartId)
- ? selectedSparepartIds.filter(id => id !== sparepartId)
- : [...selectedSparepartIds, sparepartId];
-
- onSparepartChange(newSelectedIds);
+
+ const isCurrentlySelected = selectedSparepartIds.includes(sparepartId);
+
+ // If brandId is provided, save immediately to database
+ if (brandId) {
+ try {
+ setLoading(true);
+
+ if (isCurrentlySelected) {
+ // Remove from database
+ await removeSparepartFromBrand(brandId, sparepartId);
+ message.success('Sparepart removed from brand successfully');
+ } else {
+ // Add to database
+ await addSparepartToBrand(brandId, sparepartId);
+ message.success('Sparepart added to brand successfully');
+ }
+
+ // Update local state
+ const newSelectedIds = isCurrentlySelected
+ ? selectedSparepartIds.filter(id => id !== sparepartId)
+ : [...selectedSparepartIds, sparepartId];
+
+ onSparepartChange(newSelectedIds);
+ } catch (error) {
+ message.error(error.message || 'Failed to update sparepart');
+ } finally {
+ setLoading(false);
+ }
+ } else {
+ // If no brandId (add mode), just update local state
+ const newSelectedIds = isCurrentlySelected
+ ? selectedSparepartIds.filter(id => id !== sparepartId)
+ : [...selectedSparepartIds, sparepartId];
+
+ onSparepartChange(newSelectedIds);
+ }
};
const isSelected = (sparepartId) => selectedSparepartIds.includes(sparepartId);
diff --git a/src/pages/master/brandDevice/hooks/solution.js b/src/pages/master/brandDevice/hooks/solution.js
index 4d83a54..ba60140 100644
--- a/src/pages/master/brandDevice/hooks/solution.js
+++ b/src/pages/master/brandDevice/hooks/solution.js
@@ -21,6 +21,7 @@ export const useSolutionLogic = (solutionForm) => {
solutionForm.setFieldValue(['solution_items', newKey, 'name'], '');
solutionForm.setFieldValue(['solution_items', newKey, 'type'], 'text');
solutionForm.setFieldValue(['solution_items', newKey, 'text'], '');
+ solutionForm.setFieldValue(['solution_items', newKey, 'status'], true);
}, 0);
};
@@ -57,8 +58,10 @@ export const useSolutionLogic = (solutionForm) => {
// Reset form values
solutionForm.resetFields();
solutionForm.setFieldsValue({
- solution_status_0: true,
- solution_type_0: 'text',
+ solution_items: [{
+ status: true,
+ type: 'text',
+ }]
});
};
diff --git a/src/pages/master/brandDevice/hooks/sparepart.js b/src/pages/master/brandDevice/hooks/sparepart.js
deleted file mode 100644
index 2e5329c..0000000
--- a/src/pages/master/brandDevice/hooks/sparepart.js
+++ /dev/null
@@ -1,141 +0,0 @@
-import { useState, useCallback } from 'react';
-
-export const useSparepartLogic = (sparepartForm) => {
- const [sparepartFields, setSparepartFields] = useState([]);
- const [sparepartTypes, setSparepartTypes] = useState({});
- const [sparepartStatuses, setSparepartStatuses] = useState({});
- const [sparepartsToDelete, setSparepartsToDelete] = useState(new Set());
-
- const handleAddSparepartField = useCallback(() => {
- const newKey = Date.now();
- const newField = {
- key: newKey,
- name: sparepartFields.length,
- isCreated: true,
- };
- setSparepartFields(prev => [...prev, newField]);
- setSparepartTypes(prev => ({
- ...prev,
- [newKey]: 'required'
- }));
- setSparepartStatuses(prev => ({
- ...prev,
- [newKey]: true
- }));
- }, [sparepartFields.length]);
-
- const handleRemoveSparepartField = useCallback((key) => {
- setSparepartFields(prev => prev.filter(field => field.key !== key));
- setSparepartTypes(prev => {
- const newTypes = { ...prev };
- delete newTypes[key];
- return newTypes;
- });
- setSparepartStatuses(prev => {
- const newStatuses = { ...prev };
- delete newStatuses[key];
- return newStatuses;
- });
-
- // Add to delete list if it's not a new field
- setSparepartsToDelete(prev => new Set([...prev, key]));
- }, []);
-
- const handleSparepartTypeChange = useCallback((key, type) => {
- setSparepartTypes(prev => ({
- ...prev,
- [key]: type
- }));
- }, []);
-
- const handleSparepartStatusChange = useCallback((key, status) => {
- setSparepartStatuses(prev => ({
- ...prev,
- [key]: status
- }));
- }, []);
-
- const resetSparepartFields = useCallback(() => {
- setSparepartFields([]);
- setSparepartTypes({});
- setSparepartStatuses({});
- setSparepartsToDelete(new Set());
- }, []);
-
- const getSparepartData = useCallback(() => {
- if (!sparepartForm) return [];
-
- const values = sparepartForm.getFieldsValue();
- const data = [];
-
- sparepartFields.forEach((field, index) => {
- const fieldData = {
- sparepart_id: values[`sparepart_id_${field.name}`],
- sparepart_name: values[`sparepart_name_${field.name}`],
- sparepart_description: values[`sparepart_description_${field.name}`],
- status: values[`sparepart_status_${field.name}`],
- type: sparepartTypes[field.key] || 'required',
- };
-
- // Only add if required fields are filled
- if (fieldData.sparepart_id) {
- data.push(fieldData);
- }
- });
-
- return data;
- }, [sparepartForm, sparepartFields, sparepartTypes]);
-
- const setSparepartsForExistingRecord = useCallback((sparepartData, form) => {
- resetSparepartFields();
-
- if (!sparepartData || !Array.isArray(sparepartData)) {
- return;
- }
-
- const newFields = sparepartData.map((sp, index) => ({
- key: sp.brand_sparepart_id || sp.sparepart_id || `existing-${index}`,
- name: index,
- isCreated: false,
- }));
-
- setSparepartFields(newFields);
-
- // Set form values for existing spareparts
- setTimeout(() => {
- const formValues = {};
- sparepartData.forEach((sp, index) => {
- const sparepartId = sp.brand_sparepart_id || sp.sparepart_id || sp.sparepart_name;
- formValues[`sparepart_id_${index}`] = sparepartId;
- formValues[`sparepart_status_${index}`] = sp.is_active ?? sp.status ?? true;
- formValues[`sparepart_description_${index}`] = sp.brand_sparepart_description || sp.description || sp.sparepart_name;
-
- setSparepartTypes(prev => ({
- ...prev,
- [sp.brand_sparepart_id || sp.sparepart_id || `existing-${index}`]: sp.type || sp.sparepart_type || 'required'
- }));
-
- setSparepartStatuses(prev => ({
- ...prev,
- [sp.brand_sparepart_id || sp.sparepart_id || `existing-${index}`]: sp.is_active ?? sp.status ?? true
- }));
- });
-
- form.setFieldsValue(formValues);
- }, 0);
- }, [resetSparepartFields]);
-
- return {
- sparepartFields,
- sparepartTypes,
- sparepartStatuses,
- sparepartsToDelete,
- handleAddSparepartField,
- handleRemoveSparepartField,
- handleSparepartTypeChange,
- handleSparepartStatusChange,
- resetSparepartFields,
- getSparepartData,
- setSparepartsForExistingRecord,
- };
-};
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/hooks/useBrandDeviceLogic.jsx b/src/pages/master/brandDevice/hooks/useBrandDeviceLogic.jsx
new file mode 100644
index 0000000..592ba12
--- /dev/null
+++ b/src/pages/master/brandDevice/hooks/useBrandDeviceLogic.jsx
@@ -0,0 +1,298 @@
+import { useState } from 'react';
+import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
+
+export const useBrandDeviceLogic = (isEditMode = false, brandId = null) => {
+ const [confirmLoading, setConfirmLoading] = useState(false);
+ const [currentStep, setCurrentStep] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [errorCodes, setErrorCodes] = useState([]);
+ const [pendingErrorCodes, setPendingErrorCodes] = useState([]);
+ const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null);
+ const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false);
+
+ const handleCancel = () => {
+ };
+
+ const handleNextStep = async (validateForm, setFormData, currentFormData) => {
+ try {
+ const validatedFormData = await validateForm();
+
+ setFormData({
+ brand_name: validatedFormData.brand_name,
+ brand_type: validatedFormData.brand_type || '',
+ brand_model: validatedFormData.brand_model || '',
+ brand_manufacture: validatedFormData.brand_manufacture || '',
+ is_active: validatedFormData.is_active,
+ });
+
+ setCurrentStep(1);
+ } catch (error) {
+ NotifAlert({
+ icon: 'warning',
+ title: 'Perhatian',
+ message: 'Harap isi semua kolom wajib untuk brand device!',
+ });
+ return false;
+ }
+ };
+
+ const handleFinish = async (
+ formData,
+ selectedSparepartIds,
+ apiCall,
+ successMessage,
+ navigatePath
+ ) => {
+ 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 || '#000000',
+ 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
+ }))
+ })) : (isEditMode ? errorCodes.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,
+ 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 apiCall(brandId, brandData);
+
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: response.message || successMessage,
+ });
+ navigate(navigatePath);
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response?.message || 'Gagal menyimpan data.',
+ });
+ }
+ } catch (error) {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: error.message || 'Gagal menyimpan data. Silakan coba lagi.',
+ });
+ } finally {
+ setConfirmLoading(false);
+ }
+ };
+
+ const handleAddErrorCode = async (
+ validateErrorCodeForm,
+ getSolutionData,
+ resetErrorCodeForm
+ ) => {
+ try {
+ const errorCodeValues = await validateErrorCodeForm();
+ const solutionData = getSolutionData();
+
+ if (solutionData.length === 0) {
+ NotifAlert({
+ icon: 'warning',
+ title: 'Perhatian',
+ message: 'Setiap error code harus memiliki minimal 1 solution!',
+ });
+ return false;
+ }
+
+ const newErrorCode = {
+ key: editingErrorCodeKey || `temp-${Date.now()}`,
+ 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: errorCodeValues.path_icon || '',
+ is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status,
+ solution: solutionData,
+ };
+
+ 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);
+ return true;
+ } catch (error) {
+ NotifAlert({
+ icon: 'warning',
+ title: 'Perhatian',
+ message: 'Harap isi semua kolom wajib (error code + minimal 1 solution)!',
+ });
+ return false;
+ }
+ };
+
+ const handleDeleteErrorCode = (key) => {
+ if (errorCodes.length <= 1) {
+ NotifAlert({
+ icon: 'warning',
+ title: 'Perhatian',
+ message: 'Setiap brand harus memiliki minimal 1 error code!',
+ });
+ return false;
+ }
+
+ const updatedErrorCodes = errorCodes.filter((item) => item.key !== key);
+ setErrorCodes(updatedErrorCodes);
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: 'Error code berhasil dihapus!',
+ });
+ return true;
+ };
+
+ const handleCreateNewErrorCode = (resetErrorCodeForm, resetSolutionFields) => {
+ resetErrorCodeForm();
+ resetSolutionFields();
+ setIsErrorCodeFormReadOnly(false);
+ setEditingErrorCodeKey(null);
+ };
+
+ const handlePreviewErrorCode = (
+ record,
+ setErrorCodeIcon,
+ setIsErrorCodeFormReadOnly,
+ setEditingErrorCodeKey,
+ setSolutionsForExistingRecord,
+ resetSolutionFields,
+ solutionForm
+ ) => {
+ 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,
+ setErrorCodeIcon,
+ setIsErrorCodeFormReadOnly,
+ setEditingErrorCodeKey,
+ setSolutionsForExistingRecord,
+ resetSolutionFields,
+ solutionForm
+ ) => {
+ 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' });
+ }
+ };
+
+ return {
+ // State
+ confirmLoading,
+ setConfirmLoading,
+ currentStep,
+ setCurrentStep,
+ loading,
+ setLoading,
+ errorCodes,
+ setErrorCodes,
+ pendingErrorCodes,
+ setPendingErrorCodes,
+ editingErrorCodeKey,
+ setEditingErrorCodeKey,
+ isErrorCodeFormReadOnly,
+ setIsErrorCodeFormReadOnly,
+
+ // Handlers
+ handleCancel,
+ handleNextStep,
+ handleFinish,
+ handleAddErrorCode,
+ handleDeleteErrorCode,
+ handleCreateNewErrorCode,
+ handlePreviewErrorCode,
+ handleEditErrorCode,
+ };
+};
\ No newline at end of file