diff --git a/src/pages/notification/IndexNotification.jsx b/src/pages/notification/IndexNotification.jsx
index 3f45aa4..a4828dd 100644
--- a/src/pages/notification/IndexNotification.jsx
+++ b/src/pages/notification/IndexNotification.jsx
@@ -1,29 +1,71 @@
-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, Typography } from 'antd';
+import ListNotification from './component/ListNotification';
+import DetailNotification from './component/DetailNotification';
const { Text } = Typography;
const IndexNotification = memo(function IndexNotification() {
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);
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
setBreadcrumbItems([
- { title: • Notifikasi }
+ {
+ title: (
+
+ • Notifikasi
+
+ ),
+ },
]);
} else {
navigate('/signin');
}
- }, []);
+ }, [navigate, setBreadcrumbItems]);
+
+ useEffect(() => {
+ if (actionMode === 'preview') {
+ setIsModalVisible(true);
+ if (selectedData) {
+ form.setFieldsValue(selectedData);
+ }
+ } else {
+ setIsModalVisible(false);
+ form.resetFields();
+ }
+ }, [actionMode, selectedData, form]);
+
+ const handleCancel = () => {
+ setActionMode('list');
+ setSelectedData(null);
+ form.resetFields();
+ };
return (
-
-
Notifikasi Page
-
+
+
+
+
);
});
diff --git a/src/pages/notification/component/DetailNotification.jsx b/src/pages/notification/component/DetailNotification.jsx
new file mode 100644
index 0000000..2f19a7e
--- /dev/null
+++ b/src/pages/notification/component/DetailNotification.jsx
@@ -0,0 +1,169 @@
+import React, { memo } from 'react';
+import { Modal, Row, Col, Tag, Divider } from 'antd';
+import { CloseCircleFilled, WarningFilled, CheckCircleFilled, InfoCircleFilled } from '@ant-design/icons';
+
+const DetailNotification = memo(function DetailNotification({ visible, onCancel, form, selectedData }) {
+ const getIconAndColor = (type) => {
+ switch (type) {
+ case 'critical':
+ return {
+ IconComponent: CloseCircleFilled,
+ color: '#ff4d4f',
+ bgColor: '#fff1f0',
+ tagColor: 'error',
+ };
+ case 'warning':
+ return {
+ IconComponent: WarningFilled,
+ color: '#faad14',
+ bgColor: '#fffbe6',
+ tagColor: 'warning',
+ };
+ case 'resolved':
+ return {
+ IconComponent: CheckCircleFilled,
+ color: '#52c41a',
+ bgColor: '#f6ffed',
+ tagColor: 'success',
+ };
+ default:
+ return {
+ IconComponent: InfoCircleFilled,
+ color: '#1890ff',
+ bgColor: '#e6f7ff',
+ tagColor: 'processing',
+ };
+ }
+ };
+
+ const { IconComponent, color, bgColor, tagColor } = selectedData ? getIconAndColor(selectedData.type) : {};
+
+ return (
+
+ {selectedData && (
+
+ {/* Header with Icon and Status */}
+
+
+ {IconComponent && }
+
+
+
+ {selectedData.type.toUpperCase()}
+
+
+ {selectedData.title}
+
+
+
+
+
+
+ {/* Information Grid */}
+
+
+
+
+ PLC
+
+
+ {selectedData.plc}
+
+
+
+
+
+
Tag
+
+ {selectedData.tag}
+
+
+
+
+
+
+
+
+
+ Engineer
+
+
+ {selectedData.engineer}
+
+
+
+
+
+
+ Waktu
+
+
+ {selectedData.time}
+
+
+
+
+
+
+
+ {/* Status */}
+
+
Status
+
+ {selectedData.isRead ? 'Sudah Dibaca' : 'Belum Dibaca'}
+
+
+
+ {/* Additional Info */}
+
+
+ Catatan: Notifikasi ini telah dikirim ke engineer yang bersangkutan
+ untuk ditindaklanjuti sesuai dengan prosedur yang berlaku.
+
+
+
+ )}
+
+ );
+});
+
+export default DetailNotification;
diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx
new file mode 100644
index 0000000..6ed7f4a
--- /dev/null
+++ b/src/pages/notification/component/ListNotification.jsx
@@ -0,0 +1,399 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Button, Row, Col, Card, Badge } from 'antd';
+import {
+ CloseCircleFilled,
+ WarningFilled,
+ CheckCircleFilled,
+ InfoCircleFilled,
+} from '@ant-design/icons';
+import { useNavigate } from 'react-router-dom';
+
+// Dummy data untuk notifikasi
+const initialNotifications = [
+ {
+ id: 1,
+ type: 'critical',
+ title: 'Compressor Unit A - Overheat detected (85°C)',
+ plc: 'PLC-001',
+ tag: 'A1-TEMP',
+ engineer: 'Siti Nurhaliza',
+ time: '2 menit lalu',
+ status: 'unread',
+ isRead: false,
+ },
+ {
+ id: 2,
+ type: 'warning',
+ title: 'Compressor Unit C - Pressure slightly high (7.2 bar)',
+ plc: 'PLC-003',
+ tag: 'C3-PRESS',
+ engineer: 'Joko Widodo',
+ time: '15 menit lalu',
+ status: 'unread',
+ isRead: false,
+ },
+ {
+ id: 3,
+ type: 'resolved',
+ title: 'Compressor Unit B - Vibration issue resolved',
+ plc: 'PLC-002',
+ tag: 'B2-VIB',
+ engineer: 'Rudi Santoso',
+ time: '1 jam lalu',
+ status: 'read',
+ isRead: true,
+ },
+ {
+ id: 4,
+ type: 'critical',
+ title: 'Compressor Unit E - Low oil pressure (1.5 bar)',
+ plc: 'PLC-005',
+ tag: 'E1-OIL',
+ engineer: 'Ahmad Yani',
+ time: '2 jam lalu',
+ status: 'unread',
+ isRead: false,
+ },
+ {
+ id: 5,
+ type: 'warning',
+ title: 'Compressor Unit D - Temperature rising (78°C)',
+ plc: 'PLC-004',
+ tag: 'D2-TEMP',
+ engineer: 'Budi Santoso',
+ time: '3 jam lalu',
+ status: 'read',
+ isRead: true,
+ },
+ {
+ id: 6,
+ type: 'resolved',
+ title: 'Compressor Unit F - Maintenance completed',
+ plc: 'PLC-006',
+ tag: 'F1-MAIN',
+ engineer: 'Dewi Lestari',
+ time: '5 jam lalu',
+ status: 'read',
+ isRead: true,
+ },
+];
+
+const ListNotification = memo(function ListNotification(props) {
+ const [notifications, setNotifications] = useState(initialNotifications);
+ const [activeTab, setActiveTab] = useState('all');
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ navigate('/signin');
+ }
+ }, [navigate]);
+
+ const getIconAndColor = (type) => {
+ switch (type) {
+ case 'critical':
+ return {
+ IconComponent: CloseCircleFilled,
+ color: '#ff4d4f',
+ bgColor: '#fff1f0',
+ };
+ case 'warning':
+ return {
+ IconComponent: WarningFilled,
+ color: '#faad14',
+ bgColor: '#fffbe6',
+ };
+ case 'resolved':
+ return {
+ IconComponent: CheckCircleFilled,
+ color: '#52c41a',
+ bgColor: '#f6ffed',
+ };
+ default:
+ return {
+ IconComponent: InfoCircleFilled,
+ color: '#1890ff',
+ bgColor: '#e6f7ff',
+ };
+ }
+ };
+
+ const filterNotifications = (status) => {
+ if (status === 'all') return notifications;
+ if (status === 'unread') return notifications.filter((n) => !n.isRead);
+ if (status === 'read') return notifications.filter((n) => n.isRead);
+ return notifications;
+ };
+
+ const getUnreadCount = () => {
+ return notifications.filter((n) => !n.isRead).length;
+ };
+
+ const handleViewDetail = (notification) => {
+ props.setSelectedData(notification);
+ props.setActionMode('preview');
+
+ // Mark as read
+ setNotifications((prev) =>
+ prev.map((n) => (n.id === notification.id ? { ...n, isRead: true, status: 'read' } : n))
+ );
+ };
+
+ const filteredNotifications = filterNotifications(activeTab);
+
+ const tabButtonStyle = (isActive) => ({
+ padding: '12px 16px',
+ border: 'none',
+ background: 'none',
+ cursor: 'pointer',
+ fontSize: '14px',
+ fontWeight: 500,
+ color: isActive ? '#FF6B35' : '#595959',
+ borderBottom: isActive ? '2px solid #FF6B35' : '2px solid transparent',
+ marginBottom: '-1px',
+ transition: 'all 0.3s',
+ });
+
+ return (
+
+
+
+
+
+ Notifikasi
+
+
+ Riwayat notifikasi yang dikirim ke engineer
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+
+
+ {/* Notification List */}
+
+ {filteredNotifications.length === 0 ? (
+
+ Tidak ada notifikasi
+
+ ) : (
+ filteredNotifications.map((notification) => {
+ const { IconComponent, color, bgColor } = getIconAndColor(
+ notification.type
+ );
+
+ return (
+
{
+ e.currentTarget.style.boxShadow =
+ '0 2px 8px rgba(0,0,0,0.06)';
+ e.currentTarget.style.backgroundColor = '#f6f9ff';
+ e.currentTarget.style.borderColor = '#d6e4ff';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.boxShadow = 'none';
+ e.currentTarget.style.backgroundColor = '#ffffff';
+ e.currentTarget.style.borderColor = '#f0f0f0';
+ }}
+ >
+ {/* Dot for unread */}
+ {!notification.isRead && (
+
+ )}
+
+
+ {/* Icon */}
+
+
+
+
+ {/* Content */}
+
+
+ {notification.title}
+
+
+
+
+ {notification.plc}
+
+
+ •
+
+
+ Tag: {notification.tag}
+
+
+
+
+
+ Engineer:{' '}
+
+ {notification.engineer}
+
+
+
+ {notification.time}
+
+
+
+
+ {/* Button */}
+
+
+
+ );
+ })
+ )}
+
+
+
+
+
+ );
+});
+
+export default ListNotification;