Unit Name
*
@@ -221,4 +284,4 @@ const DetailUnit = (props) => {
);
};
-export default DetailUnit;
+export default DetailUnit;
\ No newline at end of file
diff --git a/src/pages/report/report/IndexReport.jsx b/src/pages/report/report/IndexReport.jsx
new file mode 100644
index 0000000..d849260
--- /dev/null
+++ b/src/pages/report/report/IndexReport.jsx
@@ -0,0 +1,38 @@
+import React, { memo, useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
+import { Typography } from 'antd';
+import ListReport from './component/ListReport';
+
+const { Text } = Typography;
+
+const IndexReport = memo(function IndexReport() {
+ const navigate = useNavigate();
+ const { setBreadcrumbItems } = useBreadcrumb();
+ const [selectedData, setSelectedData] = useState(null);
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (token) {
+ setBreadcrumbItems([
+ {
+ title: (
+
+ • Report
+
+ ),
+ },
+ ]);
+ } else {
+ navigate('/signin');
+ }
+ }, [navigate, setBreadcrumbItems]);
+
+ return (
+
+
+
+ );
+});
+
+export default IndexReport;
diff --git a/src/pages/report/report/component/ListReport.jsx b/src/pages/report/report/component/ListReport.jsx
new file mode 100644
index 0000000..974af15
--- /dev/null
+++ b/src/pages/report/report/component/ListReport.jsx
@@ -0,0 +1,163 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Button, Row, Col, Card, Input, DatePicker, Select, Typography } from 'antd';
+import TableList from '../../../../components/Global/TableList';
+import dayjs from 'dayjs';
+import { FileTextOutlined } from '@ant-design/icons';
+
+const { Text } = Typography;
+
+const ListReport = memo(function ListReport(props) {
+ const columns = [
+ {
+ title: 'No',
+ key: 'no',
+ width: '5%',
+ align: 'center',
+ render: (_, __, index) => index + 1,
+ },
+ {
+ title: 'Datetime',
+ dataIndex: 'datetime',
+ key: 'datetime',
+ width: '10%',
+ },
+ {
+ title: 'Tag Name',
+ dataIndex: 'tag_name',
+ key: 'tag_name',
+ width: '70%',
+ },
+ {
+ title: 'Value',
+ dataIndex: 'val',
+ key: 'val',
+ width: '10%',
+ },
+ {
+ title: 'Stat',
+ dataIndex: 'stat',
+ key: 'stat',
+ width: '10%',
+ },
+ ];
+
+ const [trigerFilter, setTrigerFilter] = useState(false);
+
+ const defaultFilter = { search: '' };
+ const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
+
+ const [plantSubSection, setPlantSubSection] = useState('Semua Plant');
+ const [startDate, setStartDate] = useState(dayjs('2025-09-30'));
+ const [endDate, setEndDate] = useState(dayjs('2025-10-09'));
+ const [periode, setPeriode] = useState('10 Menit');
+
+ const getAllReport = async (params) => {
+ return {
+ data: [],
+ };
+ };
+
+ const handleReset = () => {
+ setPlantSubSection('Semua Plant');
+ setStartDate(dayjs('2025-09-30'));
+ setEndDate(dayjs('2025-10-09'));
+ setPeriode('10 Menit');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ Plant Sub Section
+
+
+
+
+
+
+
+ Tanggal Mulai
+
+
+
+
+
+
+
+ Tanggal Akhir
+
+
+
+
+
+
+ Periode
+
+
+
+
+
+
+ }>
+ Tampilkan
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+export default ListReport;
diff --git a/src/pages/report/trending/IndexTrending.jsx b/src/pages/report/trending/IndexTrending.jsx
new file mode 100644
index 0000000..73540e7
--- /dev/null
+++ b/src/pages/report/trending/IndexTrending.jsx
@@ -0,0 +1,38 @@
+import React, { memo, useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
+import { Typography } from 'antd';
+import ReportTrending from './ReportTrending';
+
+const { Text } = Typography;
+
+const IndexTrending = memo(function IndexTrending() {
+ const navigate = useNavigate();
+ const { setBreadcrumbItems } = useBreadcrumb();
+ const [selectedData, setSelectedData] = useState(null);
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (token) {
+ setBreadcrumbItems([
+ {
+ title: (
+
+ • Trending
+
+ ),
+ },
+ ]);
+ } else {
+ navigate('/signin');
+ }
+ }, [navigate, setBreadcrumbItems]);
+
+ return (
+
+
+
+ );
+});
+
+export default IndexTrending;
diff --git a/src/pages/report/trending/ReportTrending.jsx b/src/pages/report/trending/ReportTrending.jsx
new file mode 100644
index 0000000..04a1eb5
--- /dev/null
+++ b/src/pages/report/trending/ReportTrending.jsx
@@ -0,0 +1,222 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Button, Row, Col, Card, Input, DatePicker, Select, Typography } from 'antd';
+import dayjs from 'dayjs';
+import { FileTextOutlined } from '@ant-design/icons';
+import { ResponsiveLine } from '@nivo/line';
+import './trending.css';
+
+const { Text } = Typography;
+
+const tagTrendingData = [
+ {
+ id: 'TEMP_SENSOR_1',
+ color: '#FF6B4A',
+ data: [
+ { y: '08:00', x: 75 },
+ { y: '08:05', x: 76 },
+ { y: '08:10', x: 75 },
+ { y: '08:15', x: 77 },
+ { y: '08:20', x: 76 },
+ { y: '08:25', x: 78 },
+ { y: '08:30', x: 79 },
+ ],
+ },
+ {
+ id: 'GAS_LEAK_SENSOR_1',
+ color: '#4ECDC4',
+ data: [
+ { y: '08:00', x: 10 },
+ { y: '08:05', x: 150 },
+ { y: '08:10', x: 40 },
+ { y: '08:15', x: 20 },
+ { y: '08:20', x: 15 },
+ { y: '08:25', x: 18 },
+ { y: '08:30', x: 25 },
+ ],
+ },
+ {
+ id: 'PRESSURE_SENSOR_1',
+ color: '#FFE66D',
+ data: [
+ { y: '08:00', x: 1.2 },
+ { y: '08:05', x: 1.3 },
+ { y: '08:10', x: 1.2 },
+ { y: '08:15', x: 1.4 },
+ { y: '08:20', x: 1.5 },
+ { y: '08:25', x: 1.3 },
+ { y: '08:30', x: 1.2 },
+ ],
+ },
+];
+
+const ReportTrending = memo(function ReportTrending(props) {
+ const [trigerFilter, setTrigerFilter] = useState(false);
+
+ const defaultFilter = { search: '' };
+ const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
+
+ const [plantSubSection, setPlantSubSection] = useState('Semua Plant');
+ const [startDate, setStartDate] = useState(dayjs('2025-09-30'));
+ const [endDate, setEndDate] = useState(dayjs('2025-10-09'));
+ const [periode, setPeriode] = useState('10 Menit');
+
+ const getAllReport = async (params) => {
+ return {
+ data: [],
+ };
+ };
+
+ const handleReset = () => {
+ setPlantSubSection('Semua Plant');
+ setStartDate(dayjs('2025-09-30'));
+ setEndDate(dayjs('2025-10-09'));
+ setPeriode('10 Menit');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ Plant Sub Section
+
+
+
+
+
+
+
+ Tanggal Mulai
+
+
+
+
+
+
+
+ Tanggal Akhir
+
+
+
+
+
+
+ Periode
+
+
+
+
+
+
+ }>
+ Tampilkan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+export default ReportTrending;
diff --git a/src/pages/history/trending/trending.css b/src/pages/report/trending/trending.css
similarity index 100%
rename from src/pages/history/trending/trending.css
rename to src/pages/report/trending/trending.css
diff --git a/src/pages/role/IndexRole.jsx b/src/pages/role/IndexRole.jsx
index 0829656..c446522 100644
--- a/src/pages/role/IndexRole.jsx
+++ b/src/pages/role/IndexRole.jsx
@@ -1,144 +1,70 @@
import React, { memo, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
-import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
-import { Form, Typography } from 'antd';
import ListRole from './component/ListRole';
import DetailRole from './component/DetailRole';
-import { createRole, updateRole } from '../../api/role';
-import { NotifAlert, NotifOk } from '../../components/Global/ToastNotif';
+import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
+import { Typography } from 'antd';
const { Text } = Typography;
const IndexRole = memo(function IndexRole() {
const navigate = useNavigate();
const { setBreadcrumbItems } = useBreadcrumb();
- const [form] = Form.useForm();
const [actionMode, setActionMode] = useState('list');
const [selectedData, setSelectedData] = useState(null);
- const [isModalVisible, setIsModalVisible] = useState(false);
const [readOnly, setReadOnly] = useState(false);
+ const [showModal, setShowModal] = useState(false);
+
+ const setMode = (param) => {
+ setShowModal(true);
+ switch (param) {
+ case 'add':
+ setReadOnly(false);
+ break;
+ case 'edit':
+ setReadOnly(false);
+ break;
+ case 'preview':
+ setReadOnly(true);
+ break;
+ default:
+ setShowModal(false);
+ break;
+ }
+ setActionMode(param);
+ };
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
setBreadcrumbItems([
- {
- title: (
-
- • Role
-
- ),
- },
+ { title:
• Role },
]);
} else {
navigate('/signin');
}
}, [navigate, setBreadcrumbItems]);
- useEffect(() => {
- if (actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') {
- setIsModalVisible(true);
- setReadOnly(actionMode === 'preview');
-
- if (actionMode === 'add') {
- form.resetFields();
- } else if (selectedData) {
- form.setFieldsValue(selectedData);
- }
- } else {
- setIsModalVisible(false);
- form.resetFields();
- }
- }, [actionMode, selectedData, form]);
-
- const handleCancel = () => {
- setActionMode('list');
- setSelectedData(null);
- form.resetFields();
- };
-
- const handleOk = () => {
- if (readOnly) {
- handleCancel();
- return;
- }
-
- form.validateFields()
- .then(async (values) => {
- try {
- let response;
- if (actionMode === 'edit') {
- response = await updateRole(selectedData.role_id, values);
- console.log('Update Response:', response);
-
- const isSuccess = response.statusCode === 200 || response.statusCode === 201;
- if (isSuccess) {
- NotifAlert({
- icon: 'success',
- title: 'Berhasil',
- message: `Data Role "${values.role_name}" berhasil diubah.`,
- });
- handleCancel();
- } else {
- NotifOk({
- icon: 'error',
- title: 'Gagal',
- message: response.message || 'Gagal mengubah data Role',
- });
- }
- } else if (actionMode === 'add') {
- response = await createRole(values);
- console.log('Create Response:', response);
-
- const isSuccess = response.statusCode === 200 || response.statusCode === 201;
- if (isSuccess) {
- NotifAlert({
- icon: 'success',
- title: 'Berhasil',
- message: `Data Role "${values.role_name}" berhasil ditambahkan.`,
- });
- handleCancel();
- } else {
- NotifOk({
- icon: 'error',
- title: 'Gagal',
- message: response.message || 'Gagal menambahkan data Role',
- });
- }
- }
- } catch (error) {
- console.error('Error:', error);
- NotifOk({
- icon: 'error',
- title: 'Error',
- message: 'Terjadi kesalahan saat menyimpan data',
- });
- }
- })
- .catch((info) => {
- console.log('Validate Failed:', info);
- });
- };
-
return (
);
});
-export default IndexRole;
+export default IndexRole;
\ No newline at end of file
diff --git a/src/pages/role/component/DetailRole.jsx b/src/pages/role/component/DetailRole.jsx
index 8d930a8..1c24dbf 100644
--- a/src/pages/role/component/DetailRole.jsx
+++ b/src/pages/role/component/DetailRole.jsx
@@ -1,72 +1,213 @@
-import React from 'react';
-import { Modal, Form, Input, InputNumber, Switch, Row, Col, Typography, Divider } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, InputNumber, Row, Col } from 'antd';
+import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
+import { validateRun } from '../../../Utils/validate';
+import { createRole, updateRole } from '../../../api/role';
const { Text } = Typography;
+const { TextArea } = Input;
-const DetailRole = ({ visible, onCancel, onOk, form, editingKey, readOnly }) => {
- const modalTitle = editingKey ? (readOnly ? 'Preview Role' : 'Edit Role') : 'Tambah Role';
+const DetailRole = (props) => {
+ const [confirmLoading, setConfirmLoading] = useState(false);
+
+ const defaultData = {
+ role_id: '',
+ role_name: '',
+ role_level: null,
+ role_description: '',
+ is_active: true,
+ };
+
+ const [formData, setFormData] = useState(defaultData);
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ };
+
+ const handleInputNumberChange = (value) => {
+ setFormData({ ...formData, role_level: value });
+ };
+
+ const handleStatusToggle = (checked) => {
+ setFormData({ ...formData, is_active: checked });
+ };
+
+ const handleCancel = () => {
+ props.setSelectedData(null);
+ props.setActionMode('list');
+ };
+
+ const handleSave = async () => {
+ setConfirmLoading(true);
+
+ const validationRules = [
+ { field: 'role_name', label: 'Nama Role', required: true },
+ { field: 'role_level', label: 'Level', required: true },
+ ];
+
+ if (
+ validateRun(formData, validationRules, (errorMessages) => {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: errorMessages,
+ });
+ setConfirmLoading(false);
+ })
+ ) {
+ return;
+ }
+
+ try {
+ const payload = {
+ role_name: formData.role_name,
+ role_level: formData.role_level,
+ role_description: formData.role_description,
+ is_active: formData.is_active,
+ };
+
+ const response = formData.role_id
+ ? await updateRole(formData.role_id, payload)
+ : await createRole(payload);
+
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ const action = formData.role_id ? 'diubah' : 'ditambahkan';
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Role "${payload.role_name}" berhasil ${action}.`,
+ });
+ props.setActionMode('list');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response?.message || 'Gagal menyimpan data.',
+ });
+ }
+ } catch (error) {
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: error.message || 'Terjadi kesalahan pada server.',
+ });
+ } finally {
+ setConfirmLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (props.selectedData) {
+ setFormData({ ...defaultData, ...props.selectedData });
+ } else {
+ setFormData(defaultData);
+ }
+ }, [props.showModal, props.selectedData]);
return (
{modalTitle}}
- open={visible}
- onCancel={onCancel}
- onOk={onOk}
- okText="Simpan"
- cancelText="Batal"
- okButtonProps={{ disabled: readOnly }}
- destroyOnClose
-
+ title={
+
+ {props.actionMode === 'add'
+ ? 'Tambah Role'
+ : props.actionMode === 'preview'
+ ? 'Preview Role'
+ : 'Edit Role'}
+
+ }
+ open={props.showModal}
+ onCancel={handleCancel}
+ footer={[
+
+
+
+
+
+ {!props.readOnly && (
+
+ )}
+
+ ,
+ ]}
>
- Status}
- valuePropName="checked"
- initialValue={true}
- >
-
-
-
-
-
- Nama Role}
- rules={[{ required: true, message: 'Nama Role wajib diisi!' }]}
- >
-
-
-
-
- Level}
- rules={[{ required: true, message: 'Level wajib diisi!' }]}
- >
-
-
-
-
-
- Deskripsi Role}
- >
-
+ Status
+
+
-
-
+ {formData.is_active ? 'Active' : 'Inactive'}
+
+
+