From bf038911427130b7ed9299d2a1e3630007459665 Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Fri, 10 Oct 2025 14:14:10 +0700 Subject: [PATCH] refactor: streamline brand device management components and add error master view --- .../master/brandDevice/IndexBrandDevice.jsx | 199 ++----- .../component/DetailBrandDevice.jsx | 308 ++++++++--- .../brandDevice/component/ListBrandDevice.jsx | 497 +++++++++++++----- .../brandDevice/component/ListErrorMaster.jsx | 33 ++ 4 files changed, 689 insertions(+), 348 deletions(-) create mode 100644 src/pages/master/brandDevice/component/ListErrorMaster.jsx diff --git a/src/pages/master/brandDevice/IndexBrandDevice.jsx b/src/pages/master/brandDevice/IndexBrandDevice.jsx index a8aac49..d79041c 100644 --- a/src/pages/master/brandDevice/IndexBrandDevice.jsx +++ b/src/pages/master/brandDevice/IndexBrandDevice.jsx @@ -1,96 +1,45 @@ import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; -import { Form, Modal, Typography } from 'antd'; import ListBrandDevice from './component/ListBrandDevice'; import DetailBrandDevice from './component/DetailBrandDevice'; - -import { NotifConfirmDialog, NotifAlert } from '../../../components/Global/ToastNotif'; +import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; +import { Typography } from 'antd'; const { Text } = Typography; -// Mock Data -const initialData = [ - { - key: '1', - brandCode: 'HTC-01', - brandName: 'Brand A', - brandType: 'Type X', - deviceName: 'Device 1', - description: 'Description for Brand A', - }, - { - key: '2', - brandCode: 'HTC-02', - brandName: 'Brand B', - brandType: 'Type Y', - deviceName: 'Device 2', - description: 'Description for Brand B', - }, - { - key: '3', - brandCode: 'HTC-03', - brandName: 'Brand C', - brandType: 'Type Z', - deviceName: 'Device 3', - description: 'Description for Brand C', - }, - // Add more data for pagination testing - ...Array.from({ length: 20 }, (_, i) => ({ - key: `${i + 4}`, - brandCode: `HTC-${String(i + 4).padStart(2, '0')}`, - brandName: `Brand ${String.fromCharCode(68 + i)}`, - brandType: `Type ${String.fromCharCode(88 + i)}`, - deviceName: `Device ${i + 4}`, - description: `Description for Brand ${String.fromCharCode(68 + i)}`, - })), -]; - const IndexBrandDevice = memo(function IndexBrandDevice() { const navigate = useNavigate(); const { setBreadcrumbItems } = useBreadcrumb(); - const [form] = Form.useForm(); - const [data, setData] = useState(initialData); + const [activeTab, setActiveTab] = useState('brandDevice'); const [actionMode, setActionMode] = useState('list'); - const [editingKey, setEditingKey] = useState(''); - const [isModalVisible, setIsModalVisible] = useState(false); + const [selectedData, setSelectedData] = useState(null); const [readOnly, setReadOnly] = useState(false); + const [showModal, setShowmodal] = useState(false); - // Mock API function - const getAllBrandDevice = async (params) => { - const { page = 1, limit = 10, search = '' } = Object.fromEntries(params.entries()); - - let filteredData = data; - if (search) { - filteredData = data.filter(item => - item.brandName.toLowerCase().includes(search.toLowerCase()) || - item.brandType.toLowerCase().includes(search.toLowerCase()) || - item.deviceName.toLowerCase().includes(search.toLowerCase()) - ); + const setMode = (param) => { + setActionMode(param); + switch (param) { + case 'add': + setReadOnly(false); + setShowmodal(true); + break; + + case 'edit': + setReadOnly(false); + setShowmodal(true); + break; + + case 'preview': + setReadOnly(true); + setShowmodal(true); + break; + + default: + setShowmodal(false); + break; } - - const start = (page - 1) * limit; - const end = start + limit; - const paginatedData = filteredData.slice(start, end); - - return new Promise(resolve => { - setTimeout(() => { - resolve({ - status: 200, - data: { - data: paginatedData, - total: filteredData.length, - paging: { - page: parseInt(page), - limit: parseInt(limit), - total: filteredData.length, - }, - }, - }); - }, 500); - }); }; useEffect(() => { @@ -103,96 +52,26 @@ const IndexBrandDevice = memo(function IndexBrandDevice() { } else { navigate('/signin'); } - }, [navigate, setBreadcrumbItems]); - - useEffect(() => { - if (actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') { - setIsModalVisible(true); - setReadOnly(actionMode === 'preview'); - } else { - setIsModalVisible(false); - } - }, [actionMode]); - - const handleCancel = () => { - setActionMode('list'); - setEditingKey(''); - form.resetFields(); - }; - - const handleOk = () => { - if (readOnly) { - handleCancel(); - return; - } - form.validateFields() - .then((values) => { - let newData = [...data]; - if (editingKey) { - // Editing existing data - const index = newData.findIndex((item) => editingKey === item.key); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, ...values }); - } - } else { - // Adding new data - const newKey = (Math.max(...data.map(item => parseInt(item.key))) + 1).toString(); - newData = [{ key: newKey, ...values }, ...newData]; - } - setData(newData); - handleCancel(); - }) - .catch((info) => { - console.log('Validate Failed:', info); - }); - }; - - const handleEdit = (record) => { - form.setFieldsValue(record); - setEditingKey(record.key); - setActionMode('edit'); - }; - - const handlePreview = (record) => { - form.setFieldsValue(record); - setEditingKey(record.key); - setActionMode('preview'); - }; - - const handleDelete = (record) => { - NotifConfirmDialog({ - icon: 'question', - title: 'Konfirmasi', - message: `Apakah anda yakin ingin menghapus brand "${record.brandName}"?`, - onConfirm: () => { - const newData = data.filter((item) => item.key !== record.key); - setData(newData); - NotifAlert({ - icon: 'success', - title: 'Berhasil', - message: `Brand "${record.brandName}" berhasil dihapus.`, - }); - }, - }); - }; + }, []); return ( ); diff --git a/src/pages/master/brandDevice/component/DetailBrandDevice.jsx b/src/pages/master/brandDevice/component/DetailBrandDevice.jsx index f2a4a3a..3507e9a 100644 --- a/src/pages/master/brandDevice/component/DetailBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/DetailBrandDevice.jsx @@ -1,18 +1,174 @@ -import React from 'react'; -import { Modal, Form, Input, ConfigProvider, Button, Typography } from 'antd'; +import { useEffect, useState } from 'react'; +import { Modal, Input, Typography, Button, ConfigProvider, Select } from 'antd'; +import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; const { Text } = Typography; -const DetailBrandDevice = ({ visible, onCancel, onOk, form, editingKey, readOnly }) => { - const modalTitle = readOnly ? 'Preview Brand' : (editingKey ? 'Edit Brand' : 'Tambah Brand'); +const DetailBrandDevice = (props) => { + const [confirmLoading, setConfirmLoading] = useState(false); + + const defaultData = { + brand_id: '', + brandName: '', + brandType: '', + manufacturer: '', + model: '', + status: 'Active', + }; + + const [FormData, setFormData] = useState(defaultData); + + const handleCancel = () => { + props.setSelectedData(null); + props.setActionMode('list'); + }; + + const handleSave = async () => { + setConfirmLoading(true); + + // Validasi required fields + if (!FormData.brandName) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Brand Name Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.brandType) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Type Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.manufacturer) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Manufacturer Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.model) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Model Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.status) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Status Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + const payload = { + brandName: FormData.brandName, + brandType: FormData.brandType, + manufacturer: FormData.manufacturer, + model: FormData.model, + status: FormData.status, + }; + + try { + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 500)); + + const response = { + statusCode: FormData.brand_id ? 200 : 201, + data: { + brandName: FormData.brandName, + }, + }; + + console.log('Save Brand Device Response:', response); + + // Check if response is successful + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Data Brand Device "${ + response.data?.brandName || FormData.brandName + }" berhasil ${FormData.brand_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 Brand Device 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 handleSelectChange = (name, value) => { + setFormData({ + ...FormData, + [name]: value, + }); + }; + + useEffect(() => { + const token = localStorage.getItem('token'); + if (token) { + if (props.selectedData != null) { + setFormData(props.selectedData); + } else { + setFormData(defaultData); + } + } else { + // navigate('/signin'); // Uncomment if useNavigate is imported + } + }, [props.showModal]); return ( + <> - + - {!readOnly && ( - )} - , + , ]} - destroyOnClose > -
-
- Kode Brand - * - - - + {FormData && ( +
+ +
+ Brand Name + * + +
+
+ Type + * + +
+
+ Manufacturer + * + +
+
+ Model + * + +
+
+ Status + * + - -
-
- Brand Type - * - - - -
-
- Device Name - * - - - -
-
- Description - - - -
- + )} ); }; -export default DetailBrandDevice; \ No newline at end of file +export default DetailBrandDevice; diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx index c765714..d8bd42b 100644 --- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -1,57 +1,221 @@ -import React, { useState } from 'react'; -import { Button, Col, Row, Space, Input, ConfigProvider, Card } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, EyeOutlined } from '@ant-design/icons'; +import React, { memo, useState, useEffect } from 'react'; +import { Button, Col, Row, Space, Input, ConfigProvider, Card, Tag, Tabs } from 'antd'; +import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + SearchOutlined, + EyeOutlined, +} from '@ant-design/icons'; +import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif'; +import { useNavigate } from 'react-router-dom'; import TableList from '../../../../components/Global/TableList'; +import ListErrorMaster from './ListErrorMaster'; -const ListBrandDevice = ({ - setActionMode, - handleEdit, - handleDelete, - handlePreview, - getAllBrandDevice // Mock API function -}) => { - const [searchValue, setSearchValue] = useState(''); - const [formDataFilter, setFormDataFilter] = useState({ search: '' }); +// Dummy data +const initialBrandDeviceData = [ + { + brand_id: 1, + brandName: 'Siemens S7-1200', + brandType: 'PLC', + manufacturer: 'Siemens', + model: 'S7-1200', + status: 'Active', + }, + { + brand_id: 2, + brandName: 'Allen Bradley CompactLogix', + brandType: 'PLC', + manufacturer: 'Rockwell Automation', + model: 'CompactLogix 5370', + status: 'Active', + }, + { + brand_id: 3, + brandName: 'Schneider Modicon M580', + brandType: 'PLC', + manufacturer: 'Schneider Electric', + model: 'M580', + status: 'Active', + }, + { + brand_id: 4, + brandName: 'Mitsubishi FX5U', + brandType: 'PLC', + manufacturer: 'Mitsubishi', + model: 'FX5U', + status: 'Inactive', + }, +]; + +const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ + { + title: 'No', + key: 'no', + width: '5%', + align: 'center', + render: (_, __, index) => index + 1, + }, + { + title: 'Brand Device ', + dataIndex: 'brandName', + key: 'brandName', + width: '20%', + }, + { + title: 'Type', + dataIndex: 'brandType', + key: 'brandType', + width: '15%', + }, + { + title: 'Manufacturer', + dataIndex: 'manufacturer', + key: 'manufacturer', + width: '20%', + }, + { + title: 'model', + dataIndex: 'model', + key: 'model', + width: '15%', + }, + { + title: 'status', + dataIndex: 'status', + key: 'status', + width: '10%', + align: 'center', + render: (_, { status }) => ( + <> + {status === 'Active' ? ( + + Active + + ) : ( + + Inactive + + )} + + ), + }, + { + title: 'Action', + key: 'action', + align: 'center', + width: '15%', + render: (_, record) => ( + + - } - size="large" - /> - - - - - - - - - - - - ); -}; + const showPreviewModal = (param) => { + props.setSelectedData(param); + props.setActionMode('preview'); + }; -export default ListBrandDevice; \ No newline at end of file + const showEditModal = (param = null) => { + props.setSelectedData(param); + props.setActionMode('edit'); + }; + + const showAddModal = (param = null) => { + props.setSelectedData(param); + props.setActionMode('add'); + }; + + const showDeleteDialog = (param) => { + NotifConfirmDialog({ + icon: 'question', + title: 'Konfirmasi', + message: 'Apakah anda yakin hapus data "' + param.brandName + '" ?', + onConfirm: () => handleDelete(param.brand_id), + onCancel: () => props.setSelectedData(null), + }); + }; + + const handleDelete = async (brand_id) => { + // Find brand name before deleting + const brandToDelete = brandDeviceData.find((brand) => brand.brand_id === brand_id); + + // Simulate delete API call + await new Promise((resolve) => setTimeout(resolve, 300)); + + // Remove from state + const updatedBrands = brandDeviceData.filter((brand) => brand.brand_id !== brand_id); + setBrandDeviceData(updatedBrands); + + NotifAlert({ + icon: 'success', + title: 'Berhasil', + message: `Data Brand Device "${brandToDelete?.brandName || ''}" berhasil dihapus.`, + }); + }; + + const tabItems = [ + { + key: 'brandDevice', + label: 'Brand Device', + }, + { + key: 'errorMaster', + label: 'Error Master', + }, + ]; + + return ( + + + + + + {props.activeTab === 'brandDevice' && ( + + + + + { + const value = e.target.value; + setSearchValue(value); + // Auto search when clearing by backspace/delete + if (value === '') { + setFormDataFilter({ search: '' }); + setTrigerFilter((prev) => !prev); + } + }} + onSearch={handleSearch} + allowClear={{ + clearIcon: , + }} + enterButton={ + + } + size="large" + /> + + + + + + + + + + + + + + + )} + {props.activeTab === 'errorMaster' && ( + + )} + + + ); +}); + +export default ListBrandDevice; diff --git a/src/pages/master/brandDevice/component/ListErrorMaster.jsx b/src/pages/master/brandDevice/component/ListErrorMaster.jsx new file mode 100644 index 0000000..78bd856 --- /dev/null +++ b/src/pages/master/brandDevice/component/ListErrorMaster.jsx @@ -0,0 +1,33 @@ +import React, { memo } from 'react'; +import { Row, Col } from 'antd'; + +const ListErrorMaster = memo(function ListErrorMaster(props) { + return ( + + + +
+

+ Error Master +

+
+ +
+
+ ); +}); + +export default ListErrorMaster;