From 5989948bf92872456862381be9817d878f8462fd Mon Sep 17 00:00:00 2001 From: Iqbal Rizqi Kurniawan Date: Thu, 20 Nov 2025 11:00:46 +0700 Subject: [PATCH 01/19] refactor: simplify log history display in ListNotification component --- .../notification/component/ListNotification.jsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx index 2fb225c..22e8971 100644 --- a/src/pages/notification/component/ListNotification.jsx +++ b/src/pages/notification/component/ListNotification.jsx @@ -1041,10 +1041,7 @@ const ListNotification = memo(function ListNotification(props) { - {logHistoryData.slice(0, 2).map( - ( - log // Menampilkan 2 log terbaru sebagai pratinjau - ) => ( + {logHistoryData.map((log) => ( - ) - )} -
- -
+ ))} From f1c7ae5e20c12006adf2900bf0a1fbf2a4ce878c Mon Sep 17 00:00:00 2001 From: vinix Date: Thu, 20 Nov 2025 15:09:16 +0700 Subject: [PATCH 02/19] fix: update header label for clarity in ListPlantSubSection component --- .../master/plantSubSection/component/ListPlantSubSection.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/plantSubSection/component/ListPlantSubSection.jsx b/src/pages/master/plantSubSection/component/ListPlantSubSection.jsx index cd283ec..ec9607a 100644 --- a/src/pages/master/plantSubSection/component/ListPlantSubSection.jsx +++ b/src/pages/master/plantSubSection/component/ListPlantSubSection.jsx @@ -237,7 +237,7 @@ const ListPlantSubSection = memo(function ListPlantSubSection(props) { Date: Thu, 20 Nov 2025 15:14:30 +0700 Subject: [PATCH 03/19] fix: update header label from 'tag_name' to 'brand_name' in ListBrandDevice component --- src/pages/master/brandDevice/component/ListBrandDevice.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx index 4128dfb..884da82 100644 --- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -262,7 +262,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { Date: Thu, 20 Nov 2025 15:25:07 +0700 Subject: [PATCH 04/19] fix: enhance tag duplication check and handle varied response structures in DetailTag component --- src/pages/master/tag/component/DetailTag.jsx | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/pages/master/tag/component/DetailTag.jsx b/src/pages/master/tag/component/DetailTag.jsx index cde59b4..8b5fce7 100644 --- a/src/pages/master/tag/component/DetailTag.jsx +++ b/src/pages/master/tag/component/DetailTag.jsx @@ -84,12 +84,32 @@ const DetailTag = (props) => { const params = new URLSearchParams({ limit: 10000 }); const response = await getAllTag(params); - if (response && response.data && response.data.data) { - const existingTags = response.data.data; + // Handle different response structures + let existingTags = []; + if (response) { + if (Array.isArray(response)) { + existingTags = response; + } else if (response.data && Array.isArray(response.data)) { + existingTags = response.data; + } else if (response.data && response.data.data && Array.isArray(response.data.data)) { + existingTags = response.data.data; + } + } + if (existingTags.length > 0) { const isDuplicate = existingTags.some((tag) => { - const isSameNumber = Number(tag.tag_number) === tagNumberInt; - const isDifferentTag = formData.tag_id ? tag.tag_id !== formData.tag_id : true; + // Handle both string and number tag_number + const existingTagNumber = Number(tag.tag_number); + const currentTagNumber = Number(formData.tag_number); + + // Check if numbers are valid and equal + const isSameNumber = !isNaN(existingTagNumber) && !isNaN(currentTagNumber) && + existingTagNumber === currentTagNumber; + + // For edit mode, exclude the current tag from duplicate check + const isDifferentTag = formData.tag_id ? + String(tag.tag_id) !== String(formData.tag_id) : true; + return isSameNumber && isDifferentTag; }); @@ -97,7 +117,7 @@ const DetailTag = (props) => { NotifOk({ icon: 'warning', title: 'Peringatan', - message: `Tag Number ${tagNumberInt} sudah digunakan. Silakan gunakan nomor yang berbeda.`, + message: `Tag Number ${formData.tag_number} sudah digunakan. Silakan gunakan nomor yang berbeda.`, }); setConfirmLoading(false); return; From 1413d0ef33a96759bb29e40dce10db1e7c024d58 Mon Sep 17 00:00:00 2001 From: Nasywa Syafinka Widyamara Date: Thu, 20 Nov 2025 15:40:06 +0700 Subject: [PATCH 05/19] fix: header list tag plant sub section --- src/pages/master/tag/component/ListTag.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/tag/component/ListTag.jsx b/src/pages/master/tag/component/ListTag.jsx index 220eeb8..1afc579 100644 --- a/src/pages/master/tag/component/ListTag.jsx +++ b/src/pages/master/tag/component/ListTag.jsx @@ -63,7 +63,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ render: (text) => text || '-', }, { - title: 'Sub Section', + title: 'Plant Sub Section', dataIndex: 'plant_sub_section_name', key: 'plant_sub_section_name', width: '10%', From 2d0c28bc484af38c208ec655bdc76a0aa6cc4bab Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Thu, 20 Nov 2025 15:55:03 +0700 Subject: [PATCH 06/19] fix: validation in DetailContact component --- src/pages/contact/component/DetailContact.jsx | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/pages/contact/component/DetailContact.jsx b/src/pages/contact/component/DetailContact.jsx index 6918f20..f6540b6 100644 --- a/src/pages/contact/component/DetailContact.jsx +++ b/src/pages/contact/component/DetailContact.jsx @@ -14,34 +14,22 @@ const DetailContact = memo(function DetailContact(props) { name: '', phone: '', is_active: true, - contact_type: 'operator', + contact_type: '', }; const [formData, setFormData] = useState(defaultData); const handleInputChange = (e) => { - let name, value; - - if (e && e.target) { - name = e.target.name; - value = e.target.value; - } else if (e && e.type === 'change') { - name = e.name || e.target?.name; - value = e.value !== undefined ? e.value : e.checked; - } else if (typeof e === 'string' || typeof e === 'number') { - // Handle Select onChange - value = e; - name = 'contact_type'; - } else { - return; - } + const { name, value } = e.target; // Validasi untuk field phone - hanya angka yang diperbolehkan if (name === 'phone') { - value = value.replace(/[^0-9+\-\s()]/g, ''); - } - - if (name) { + const cleanedValue = value.replace(/[^0-9+\-\s()]/g, ''); + setFormData((prev) => ({ + ...prev, + [name]: cleanedValue, + })); + } else { setFormData((prev) => ({ ...prev, [name]: value, @@ -49,6 +37,14 @@ const DetailContact = memo(function DetailContact(props) { } }; + + const handleContactTypeChange = (value) => { + setFormData((prev) => ({ + ...prev, + contact_type: value, + })); + }; + const handleStatusToggle = (checked) => { setFormData({ ...formData, @@ -59,6 +55,21 @@ const DetailContact = memo(function DetailContact(props) { const handleSave = async () => { setConfirmLoading(true); + // Validation rules + const validationRules = [ + { field: 'name', label: 'Contact Name', required: true }, + { field: 'phone', label: 'Phone', required: true }, + { field: 'contact_type', label: 'Contact Type', required: true }, + ]; + + if ( + validateRun(formData, validationRules, (errorMessages) => { + NotifOk({ icon: 'warning', title: 'Peringatan', message: errorMessages }); + setConfirmLoading(false); + }) + ) + return; + // Custom validation untuk name - minimal 3 karakter if (formData.name && formData.name.length < 3) { NotifOk({ @@ -82,21 +93,6 @@ const DetailContact = memo(function DetailContact(props) { return; } - // Validation rules - const validationRules = [ - { field: 'name', label: 'Contact Name', required: true }, - { field: 'phone', label: 'Phone', required: true }, - { field: 'contact_type', label: 'Contact Type', required: true }, - ]; - - if ( - validateRun(formData, validationRules, (errorMessages) => { - NotifOk({ icon: 'warning', title: 'Peringatan', message: errorMessages }); - setConfirmLoading(false); - }) - ) - return; - try { const contactData = { contact_name: formData.name, @@ -107,7 +103,10 @@ const DetailContact = memo(function DetailContact(props) { let response; if (props.actionMode === 'edit') { - response = await updateContact(props.selectedData.contact_id || props.selectedData.id, contactData); + response = await updateContact( + props.selectedData.contact_id || props.selectedData.id, + contactData + ); } else { response = await createContact(contactData); } @@ -145,15 +144,16 @@ const DetailContact = memo(function DetailContact(props) { setFormData({ name: props.selectedData.contact_name || props.selectedData.name, phone: props.selectedData.contact_phone || props.selectedData.phone, - is_active: props.selectedData.is_active || props.selectedData.status === 'active', - contact_type: props.selectedData.contact_type || props.contactType || 'operator', + is_active: + props.selectedData.is_active || props.selectedData.status === 'active', + contact_type: props.selectedData.contact_type || props.contactType || '', }); } else if (props.actionMode === 'add') { setFormData({ name: '', phone: '', is_active: true, - contact_type: props.contactType === 'all' ? 'operator' : props.contactType || 'operator', + contact_type: props.contactType === 'all' ? '' : props.contactType || '', }); } } @@ -256,8 +256,8 @@ const DetailContact = memo(function DetailContact(props) { Contact Type * + {/* Error Name */} @@ -106,17 +93,11 @@ const ErrorCodeSimpleForm = ({ name="error_code_name" rules={[{ required: true, message: 'Error name wajib diisi!' }]} > - + {/* Error Description */} - + - + @@ -152,7 +135,13 @@ const ErrorCodeSimpleForm = ({ ) : ( -
+
No upload allowed
)} @@ -181,12 +170,7 @@ const ErrorCodeSimpleForm = ({
{!isErrorCodeFormReadOnly && ( - )} @@ -221,7 +205,7 @@ const ErrorCodeSimpleForm = ({ }} style={{ width: '100%' }} > - + Add Error Code + Simpan Error Code
@@ -230,4 +214,4 @@ const ErrorCodeSimpleForm = ({ ); }; -export default ErrorCodeSimpleForm; \ No newline at end of file +export default ErrorCodeSimpleForm; From 1cd9cf765cba3db37eae05205f4c418bca00f773 Mon Sep 17 00:00:00 2001 From: vinix Date: Mon, 24 Nov 2025 15:57:12 +0700 Subject: [PATCH 14/19] fix dual action --- src/components/Global/CardList.jsx | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/Global/CardList.jsx b/src/components/Global/CardList.jsx index bf8f373..298ac0c 100644 --- a/src/components/Global/CardList.jsx +++ b/src/components/Global/CardList.jsx @@ -58,34 +58,34 @@ const CardList = ({ style={getCardStyle(fieldColor ? item[fieldColor] : cardColor)} actions={[ showPreviewModal && ( - showPreviewModal(item)} - /> + showPreviewModal(item)} + /> ), showEditModal && ( - showEditModal(item)} - /> + showEditModal(item)} + /> ), showDeleteDialog && ( - showDeleteDialog(item)} - /> + showDeleteDialog(item)} + /> ), - ].filter(Boolean)} // <== Hapus elemen yang undefined + ].filter(Boolean)} // <== Hapus elemen yang undefined >
{column.map((itemCard, index) => ( {!itemCard.hidden && itemCard.title !== 'No' && - itemCard.title !== 'Aksi' && ( + itemCard.title !== 'Action' && (

{itemCard.title}:{' '} {itemCard.render From 1986368c1cab33e29e794463be2a341b9bf92d4d Mon Sep 17 00:00:00 2001 From: vinix Date: Mon, 24 Nov 2025 15:58:23 +0700 Subject: [PATCH 15/19] feat: update button label from 'Add Data' to 'Add Role' in ListRole component --- src/pages/role/component/ListRole.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/role/component/ListRole.jsx b/src/pages/role/component/ListRole.jsx index 3b9402f..649a64b 100644 --- a/src/pages/role/component/ListRole.jsx +++ b/src/pages/role/component/ListRole.jsx @@ -229,7 +229,7 @@ const ListRole = memo(function ListRole(props) { onClick={() => showAddModal()} size="large" > - Add Data + Add Role From b05e3fe5d9b512c12d59b735b0d3a2a1e552cb66 Mon Sep 17 00:00:00 2001 From: vinix Date: Mon, 24 Nov 2025 20:40:38 +0700 Subject: [PATCH 16/19] feat: implement sparepart management with CRUD operations and UI components --- src/api/sparepart.jsx | 50 +++ src/pages/master/sparepart/IndexSparepart.jsx | 422 +++--------------- .../sparepart/component/DetailSparepart.jsx | 289 ++++++++++++ .../sparepart/component/ListSparepart.jsx | 276 ++++++++++++ 4 files changed, 674 insertions(+), 363 deletions(-) create mode 100644 src/api/sparepart.jsx create mode 100644 src/pages/master/sparepart/component/DetailSparepart.jsx create mode 100644 src/pages/master/sparepart/component/ListSparepart.jsx diff --git a/src/api/sparepart.jsx b/src/api/sparepart.jsx new file mode 100644 index 0000000..0435d13 --- /dev/null +++ b/src/api/sparepart.jsx @@ -0,0 +1,50 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +const getAllSparepart = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `sparepart?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getSparepartById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `sparepart/${id}`, + }); + + return response.data; +}; + +const createSparepart = async (queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `sparepart`, + params: queryParams, + }); + + return response.data; +}; + +const updateSparepart = async (id, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `sparepart/${id}`, + params: queryParams, + }); + + return response.data; +}; + +const deleteSparepart = async (id) => { + const response = await SendRequest({ + method: 'delete', + prefix: `sparepart/${id}`, + }); + + return response.data; +}; + +export { getAllSparepart, getSparepartById, createSparepart, updateSparepart, deleteSparepart }; \ No newline at end of file diff --git a/src/pages/master/sparepart/IndexSparepart.jsx b/src/pages/master/sparepart/IndexSparepart.jsx index c6e55fe..334a661 100644 --- a/src/pages/master/sparepart/IndexSparepart.jsx +++ b/src/pages/master/sparepart/IndexSparepart.jsx @@ -1,379 +1,75 @@ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Button, Col, Row, Space, Input, ConfigProvider, Card, Tag, Spin, Table, Modal } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, EyeOutlined } from '@ant-design/icons'; -import { NotifAlert, NotifConfirmDialog, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { Typography } from 'antd'; +import ListSparepart from './component/ListSparepart'; +import DetailSparepart from './component/DetailSparepart'; -const { Text, Title } = Typography; - -// Mock data untuk sparepart -const mockSpareparts = [ - { - sparepart_id: 1, - sparepart_name: 'Compressor Oil', - sparepart_code: 'SP-COMP-001', - sparepart_description: 'Oil for industrial compressor maintenance', - brand_name: 'Sullair', - model: '750H', - type: 'consumable', - is_active: true, - created_date: '2024-01-15', - updated_date: '2024-11-20' - }, - { - sparepart_id: 2, - sparepart_name: 'Air Filter Element', - sparepart_code: 'SP-AF-002', - sparepart_description: 'High efficiency air filter for compressor', - brand_name: 'Ingersoll Rand', - model: 'X7', - type: 'filter', - is_active: true, - created_date: '2024-02-20', - updated_date: '2024-10-15' - }, - { - sparepart_id: 3, - sparepart_name: 'Oil Filter', - sparepart_code: 'SP-OF-003', - sparepart_description: 'Oil filtration system component', - brand_name: 'Atlas Copco', - model: 'GA 55', - type: 'filter', - is_active: false, - created_date: '2024-03-10', - updated_date: '2024-09-05' - }, - { - sparepart_id: 4, - sparepart_name: 'Cooling Fan Motor', - sparepart_code: 'SP-CFM-004', - sparepart_description: 'Motor for cooling system in compressor', - brand_name: 'Kobelco', - model: 'SRE-110', - type: 'electrical', - is_active: true, - created_date: '2024-04-05', - updated_date: '2024-11-10' - }, - { - sparepart_id: 5, - sparepart_name: 'Coupling Elastomer', - sparepart_code: 'SP-CE-005', - sparepart_description: 'Elastomer coupling for drive system', - brand_name: 'Kaesar', - model: 'Comprex', - type: 'mechanical', - is_active: true, - created_date: '2024-05-12', - updated_date: '2024-11-18' - } -]; +const { Text } = Typography; const IndexSparepart = memo(function IndexSparepart() { - const navigate = useNavigate(); - const { setBreadcrumbItems } = useBreadcrumb(); - const [spareparts, setSpareparts] = useState([]); - const [filteredSpareparts, setFilteredSpareparts] = useState([]); - const [loading, setLoading] = useState(true); - const [searchValue, setSearchValue] = useState(''); + const navigate = useNavigate(); + const { setBreadcrumbItems } = useBreadcrumb(); - useEffect(() => { - const token = localStorage.getItem('token'); - if (token) { - // Simulasi loading data - setTimeout(() => { - setSpareparts(mockSpareparts); - setFilteredSpareparts(mockSpareparts); - setLoading(false); - }, 1000); + const [actionMode, setActionMode] = useState('list'); + const [selectedData, setSelectedData] = useState(null); + const [readOnly, setReadOnly] = useState(false); + const [showModal, setShowmodal] = useState(false); - setBreadcrumbItems([ - { title: • Master }, - { title: Sparepart } - ]); - } else { - navigate('/signin'); - } - }, [navigate, setBreadcrumbItems]); + const setMode = (param) => { + setShowmodal(true); + switch (param) { + case 'add': + setReadOnly(false); + break; - // Fungsi untuk pencarian - const handleSearch = (value) => { - if (!value) { - setFilteredSpareparts(spareparts); - } else { - const filtered = spareparts.filter(item => - item.sparepart_name.toLowerCase().includes(value.toLowerCase()) || - item.sparepart_code.toLowerCase().includes(value.toLowerCase()) || - item.brand_name.toLowerCase().includes(value.toLowerCase()) || - item.model.toLowerCase().includes(value.toLowerCase()) - ); - setFilteredSpareparts(filtered); - } - }; + case 'edit': + setReadOnly(false); + break; - const handleSearchChange = (e) => { - const value = e.target.value; - setSearchValue(value); - handleSearch(value); - }; + case 'preview': + setReadOnly(true); + break; - const handleSearchClear = () => { - setSearchValue(''); - setFilteredSpareparts(spareparts); - }; + default: + setShowmodal(false); + break; + } + setActionMode(param); + }; - const handleAddSparepart = () => { - // Navigasi ke halaman tambah sparepart (akan dibuat nanti) - NotifAlert({ - icon: 'info', - title: 'Info', - message: 'Fitur tambah sparepart akan segera dibuat', - }); - }; + useEffect(() => { + const token = localStorage.getItem('token'); + if (token) { + setBreadcrumbItems([ + { title: • Master }, + { title: Sparepart } + ]); + } else { + navigate('/signin'); + } + }, []); - const handleEditSparepart = (record) => { - // Navigasi ke halaman edit sparepart (akan dibuat nanti) - NotifAlert({ - icon: 'info', - title: 'Info', - message: `Edit sparepart: ${record.sparepart_name}`, - }); - }; - - const handleViewSparepart = (record) => { - // Tampilkan detail sparepart (akan dibuat nanti) - NotifOk({ - icon: 'success', - title: 'Info', - message: `View sparepart: ${record.sparepart_name}`, - }); - }; - - const handleDeleteSparepart = (record) => { - NotifConfirmDialog({ - icon: 'question', - title: 'Konfirmasi', - message: 'Apakah anda yakin hapus sparepart "' + record.sparepart_name + '" ?', - onConfirm: () => { - const updatedSpareparts = spareparts.filter(item => item.sparepart_id !== record.sparepart_id); - setSpareparts(updatedSpareparts); - setFilteredSpareparts(updatedSpareparts); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: `Sparepart ${record.sparepart_name} deleted successfully.`, - }); - }, - onCancel: () => {}, - }); - }; - - // Kolom-kolom tabel - const columns = [ - { - title: 'No', - key: 'no', - width: '5%', - align: 'center', - render: (_, __, index) => index + 1, - }, - { - title: 'Sparepart Code', - dataIndex: 'sparepart_code', - key: 'sparepart_code', - width: '15%', - }, - { - title: 'Sparepart Name', - dataIndex: 'sparepart_name', - key: 'sparepart_name', - width: '20%', - }, - { - title: 'Description', - dataIndex: 'sparepart_description', - key: 'sparepart_description', - width: '20%', - render: (text) => text || '-', - }, - { - title: 'Brand', - dataIndex: 'brand_name', - key: 'brand_name', - width: '12%', - }, - { - title: 'Model', - dataIndex: 'model', - key: 'model', - width: '10%', - }, - { - title: 'Type', - dataIndex: 'type', - key: 'type', - width: '10%', - render: (text) => ( - - {text?.toUpperCase()} - - ), - }, - { - title: 'Status', - dataIndex: 'is_active', - key: 'is_active', - width: '8%', - align: 'center', - render: (_, { is_active }) => ( - <> - {is_active === true ? ( - - Active - - ) : ( - - Inactive - - )} - - ), - }, - { - title: 'Action', - key: 'action', - align: 'center', - width: '15%', - render: (_, record) => ( - - - } - size="large" - /> - - - - - - - - - - - - {loading ? ( -

- -
- ) : ( - `${range[0]}-${range[1]} of ${total} items`, - }} - scroll={{ x: 1000 }} - size="middle" - /> - )} - - - - - ); + return ( + + + + + ); }); export default IndexSparepart; \ No newline at end of file diff --git a/src/pages/master/sparepart/component/DetailSparepart.jsx b/src/pages/master/sparepart/component/DetailSparepart.jsx new file mode 100644 index 0000000..7dd4e38 --- /dev/null +++ b/src/pages/master/sparepart/component/DetailSparepart.jsx @@ -0,0 +1,289 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, message } from 'antd'; +import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; +import { createSparepart, updateSparepart } from '../../../../api/sparepart'; +import { validateRun } from '../../../../Utils/validate'; + +const { Text } = Typography; +const { TextArea } = Input; + +const DetailSparepart = (props) => { + const [confirmLoading, setConfirmLoading] = useState(false); + + const defaultData = { + sparepart_id: '', + sparepart_name: '', + sparepart_description: '', + sparepart_model: '', + sparepart_item_type: '', + sparepart_unit: '', + sparepart_merk: '', + sparepart_stok: '', + }; + + const [formData, setFormData] = useState(defaultData); + + const handleCancel = () => { + props.setSelectedData(null); + props.setActionMode('list'); + }; + + const handleSave = async () => { + setConfirmLoading(true); + + // Daftar aturan validasi + const validationRules = [ + { field: 'sparepart_name', label: 'Sparepart Name', required: true }, + { field: 'sparepart_model', label: 'Sparepart Model', required: true }, + { field: 'sparepart_unit', label: 'Sparepart Unit', required: true }, + { field: 'sparepart_merk', label: 'Sparepart Merk', required: true }, + { field: 'sparepart_stok', label: 'Sparepart Stok', required: true }, + ]; + + if ( + validateRun(formData, validationRules, (errorMessages) => { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: errorMessages, + }); + setConfirmLoading(false); + }) + ) + return; + + try { + const payload = { + sparepart_name: formData.sparepart_name, + sparepart_description: formData.sparepart_description, + sparepart_model: formData.sparepart_model, + sparepart_item_type: formData.sparepart_item_type, + sparepart_unit: formData.sparepart_unit, + sparepart_merk: formData.sparepart_merk, + sparepart_stok: formData.sparepart_stok, + }; + + const response = formData.sparepart_id + ? await updateSparepart(formData.sparepart_id, payload) + : await createSparepart(payload); + + // Check if response is successful + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + const sparepartName = response.data?.sparepart_name || formData.sparepart_name; + + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Data Sparepart "${sparepartName}" berhasil ${ + formData.sparepart_id ? 'diubah' : 'ditambahkan' + }.`, + }); + + props.setActionMode('list'); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Terjadi kesalahan saat menyimpan data.', + }); + } + } catch (error) { + console.error('Save Sparepart Error:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.', + }); + } + + setConfirmLoading(false); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleFieldChange = (name, value) => { + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleStatusToggle = (event) => { + const isChecked = event; + setFormData({ + ...formData, + is_active: isChecked ? true : false, + }); + }; + + useEffect(() => { + if (props.selectedData) { + setFormData(props.selectedData); + } else { + setFormData(defaultData); + } + }, [props.showModal, props.selectedData, props.actionMode]); + + return ( + + + + + + {!props.readOnly && ( + + )} + + , + ]} + > + {formData && ( +
+ + +
+ Sparepart Name + * + +
+ +
+ Sparepart Description +