diff --git a/src/App.jsx b/src/App.jsx
index 79a229f..c7de63d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -15,6 +15,7 @@ import IndexTag from './pages/master/tag/IndexTag';
import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice';
import IndexErrorCode from './pages/master/errorCode/IndexErrorCode';
import IndexPlantSection from './pages/master/plantSection/IndexPlantSection';
+import IndexStatus from './pages/master/status/IndexStatus';
// History
import IndexTrending from './pages/history/trending/IndexTrending';
@@ -54,6 +55,7 @@ const App = () => {
} />
} />
} />
+ } />
}>
diff --git a/src/layout/LayoutMenu.jsx b/src/layout/LayoutMenu.jsx
index 382a1dc..9124704 100644
--- a/src/layout/LayoutMenu.jsx
+++ b/src/layout/LayoutMenu.jsx
@@ -66,6 +66,11 @@ const allItems = [
icon: ,
label: Error Code,
},
+ {
+ key: 'master-status',
+ icon: ,
+ label: Status,
+ },
],
},
{
diff --git a/src/pages/history/report/IndexReport.jsx b/src/pages/history/report/IndexReport.jsx
index 44c3b7a..4a64fcc 100644
--- a/src/pages/history/report/IndexReport.jsx
+++ b/src/pages/history/report/IndexReport.jsx
@@ -1,13 +1,46 @@
-import React, { memo, useEffect } from 'react';
+import React, { memo, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
-import { Typography } from 'antd';
+import { Typography, Table } from 'antd';
const { Text } = Typography;
+// Mock Data
+const initialData = [
+ {
+ key: '1',
+ plantSubSection: 'Section A',
+ device: 'Device 1',
+ errorCode: 'E-101',
+ status: 'Warning',
+ },
+ {
+ key: '2',
+ plantSubSection: 'Section B',
+ device: 'Device 2',
+ errorCode: 'E-102',
+ status: 'Alarm',
+ },
+ {
+ key: '3',
+ plantSubSection: 'Section C',
+ device: 'Device 3',
+ errorCode: 'E-103',
+ status: 'Done',
+ },
+ {
+ key: '4',
+ plantSubSection: 'Section A',
+ device: 'Device 4',
+ errorCode: 'E-104',
+ status: 'Warning',
+ },
+];
+
const IndexReport = memo(function IndexReport() {
const navigate = useNavigate();
const { setBreadcrumbItems } = useBreadcrumb();
+ const [data, setData] = useState(initialData);
useEffect(() => {
const token = localStorage.getItem('token');
@@ -21,9 +54,62 @@ const IndexReport = memo(function IndexReport() {
}
}, []);
+ const getTitleStyle = (statusName) => {
+ let backgroundColor;
+ switch (statusName.toLowerCase()) {
+ case 'done':
+ backgroundColor = '#52c41a'; // green
+ break;
+ case 'warning':
+ backgroundColor = '#faad14'; // orange
+ break;
+ case 'alarm':
+ backgroundColor = '#f5222d'; // red
+ break;
+ case 'critical':
+ backgroundColor = '#000000'; // black
+ break;
+ default:
+ backgroundColor = 'transparent';
+ }
+ return {
+ backgroundColor,
+ color: '#fff',
+ padding: '2px 8px',
+ borderRadius: '4px',
+ display: 'inline-block'
+ };
+ };
+
+ const columns = [
+ {
+ title: 'Plant Sub Section',
+ dataIndex: 'plantSubSection',
+ key: 'plantSubSection',
+ },
+ {
+ title: 'Device',
+ dataIndex: 'device',
+ key: 'device',
+ },
+ {
+ title: 'Error Code',
+ dataIndex: 'errorCode',
+ key: 'errorCode',
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ key: 'status',
+ render: (status) => (
+ {status}
+ ),
+ },
+ ];
+
return (
-
-
Report Page
+
);
});
diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx
index b015d6c..c765714 100644
--- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx
+++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx
@@ -45,9 +45,9 @@ const ListBrandDevice = ({
key: 'action',
render: (_, record) => (
- } onClick={() => handlePreview(record)} />
- } onClick={() => handleEdit(record)} />
- } onClick={() => handleDelete(record)} />
+ } onClick={() => handlePreview(record)} />
+ } onClick={() => handleEdit(record)} />
+ } onClick={() => handleDelete(record)} />
),
},
diff --git a/src/pages/master/plantSection/component/ListPlantSection.jsx b/src/pages/master/plantSection/component/ListPlantSection.jsx
index dfb7c3f..f10810a 100644
--- a/src/pages/master/plantSection/component/ListPlantSection.jsx
+++ b/src/pages/master/plantSection/component/ListPlantSection.jsx
@@ -47,9 +47,9 @@ const ListPlantSection = ({
key: 'action',
render: (_, record) => (
- } onClick={() => handlePreview(record)} />
- } onClick={() => handleEdit(record)} />
- } onClick={() => handleDelete(record)} />
+ } onClick={() => handlePreview(record)} />
+ } onClick={() => handleEdit(record)} />
+ } onClick={() => handleDelete(record)} />
),
},
diff --git a/src/pages/master/status/IndexStatus.jsx b/src/pages/master/status/IndexStatus.jsx
new file mode 100644
index 0000000..86e1421
--- /dev/null
+++ b/src/pages/master/status/IndexStatus.jsx
@@ -0,0 +1,167 @@
+import React, { memo, useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
+import { Typography } from 'antd';
+import ListStatus from './component/ListStatus';
+import DetailStatus from './component/DetailStatus';
+
+import { NotifConfirmDialog, NotifAlert } from '../../../components/Global/ToastNotif';
+
+const { Text } = Typography;
+
+// Mock Data
+const initialData = [
+ {
+ key: '3',
+ statusCode: 3,
+ statusName: 'Done',
+ description: 'Indicates that the process is complete.',
+ },
+ {
+ key: '1',
+ statusCode: 1,
+ statusName: 'Warning',
+ description: 'Indicates a warning condition.',
+ },
+ {
+ key: '2',
+ statusCode: 2,
+ statusName: 'Alarm',
+ description: 'Indicates an alarm condition.',
+ },
+ {
+ key: '4',
+ statusCode: 4,
+ statusName: 'Critical',
+ description: 'Indicates a critical condition.',
+ },
+];
+
+const IndexStatus = memo(function IndexStatus() {
+ const navigate = useNavigate();
+ const { setBreadcrumbItems } = useBreadcrumb();
+
+ const [data, setData] = useState(initialData);
+ const [actionMode, setActionMode] = useState('list');
+ const [selectedData, setSelectedData] = useState(null);
+ const [isModalVisible, setIsModalVisible] = useState(false);
+ const [readOnly, setReadOnly] = useState(false);
+
+ // Mock API function
+ const getAllStatus = async (params) => {
+ const { page = 1, limit = 10, search = '' } = Object.fromEntries(params.entries());
+
+ let filteredData = data;
+ if (search) {
+ filteredData = data.filter(item =>
+ item.statusName.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');
+ if (token) {
+ setBreadcrumbItems([
+ { title:
• Master },
+ { title:
Status }
+ ]);
+ } else {
+ navigate('/signin');
+ }
+ }, [navigate, setBreadcrumbItems]);
+
+ useEffect(() => {
+ if (actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') {
+ setIsModalVisible(true);
+ setReadOnly(actionMode === 'preview');
+ } else {
+ setIsModalVisible(false);
+ }
+ }, [actionMode]);
+
+ const handleDataSaved = (values) => {
+ let newData = [...data];
+ if (values.key) { // Editing
+ const index = newData.findIndex((item) => values.key === item.key);
+ if (index > -1) {
+ newData.splice(index, 1, values);
+ }
+ } else { // Adding
+ const newKey = (Math.max(...data.map(item => parseInt(item.key))) + 1).toString();
+ newData = [{ key: newKey, ...values }, ...newData];
+ }
+ setData(newData);
+ };
+
+ const handleEdit = (record) => {
+ setSelectedData(record);
+ setActionMode('edit');
+ };
+
+ const handlePreview = (record) => {
+ setSelectedData(record);
+ setActionMode('preview');
+ };
+
+ const handleDelete = (record) => {
+ NotifConfirmDialog({
+ icon: 'question',
+ title: 'Konfirmasi',
+ message: `Apakah anda yakin ingin menghapus status "${record.statusName}"?`,
+ onConfirm: () => {
+ const newData = data.filter((item) => item.key !== record.key);
+ setData(newData);
+ NotifAlert({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Status "${record.statusName}" berhasil dihapus.`,
+ });
+ },
+ });
+ };
+
+ return (
+
+
+
+
+ );
+});
+
+export default IndexStatus;
\ No newline at end of file
diff --git a/src/pages/master/status/component/DetailStatus.jsx b/src/pages/master/status/component/DetailStatus.jsx
new file mode 100644
index 0000000..a5a8274
--- /dev/null
+++ b/src/pages/master/status/component/DetailStatus.jsx
@@ -0,0 +1,160 @@
+import React, { useEffect, useState } from 'react';
+import { Modal, Input, Divider, Typography, Button, ConfigProvider, InputNumber, Form } from 'antd';
+import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
+
+const { Text } = Typography;
+const { TextArea } = Input;
+
+const DetailStatus = (props) => {
+ const [form] = Form.useForm();
+ const [confirmLoading, setConfirmLoading] = useState(false);
+
+ const defaultData = {
+ key: '',
+ statusCode: '',
+ statusName: '',
+ description: '',
+ };
+
+ const [FormData, setFormData] = useState(defaultData);
+
+ const handleCancel = () => {
+ props.setSelectedData(null);
+ props.setActionMode('list');
+ form.resetFields();
+ };
+
+ const handleSave = async () => {
+ try {
+ const values = await form.validateFields();
+ setConfirmLoading(true);
+
+ const payload = {
+ key: FormData.key,
+ ...values,
+ };
+
+ props.onDataSaved(payload);
+
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Status "${payload.statusName}" berhasil ${
+ payload.key ? 'diubah' : 'ditambahkan'
+ }.`,
+ });
+
+ setConfirmLoading(false);
+ props.setActionMode('list');
+ form.resetFields();
+ } catch (errorInfo) {
+ console.log('Failed:', errorInfo);
+ }
+ };
+
+ useEffect(() => {
+ if (props.selectedData) {
+ setFormData(props.selectedData);
+ form.setFieldsValue(props.selectedData);
+ } else {
+ setFormData(defaultData);
+ form.resetFields();
+ }
+ }, [props.showModal, props.selectedData, form]);
+
+ return (
+
+ {props.actionMode === 'add'
+ ? 'Tambah Data'
+ : props.actionMode === 'preview'
+ ? 'Preview Status'
+ : 'Edit Status'}
+
+ }
+ open={props.showModal}
+ onCancel={handleCancel}
+ footer={
+ !props.readOnly && (
+
+
+
+
+
+
+
+
+ )
+ }
+ >
+
+ Status Code}
+ rules={[{ required: true, message: 'Silakan masukkan kode status!' }]}
+ >
+
+
+ Status Name}
+ rules={[{ required: true, message: 'Silakan masukkan nama status!' }]}
+ >
+
+
+ Description}
+ rules={[{ required: true, message: 'Silakan masukkan deskripsi!' }]}
+ >
+
+
+
+
+ );
+};
+
+export default DetailStatus;
\ No newline at end of file
diff --git a/src/pages/master/status/component/ListStatus.jsx b/src/pages/master/status/component/ListStatus.jsx
new file mode 100644
index 0000000..945f778
--- /dev/null
+++ b/src/pages/master/status/component/ListStatus.jsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Card, Button, Row, Col, Typography, Space, ConfigProvider } from 'antd';
+import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
+
+const { Title, Text } = Typography;
+
+const ListStatus = ({
+ setActionMode,
+ handleEdit,
+ handleDelete,
+ handlePreview,
+ data,
+}) => {
+
+ const getCardStyle = (statusName) => {
+ let color;
+ switch (statusName.toLowerCase()) {
+ case 'done':
+ color = '#52c41a'; // green
+ break;
+ case 'warning':
+ color = '#faad14'; // orange
+ break;
+ case 'alarm':
+ color = '#f5222d'; // red
+ break;
+ case 'critical':
+ color = '#000000'; // black
+ break;
+ default:
+ color = '#d9d9d9'; // default antd border color
+ }
+ return { border: `2px solid ${color}` };
+ };
+
+ const getTitleStyle = (statusName) => {
+ let backgroundColor;
+ switch (statusName.toLowerCase()) {
+ case 'done':
+ backgroundColor = '#52c41a'; // green
+ break;
+ case 'warning':
+ backgroundColor = '#faad14'; // orange
+ break;
+ case 'alarm':
+ backgroundColor = '#f5222d'; // red
+ break;
+ case 'critical':
+ backgroundColor = '#000000'; // black
+ break;
+ default:
+ backgroundColor = 'transparent';
+ }
+ return {
+ backgroundColor,
+ color: '#fff',
+ padding: '2px 8px',
+ borderRadius: '4px',
+ display: 'inline-block'
+ };
+ };
+
+ return (
+
+
+
+
+ }
+ onClick={() => setActionMode('add')}
+ >
+ Tambah Data
+
+
+
+
+
+ {data.map(item => (
+
+ {item.statusName}}
+ style={getCardStyle(item.statusName)}
+ actions={[
+
+ } onClick={() => handlePreview(item)} />
+ } onClick={() => handleEdit(item)} />
+ } onClick={() => handleDelete(item)} />
+
+ ]}
+ >
+ Code: {item.statusCode}
+ Description: {item.description}
+
+
+ ))}
+
+
+ );
+};
+
+export default ListStatus;
\ No newline at end of file
diff --git a/src/pages/master/tag/component/ListTag.jsx b/src/pages/master/tag/component/ListTag.jsx
index dc882e0..7365b08 100644
--- a/src/pages/master/tag/component/ListTag.jsx
+++ b/src/pages/master/tag/component/ListTag.jsx
@@ -233,6 +233,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
render: (_, record) => (
}
onClick={() => showPreviewModal(record)}
style={{
@@ -241,6 +242,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
}}
/>
}
onClick={() => showEditModal(record)}
style={{
@@ -250,6 +252,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
/>
}
onClick={() => showDeleteDialog(record)}
style={{
@@ -483,4 +486,4 @@ const ListTag = memo(function ListTag(props) {
);
});
-export default ListTag;
+export default ListTag;
\ No newline at end of file