diff --git a/src/pages/master/brandDevice/IndexBrandDevice.jsx b/src/pages/master/brandDevice/IndexBrandDevice.jsx index aa60fc3..a8aac49 100644 --- a/src/pages/master/brandDevice/IndexBrandDevice.jsx +++ b/src/pages/master/brandDevice/IndexBrandDevice.jsx @@ -1,13 +1,97 @@ -import React, { memo, useEffect } from 'react'; + +import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; -import { Typography } from 'antd'; +import { Form, Modal, Typography } from 'antd'; +import ListBrandDevice from './component/ListBrandDevice'; +import DetailBrandDevice from './component/DetailBrandDevice'; + +import { NotifConfirmDialog, NotifAlert } from '../../../components/Global/ToastNotif'; 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 [actionMode, setActionMode] = useState('list'); + const [editingKey, setEditingKey] = useState(''); + const [isModalVisible, setIsModalVisible] = useState(false); + const [readOnly, setReadOnly] = 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 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(() => { const token = localStorage.getItem('token'); @@ -19,12 +103,98 @@ 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 ( -
-

Brand Device Page

-
+ + + + ); }); diff --git a/src/pages/master/brandDevice/component/DetailBrandDevice.jsx b/src/pages/master/brandDevice/component/DetailBrandDevice.jsx new file mode 100644 index 0000000..f2a4a3a --- /dev/null +++ b/src/pages/master/brandDevice/component/DetailBrandDevice.jsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { Modal, Form, Input, ConfigProvider, Button, Typography } from 'antd'; + +const { Text } = Typography; + +const DetailBrandDevice = ({ visible, onCancel, onOk, form, editingKey, readOnly }) => { + const modalTitle = readOnly ? 'Preview Brand' : (editingKey ? 'Edit Brand' : 'Tambah Brand'); + + return ( + + + + + + {!readOnly && ( + + )} + + , + ]} + destroyOnClose + > +
+
+ Kode Brand + * + + + +
+
+ Brand Name + * + + + +
+
+ Brand Type + * + + + +
+
+ Device Name + * + + + +
+
+ Description + + + +
+
+
+ ); +}; + +export default DetailBrandDevice; \ No newline at end of file diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx new file mode 100644 index 0000000..b015d6c --- /dev/null +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -0,0 +1,137 @@ +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 TableList from '../../../../components/Global/TableList'; + +const ListBrandDevice = ({ + setActionMode, + handleEdit, + handleDelete, + handlePreview, + getAllBrandDevice // Mock API function +}) => { + const [searchValue, setSearchValue] = useState(''); + const [formDataFilter, setFormDataFilter] = useState({ search: '' }); + const [trigerFilter, setTrigerFilter] = useState(false); + + const columns = [ + { + title: 'Kode Brand', + dataIndex: 'brandCode', + key: 'brandCode', + }, + { + title: 'Brand Name', + dataIndex: 'brandName', + key: 'brandName', + }, + { + title: 'Brand Type', + dataIndex: 'brandType', + key: 'brandType', + }, + { + title: 'Device Name', + dataIndex: 'deviceName', + key: 'deviceName', + }, + { + title: 'Description', + dataIndex: 'description', + key: 'description', + }, + { + title: 'Aksi', + key: 'action', + render: (_, record) => ( + + + } + size="large" + /> + + + + + + + + + + + + ); +}; + +export default ListBrandDevice; \ No newline at end of file diff --git a/src/pages/master/tag/IndexTag.jsx b/src/pages/master/tag/IndexTag.jsx index 17ef600..b763c27 100644 --- a/src/pages/master/tag/IndexTag.jsx +++ b/src/pages/master/tag/IndexTag.jsx @@ -1,5 +1,7 @@ -import React, { memo, useEffect } from 'react'; +import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import ListTag from './component/ListTag'; +import DetailTag from './component/DetailTag'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { Typography } from 'antd'; @@ -9,6 +11,35 @@ const IndexTag = memo(function IndexTag() { const navigate = useNavigate(); const { setBreadcrumbItems } = useBreadcrumb(); + const [actionMode, setActionMode] = useState('list'); + const [selectedData, setSelectedData] = useState(null); + const [readOnly, setReadOnly] = useState(false); + const [showModal, setShowmodal] = useState(false); + + 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; + } + }; + useEffect(() => { const token = localStorage.getItem('token'); if (token) { @@ -22,9 +53,23 @@ const IndexTag = memo(function IndexTag() { }, []); return ( -
-

Tag Page

-
+ + + + ); }); diff --git a/src/pages/master/tag/component/DetailTag.jsx b/src/pages/master/tag/component/DetailTag.jsx new file mode 100644 index 0000000..754d49e --- /dev/null +++ b/src/pages/master/tag/component/DetailTag.jsx @@ -0,0 +1,286 @@ +import { useEffect, useState } from 'react'; +import { Modal, Input, Divider, Typography, Button, ConfigProvider, Select } from 'antd'; +import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; + +const { Text } = Typography; +const { TextArea } = Input; + +const DetailTag = (props) => { + const [confirmLoading, setConfirmLoading] = useState(false); + + const defaultData = { + tag_id: '', + tag_code: '', + tag_name: '', + tag_value: 'Off', + tag_type: 'alarm', + tag_description: '', + }; + + const [FormData, setFormData] = useState(defaultData); + + const handleCancel = () => { + props.setSelectedData(null); + props.setActionMode('list'); + }; + + const handleSave = async () => { + setConfirmLoading(true); + + // Validasi required fields + if (!FormData.tag_code) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Tag Code Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.tag_name) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Tag Name Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.tag_value) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Tag Value Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + if (!FormData.tag_type) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Kolom Tag Type Tidak Boleh Kosong', + }); + setConfirmLoading(false); + return; + } + + const payload = { + tag_code: FormData.tag_code, + tag_name: FormData.tag_name, + tag_value: FormData.tag_value, + tag_type: FormData.tag_type, + tag_description: FormData.tag_description, + }; + + try { + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 500)); + + const response = { + statusCode: FormData.tag_id ? 200 : 201, + data: { + tag_name: FormData.tag_name, + }, + }; + + console.log('Save Tag Response:', response); + + // Check if response is successful + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Data Tag "${response.data?.tag_name || FormData.tag_name}" berhasil ${ + FormData.tag_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 Tag 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 ( + + + + + + {!props.readOnly && ( + + )} + + , + ]} + > + {FormData && ( +
+ +
+ Tag Code + * + +
+
+ Tag Name + * + +
+
+ Tag Value + * + handleSelectChange('tag_type', value)} + disabled={props.readOnly} + style={{ width: '100%' }} + placeholder="Select Type" + options={[ + { value: 'alarm', label: 'Alarm' }, + { value: 'measurement', label: 'Measurement' }, + ]} + /> +
+
+ Tag Description +