diff --git a/src/App.jsx b/src/App.jsx
index 9f77ca2..498ca76 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -36,6 +36,13 @@ import IndexUser from './pages/user/IndexUser';
import IndexMember from './pages/shiftManagement/member/IndexMember';
import SvgTest from './pages/home/SvgTest';
+import SvgOverview from './pages/home/SvgOverview';
+import SvgCompressorA from './pages/home/SvgCompressorA';
+import SvgCompressorB from './pages/home/SvgCompressorB';
+import SvgCompressorC from './pages/home/SvgCompressorC';
+import SvgAirDryerA from './pages/home/SvgAirDryerA';
+import SvgAirDryerB from './pages/home/SvgAirDryerB';
+import SvgAirDryerC from './pages/home/SvgAirDryerC';
const App = () => {
return (
@@ -53,6 +60,16 @@ const App = () => {
} />
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
}>
} />
} />
diff --git a/src/api/master-status.jsx b/src/api/master-status.jsx
new file mode 100644
index 0000000..d5ed80d
--- /dev/null
+++ b/src/api/master-status.jsx
@@ -0,0 +1,168 @@
+import { SendRequest } from '../components/Global/ApiRequest';
+
+const getAllStatus = async (queryParams) => {
+ try {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `status?${queryParams.toString()}`,
+ });
+
+ if (response.error) {
+ console.error('getAllStatus error response:', response);
+ return {
+ status: response.statusCode || 500,
+ data: {
+ data: [],
+ paging: {
+ page: 1,
+ limit: 10,
+ total: 0,
+ page_total: 0
+ },
+ total: 0
+ },
+ error: response.message
+ };
+ }
+
+ if (response.paging) {
+ const totalData = response.data?.[0]?.total_data || response.rows || response.data?.length || 0;
+
+ return {
+ status: response.statusCode || 200,
+ data: {
+ data: response.data || [],
+ paging: {
+ page: response.paging.current_page || 1,
+ limit: response.paging.current_limit || 10,
+ total: totalData,
+ page_total: response.paging.total_page || Math.ceil(totalData / (response.paging.current_limit || 10))
+ },
+ total: totalData
+ }
+ };
+ }
+
+ const params = Object.fromEntries(queryParams);
+ const currentPage = parseInt(params.page) || 1;
+ const currentLimit = parseInt(params.limit) || 10;
+
+ const allData = response.data || [];
+ const totalData = allData.length;
+
+ const startIndex = (currentPage - 1) * currentLimit;
+ const endIndex = startIndex + currentLimit;
+ const paginatedData = allData.slice(startIndex, endIndex);
+
+ return {
+ status: response.statusCode || 200,
+ data: {
+ data: paginatedData,
+ paging: {
+ page: currentPage,
+ limit: currentLimit,
+ total: totalData,
+ page_total: Math.ceil(totalData / currentLimit)
+ },
+ total: totalData
+ }
+ };
+ } catch (error) {
+ console.error('getAllStatus catch error:', error);
+ return {
+ status: 500,
+ data: {
+ data: [],
+ paging: {
+ page: 1,
+ limit: 10,
+ total: 0,
+ page_total: 0
+ },
+ total: 0
+ },
+ error: error.message
+ };
+ }
+};
+
+const getStatusById = async (id) => {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `status/${id}`,
+ });
+ return response.data;
+};
+
+const createStatus = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'post',
+ prefix: `status`,
+ params: queryParams,
+ });
+
+ if (response.error) {
+ return {
+ statusCode: response.statusCode || 500,
+ data: null,
+ message: response.message || 'Request failed',
+ rows: 0
+ };
+ }
+
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data?.[0] || response.data,
+ message: response.message,
+ rows: response.rows
+ };
+};
+
+const updateStatus = async (status_id, queryParams) => {
+ const response = await SendRequest({
+ method: 'put',
+ prefix: `status/${status_id}`,
+ params: queryParams,
+ });
+
+ if (response.error) {
+ return {
+ statusCode: response.statusCode || 500,
+ data: null,
+ message: response.message || 'Request failed',
+ rows: 0
+ };
+ }
+
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data?.[0] || response.data,
+ message: response.message,
+ rows: response.rows
+ };
+};
+
+const deleteStatus = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'delete',
+ prefix: `status/${queryParams}`,
+ });
+
+ if (response.error) {
+ return {
+ statusCode: response.statusCode || 500,
+ data: null,
+ message: response.message || 'Request failed',
+ rows: 0
+ };
+ }
+
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message,
+ rows: response.rows
+ };
+};
+
+export { getAllStatus, getStatusById, createStatus, updateStatus, deleteStatus };
\ No newline at end of file
diff --git a/src/layout/LayoutMenu.jsx b/src/layout/LayoutMenu.jsx
index 6e0bc57..e3c9d62 100644
--- a/src/layout/LayoutMenu.jsx
+++ b/src/layout/LayoutMenu.jsx
@@ -26,6 +26,9 @@ import {
TeamOutlined,
ClockCircleOutlined,
CalendarOutlined,
+ DesktopOutlined,
+ NodeExpandOutlined,
+ GroupOutlined,
} from '@ant-design/icons';
const { Text } = Typography;
@@ -40,6 +43,48 @@ const allItems = [
),
},
+ {
+ key: 'dashboard-svg',
+ icon: ,
+ label: 'Dashboard',
+ children: [
+ {
+ key: 'dashboard-svg-overview',
+ icon: ,
+ label: Overview,
+ },
+ {
+ key: 'dashboard-svg-compressor-a',
+ icon: ,
+ label: Compressor A,
+ },
+ {
+ key: 'dashboard-svg-compressor-b',
+ icon: ,
+ label: Compressor B,
+ },
+ {
+ key: 'dashboard-svg-compressor-c',
+ icon: ,
+ label: Compressor C,
+ },
+ {
+ key: 'dashboard-svg-airdryer-a',
+ icon: ,
+ label: Air Dryer A,
+ },
+ {
+ key: 'dashboard-svg-airdryer-b',
+ icon: ,
+ label: Air Dryer B,
+ },
+ {
+ key: 'dashboard-svg-airdryer-c',
+ icon: ,
+ label: Air Dryer C,
+ },
+ ],
+ },
{
key: 'master',
icon: ,
@@ -60,16 +105,16 @@ const allItems = [
icon: ,
label: Device,
},
- {
- key: 'master-tag',
- icon: ,
- label: Tag,
- },
{
key: 'master-unit',
icon: ,
label: Unit,
},
+ {
+ key: 'master-tag',
+ icon: ,
+ label: Tag,
+ },
{
key: 'master-status',
icon: ,
@@ -163,6 +208,7 @@ const LayoutMenu = () => {
if (pathname === '/notification') return 'notification';
if (pathname === '/event-alarm') return 'event-alarm';
if (pathname === '/jadwal-shift') return 'jadwal-shift';
+ if (pathname === '/dashboard-svg') return 'dashboard-svg';
// Handle master routes
if (pathname.startsWith('/master/')) {
@@ -170,6 +216,12 @@ const LayoutMenu = () => {
return `master-${subPath}`;
}
+ // Handle master routes
+ if (pathname.startsWith('/dashboard-svg/')) {
+ const subPath = pathParts[1];
+ return `dashboard-svg-${subPath}`;
+ }
+
// Handle history routes
if (pathname.startsWith('/history/')) {
const subPath = pathParts[1];
@@ -188,6 +240,7 @@ const LayoutMenu = () => {
// Function to get parent key from menu key
const getParentKey = (key) => {
if (key.startsWith('master-')) return 'master';
+ if (key.startsWith('dashboard-svg-')) return 'dashboard-svg';
if (key.startsWith('history-')) return 'history';
if (key.startsWith('shift-')) return 'shift-management';
return null;
diff --git a/src/pages/home/SvgAirDryerA.jsx b/src/pages/home/SvgAirDryerA.jsx
new file mode 100644
index 0000000..37f07a3
--- /dev/null
+++ b/src/pages/home/SvgAirDryerA.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/air_dryer_A_rev.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgAirDryerA = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgAirDryerA;
diff --git a/src/pages/home/SvgAirDryerB.jsx b/src/pages/home/SvgAirDryerB.jsx
new file mode 100644
index 0000000..6250cea
--- /dev/null
+++ b/src/pages/home/SvgAirDryerB.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/air_dryer_B_rev.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgAirDryerB = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgAirDryerB;
diff --git a/src/pages/home/SvgAirDryerC.jsx b/src/pages/home/SvgAirDryerC.jsx
new file mode 100644
index 0000000..cdb61ce
--- /dev/null
+++ b/src/pages/home/SvgAirDryerC.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/air_dryer_C_rev.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgAirDryerC = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgAirDryerC;
diff --git a/src/pages/home/SvgCompressorA.jsx b/src/pages/home/SvgCompressorA.jsx
new file mode 100644
index 0000000..bc7ffd2
--- /dev/null
+++ b/src/pages/home/SvgCompressorA.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/test-new.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgCompressorA = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgCompressorA;
diff --git a/src/pages/home/SvgCompressorB.jsx b/src/pages/home/SvgCompressorB.jsx
new file mode 100644
index 0000000..fbf5871
--- /dev/null
+++ b/src/pages/home/SvgCompressorB.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/test-new.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgCompressorB = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgCompressorB;
diff --git a/src/pages/home/SvgCompressorC.jsx b/src/pages/home/SvgCompressorC.jsx
new file mode 100644
index 0000000..0983260
--- /dev/null
+++ b/src/pages/home/SvgCompressorC.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/test-new.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgCompressorC = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgCompressorC;
diff --git a/src/pages/home/SvgOverview.jsx b/src/pages/home/SvgOverview.jsx
new file mode 100644
index 0000000..d5b432d
--- /dev/null
+++ b/src/pages/home/SvgOverview.jsx
@@ -0,0 +1,20 @@
+import { useEffect, useState } from 'react';
+import { Card, Typography, Flex } from 'antd';
+import { setValSvg } from '../../components/Global/MqttConnection';
+import SvgTemplate from './SvgTemplate';
+import SvgViewer from './SvgViewer';
+
+const { Text } = Typography;
+
+const filePathSvg = '/svg/test-new.svg';
+const topicMqtt = 'PIU_GGCP/Devices/PB';
+
+const SvgOverview = () => {
+ return (
+
+
+
+ );
+};
+
+export default SvgOverview;
diff --git a/src/pages/home/SvgTemplate.jsx b/src/pages/home/SvgTemplate.jsx
new file mode 100644
index 0000000..da2a702
--- /dev/null
+++ b/src/pages/home/SvgTemplate.jsx
@@ -0,0 +1,19 @@
+const SvgTemplate = ({ children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default SvgTemplate;
diff --git a/src/pages/home/SvgTest.jsx b/src/pages/home/SvgTest.jsx
index 1b65a69..87d8beb 100644
--- a/src/pages/home/SvgTest.jsx
+++ b/src/pages/home/SvgTest.jsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { Card, Typography, Flex } from 'antd';
// import { ReactSVG } from 'react-svg';
import { setValSvg } from '../../components/Global/MqttConnection';
+import { ReactSVG } from 'react-svg';
const { Text } = Typography;
diff --git a/src/pages/home/SvgViewer.jsx b/src/pages/home/SvgViewer.jsx
new file mode 100644
index 0000000..242f012
--- /dev/null
+++ b/src/pages/home/SvgViewer.jsx
@@ -0,0 +1,19 @@
+// SvgViewer.jsx
+import { ReactSVG } from 'react-svg';
+
+const SvgViewer = ({ filePathSvg, topicMqtt, setValSvg }) => {
+ return (
+ {
+ svg.setAttribute('width', '100%');
+ svg.setAttribute('height', '100%');
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
+ if (setValSvg) setValSvg(topicMqtt, svg);
+ }}
+ style={{ width: '100%', height: '100%' }}
+ />
+ );
+};
+
+export default SvgViewer;
diff --git a/src/pages/master/shift/component/DetailShift.jsx b/src/pages/master/shift/component/DetailShift.jsx
index d8bd410..86d1b8d 100644
--- a/src/pages/master/shift/component/DetailShift.jsx
+++ b/src/pages/master/shift/component/DetailShift.jsx
@@ -2,6 +2,10 @@ import { useEffect, useState } from 'react';
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } from 'antd';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
import { createShift, updateShift } from '../../../../api/master-shift';
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+
+dayjs.extend(utc);
const { Text } = Typography;
@@ -198,16 +202,13 @@ const DetailShift = (props) => {
});
};
- // Helper function to extract time from ISO timestamp
+ // Helper function to extract time from ISO timestamp using dayjs
const extractTime = (timeString) => {
if (!timeString) return '';
// If it's ISO timestamp like "1970-01-01T08:00:00.000Z"
if (timeString.includes('T')) {
- const date = new Date(timeString);
- const hours = String(date.getUTCHours()).padStart(2, '0');
- const minutes = String(date.getUTCMinutes()).padStart(2, '0');
- return `${hours}:${minutes}`;
+ return dayjs.utc(timeString).format('HH:mm');
}
// If it's already in HH:mm:ss format, remove seconds
diff --git a/svg/air_dryer_A_rev.svg b/svg/air_dryer_A_rev.svg
new file mode 100644
index 0000000..53876ed
--- /dev/null
+++ b/svg/air_dryer_A_rev.svg
@@ -0,0 +1,238 @@
+
+
\ No newline at end of file
diff --git a/svg/air_dryer_B_rev.svg b/svg/air_dryer_B_rev.svg
new file mode 100644
index 0000000..4dff501
--- /dev/null
+++ b/svg/air_dryer_B_rev.svg
@@ -0,0 +1,238 @@
+
+
\ No newline at end of file
diff --git a/svg/air_dryer_C_rev.svg b/svg/air_dryer_C_rev.svg
new file mode 100644
index 0000000..3c13ba1
--- /dev/null
+++ b/svg/air_dryer_C_rev.svg
@@ -0,0 +1,238 @@
+
+
\ No newline at end of file