diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx
index 8812841..433bdc6 100644
--- a/src/pages/notification/component/ListNotification.jsx
+++ b/src/pages/notification/component/ListNotification.jsx
@@ -1,5 +1,5 @@
import React, { memo, useState, useEffect } from 'react';
-import { Button, Row, Col, Card, Badge, Input, Typography, Space } from 'antd';
+import { Button, Row, Col, Card, Badge, Input, Typography, Space, Divider, Modal, Tag, message } from 'antd';
import {
CloseCircleFilled,
WarningFilled,
@@ -11,16 +11,22 @@ import {
SendOutlined,
MailOutlined,
UserOutlined,
- FileTextOutlined,
HistoryOutlined,
EyeOutlined,
+ MobileOutlined,
+ CloseOutlined,
+ BookOutlined,
+ ToolOutlined,
+ FilePdfOutlined,
+ PlusOutlined,
+ ExclamationCircleOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
const { Text, Paragraph, Link } = Typography;
-// Dummy data untuk notifikasi
-const initialNotifications = [
+// Dummy data untuk notifikasi device
+const initialDeviceNotifications = [
{
id: 1,
type: 'critical',
@@ -31,7 +37,13 @@ const initialNotifications = [
location: 'Lantai 2, Area Produksi A, Zona 3',
details: 'Terjadi kenaikan suhu melebihi ambang batas pada Compressor Unit A. Pengecekan potensi kerusakan dibutuhkan.',
link: 'https://tinyurl.com/compA85',
+ subsection: 'Air Dryer A',
+ value: '85° C',
+ threshold: '≤75° C',
isRead: false,
+ plc: 'PLC-001',
+ status: 'Butuh Info lebih',
+ tag: 'A1-TEMP',
},
{
id: 2,
@@ -43,7 +55,13 @@ const initialNotifications = [
location: 'Lantai 1, Area Produksi C, Zona 1',
details: 'Tekanan mendekati ambang batas atas. Perlu monitoring lebih lanjut oleh engineer.',
link: 'https://tinyurl.com/compC72',
+ subsection: 'Main Compressor',
+ value: '7.2 bar',
+ threshold: '<7.5 bar',
isRead: false,
+ plc: 'PLC-002',
+ status: 'Monitoring',
+ tag: 'C1-PRESSURE',
},
{
id: 3,
@@ -55,14 +73,62 @@ const initialNotifications = [
location: 'Lantai 2, Area Produksi B, Zona 2',
details: 'Getaran pada Unit B telah kembali normal setelah perbaikan.',
link: 'https://tinyurl.com/compBresolved',
+ subsection: 'Motor Bearing',
+ value: 'Normal',
+ threshold: 'N/A',
isRead: true,
+ plc: 'PLC-003',
+ status: 'Resolved',
+ tag: 'B1-VIBRATION',
},
];
+// Dummy data untuk user history
+const userHistoryData = [
+ { id: 1, name: 'John Doe', phone: '081234567890', status: 'Delivered', timestamp: '04-11-2025 11:40 WIB' },
+ { id: 2, name: 'Jane Smith', phone: '087654321098', status: 'Delivered', timestamp: '04-11-2025 11:41 WIB' },
+ { id: 3, name: 'Peter Jones', phone: '082345678901', status: 'Delivered', timestamp: '04-11-2025 11:42 WIB' },
+];
+
+// Dummy data untuk log history
+const logHistoryData = [
+ {
+ id: 1,
+ timestamp: '04-11-2025 11:55 WIB',
+ addedBy: {
+ name: 'Budi Santoso',
+ phone: '081122334455'
+ },
+ description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.'
+ },
+ {
+ id: 2,
+ timestamp: '04-11-2025 11:45 WIB',
+ addedBy: {
+ name: 'John Doe',
+ phone: '081234567890'
+ },
+ description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
+ },
+ {
+ id: 3,
+ timestamp: '04-11-2025 11:40 WIB',
+ addedBy: {
+ name: 'Jane Smith',
+ phone: '087654321098'
+ },
+ description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
+ }
+];
+
+
const ListNotification = memo(function ListNotification(props) {
- const [notifications, setNotifications] = useState(initialNotifications);
+ const [notifications, setNotifications] = useState(initialDeviceNotifications);
const [activeTab, setActiveTab] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
+ const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null
+ const [isAddingLog, setIsAddingLog] = useState(false);
+ const [selectedNotification, setSelectedNotification] = useState(null);
const navigate = useNavigate();
useEffect(() => {
@@ -74,33 +140,30 @@ const ListNotification = memo(function ListNotification(props) {
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',
- };
+ 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 handleResend = (notification) => {
+ Modal.confirm({
+ title: 'Confirm Resend',
+ icon: ,
+ content: `Are you sure you want to resend the notification for "${notification.title}"?`,
+ okText: 'Resend',
+ cancelText: 'Cancel',
+ onOk() {
+ console.log('Resending notification:', notification.id);
+ // Di sini Anda bisa menambahkan logika pemanggilan API
+ message.success(`Notification for "${notification.title}" has been resent successfully.`);
+ },
+ onCancel() {
+ console.log('Resend cancelled');
+ },
+ });
+ };
const handleMarkAsRead = (id) => {
setNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
@@ -108,21 +171,14 @@ const ListNotification = memo(function ListNotification(props) {
};
const filteredNotifications = notifications
- .filter((n) => {
- if (activeTab === 'all') return true;
- if (activeTab === 'unread') return !n.isRead;
- if (activeTab === 'read') return n.isRead;
- return true;
- })
- .filter((n) => {
+ .filter(n => (activeTab === 'all' || (activeTab === 'unread' && !n.isRead) || (activeTab === 'read' && n.isRead)))
+ .filter(n => {
if (!searchTerm) return true;
const searchableText = `${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase();
return searchableText.includes(searchTerm.toLowerCase());
});
- const getUnreadCount = () => {
- return notifications.filter((n) => !n.isRead).length;
- };
+ const getUnreadCount = () => notifications.filter((n) => !n.isRead).length;
const tabButtonStyle = (isActive) => ({
padding: '12px 16px',
@@ -137,187 +193,493 @@ const ListNotification = memo(function ListNotification(props) {
transition: 'all 0.3s',
});
+ const renderDeviceNotifications = () => (
+
+ {filteredNotifications.length === 0 ? (
+ Tidak ada notifikasi
+ ) : (
+ filteredNotifications.map((notification) => {
+ const { IconComponent, color, bgColor } = getIconAndColor(notification.type);
+ return (
+ handleMarkAsRead(notification.id)}
+ >
+
+
+
+
+
+
+
+
+
+
{notification.title}
+
{notification.issue}
+
+ {!notification.isRead &&
}
+
+
+
+
+
+
+ {notification.description}
+ {notification.details}
+
+
+
+ {notification.timestamp}
+ {notification.location}
+
+ {notification.link}
+ } style={{ paddingLeft: '8px' }}
+ onClick={(e) => { e.stopPropagation(); handleResend(notification); }}
+ >
+ Resend
+
+
+
+
+
+
+ } title="User History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('user'); }} />
+ } title="Details" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('details'); setSelectedNotification(notification); }} />
+ } title="Log History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('log'); }} />
+
+
+
+
+
+
+ );
+ })
+ )}
+
+ );
+
+ const renderUserHistory = () => (
+ <>
+
+ {userHistoryData.map(user => (
+
+
+
+
+ {user.name}
+ |
+ {user.phone}
+ |
+
+
+
+
+
+ Success Delivered at {user.timestamp}
+
+
+
+ }>Resend
+
+
+
+ ))}
+
+ >
+ );
+
+ const renderLogHistory = () => (
+ <>
+
+ {/* Garis vertikal yang menyambung */}
+
+
+ {logHistoryData.map((log, index) => (
+
+ {/* Kolom Kiri: Branch/Timeline */}
+
+
+
+
+ {/* Kolom Kanan: Card */}
+
+
+
+
+
+
+
+ Added at {log.timestamp}
+
+
+ Added by: {log.addedBy.name}
+
+ {log.addedBy.phone}
+
+
+
+
+
+
+ {log.description}
+
+
+
+
+
+
+ ))}
+
+ >
+ );
+
+ const renderDetailsNotification = () => {
+ if (!selectedNotification) return null;
+
+ const { IconComponent, color } = getIconAndColor(selectedNotification.type);
+
+ return (
+
+
+ {/* Kolom Kiri: Data Kompresor */}
+
+
+
+
+
+
+
+
+ {selectedNotification.title}
+ {selectedNotification.issue}
+
+
+
+
Plant Subsection
+
{selectedNotification.subsection}
+
Time
+
{selectedNotification.timestamp.split(' ')[1]} WIB
+
+
+
+
+
+ Value
+ {selectedNotification.value.includes('°') ? selectedNotification.value : `${selectedNotification.value}° C`}
+
+
+ Treshold
+ {selectedNotification.threshold.includes('°') ? selectedNotification.threshold : `${selectedNotification.threshold}° C`}
+
+
+
+
+
+
+
+ {/* Kolom Kanan: Informasi Teknis */}
+
+
+
+
+
PLC
+
{selectedNotification.plc}
+
+
+
Status
+
{selectedNotification.status}
+
+
+
Tag
+
{selectedNotification.tag}
+
+
+
+
+
+
+
+
+
+
+
+ Handling Guideline
+
+
+
+
+
+
+
+ Spare Part
+
+
+
+
+ setModalContent('log')}>
+
+
+ Log Activity
+
+
+
+
+
+
+
+
+ PDF}
+ >
+
+
+
+ Error 303.pdf
+
+
+ lihat disini
+
+
+
+
+ PDF}
+ >
+
+
+
+ Error 303.pdf
+
+
+ lihat disini
+
+
+
+
+ PDF}
+ >
+
+
+
+ Error 303.pdf
+
+
+ lihat disini
+
+
+
+
+ Text}
+ >
+
+ Error 303: Sensor suhu tidak merespon. Lakukan reset manual pada panel kontrol.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Available
+
+
+
+ Air Filter
+
+ Filters incoming air to remove dust and impurities before compression.
+
+
+ Part No: AF-2024-X2 | Condition: New | Compatible with Model XR-60
+
+
+
+
+
+
+
+
+
+
+
+ Not Available
+
+
+
+ Compressor Oil
+
+ Special synthetic oil to lubricate and cool down the compressor unit.
+
+
+ Part No: OL-SYN-550 | Condition: New | Compatible with Model XR-60
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isAddingLog && (
+ <>
+ Add New Log / Update Progress
+
+ >
+ )}
+ } onClick={() => setIsAddingLog(!isAddingLog)}>
+ {isAddingLog ? "Submit Log" : "Add Log"}
+
+
+
+ {logHistoryData.slice(0, 2).map(log => ( // Menampilkan 2 log terbaru sebagai pratinjau
+
+
+ {log.addedBy.name}: {log.description}
+
+ {log.timestamp}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+ };
+
return (
-
- Notification
-
+ Notification
Riwayat notifikasi yang dikirim ke engineer
-
-
+
+
setSearchTerm(value)}
+ onSearch={setSearchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
- style={{ marginBottom: '24px', width: 300 }}
+ style={{ width: 300 }}
/>
-
- {/* Tabs */}
-
-
-
-
-
-
-
-
- {/* Notification List */}
-
- {filteredNotifications.length === 0 ? (
-
- Tidak ada notifikasi
+
+
+
+
+
- ) : (
- filteredNotifications.map((notification) => {
- const { IconComponent, color, bgColor } = getIconAndColor(
- notification.type
- );
+
- return (
-
handleMarkAsRead(notification.id)}
- >
-
- {/* Icon */}
-
-
-
-
- {/* Content */}
-
-
- {/* Left: Title and Issue */}
-
-
-
-
{notification.title}
-
- {notification.issue}
-
-
- {!notification.isRead && (
-
- )}
-
-
-
- {/* Middle: Description and Details */}
-
-
-
-
- {notification.description}
-
- {notification.details}
-
-
-
-
-
- {notification.timestamp}
-
-
-
- {notification.location}
-
-
-
- {notification.link}
- } style={{ paddingLeft: '8px' }}>
- Resend
-
-
-
-
-
- {/* Right: Action Icons */}
-
-
- } title="User History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
- } title="Details" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
- } title="Log History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
-
-
-
-
-
-
- );
- })
- )}
-
+ {renderDeviceNotifications()}
+
+
+ {modalContent === 'details' ? (
+
+
+ Error Notification Detail
+
+
+ ) : (
+
+ {modalContent === 'user' && 'User History Notification'}
+ {modalContent === 'log' && 'Log History Notification'}
+
+ )}
+
+ }
+ open={modalContent !== null}
+ onCancel={() => {
+ setModalContent(null);
+ // Reset state isAddingLog saat modal ditutup
+ if (modalContent === 'details') {
+ setIsAddingLog(false);
+ }
+ setSelectedNotification(null);
+ }}
+ closable={false} // Menghilangkan tombol 'x' di pojok kanan atas
+ width={modalContent === 'details' ? 900 : 700}
+ footer={
+
+
+
+ }
+ >
+ {modalContent === 'user' && renderUserHistory()}
+ {modalContent === 'log' && renderLogHistory()}
+ {modalContent === 'details' && renderDetailsNotification()}
+
);
});
-export default ListNotification;
+export default ListNotification;
\ No newline at end of file
diff --git a/src/pages/notification/component/LogHistoryModal.jsx b/src/pages/notification/component/LogHistoryModal.jsx
new file mode 100644
index 0000000..debebc8
--- /dev/null
+++ b/src/pages/notification/component/LogHistoryModal.jsx
@@ -0,0 +1,102 @@
+import React from 'react';
+import { Modal, Table, Tag, Typography } from 'antd';
+import { ClockCircleOutlined, UserOutlined } from '@ant-design/icons';
+import dayjs from 'dayjs';
+
+const { Text } = Typography;
+
+// Dummy data untuk log history
+const getDummyLogHistory = (notification) => {
+ if (!notification) return [];
+ return [
+ {
+ key: '1',
+ timestamp: dayjs().subtract(2, 'hour').format('DD-MM-YYYY HH:mm:ss'),
+ activity: 'Notification Created',
+ details: `System generated a ${notification.type} notification for: ${notification.issue}`,
+ },
+ {
+ key: '2',
+ timestamp: dayjs().subtract(1, 'hour').format('DD-MM-YYYY HH:mm:ss'),
+ activity: 'Notification Sent',
+ details: 'Sent to 2 engineers',
+ },
+ {
+ key: '3',
+ timestamp: dayjs().subtract(30, 'minute').format('DD-MM-YYYY HH:mm:ss'),
+ activity: 'Notification Read',
+ details: 'Read by Engineer A',
+ },
+ {
+ key: '4',
+ timestamp: dayjs().subtract(5, 'minute').format('DD-MM-YYYY HH:mm:ss'),
+ activity: 'Resend Triggered',
+ details: 'Notification resent by Admin',
+ },
+ ];
+};
+
+const columns = [
+ {
+ title: 'Timestamp',
+ dataIndex: 'timestamp',
+ key: 'timestamp',
+ render: (text) => (
+
+
+ {text}
+
+ ),
+ },
+ {
+ title: 'Activity',
+ dataIndex: 'activity',
+ key: 'activity',
+ render: (text) => {
+ let color = 'blue';
+ if (text.includes('Created')) {
+ color = 'geekblue';
+ } else if (text.includes('Sent')) {
+ color = 'purple';
+ } else if (text.includes('Read')) {
+ color = 'green';
+ } else if (text.includes('Triggered')) {
+ color = 'orange';
+ }
+ return {text.toUpperCase()};
+ },
+ },
+ {
+ title: 'Details',
+ dataIndex: 'details',
+ key: 'details',
+ },
+];
+
+const LogHistoryModal = ({ visible, onCancel, notificationData }) => {
+ const logHistoryData = getDummyLogHistory(notificationData);
+
+ return (
+
+ Log History: {notificationData?.title}
+
+ }
+ open={visible}
+ onCancel={onCancel}
+ footer={null}
+ width={800}
+ destroyOnClose
+ >
+
+
+ );
+};
+
+export default LogHistoryModal;
diff --git a/src/pages/notification/component/UserHistoryModal.jsx b/src/pages/notification/component/UserHistoryModal.jsx
new file mode 100644
index 0000000..9cf832e
--- /dev/null
+++ b/src/pages/notification/component/UserHistoryModal.jsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import { Modal, Typography, Card, Row, Col, Avatar, Tag, Button, Space } from 'antd';
+import { UserOutlined, PhoneOutlined, CheckCircleOutlined, SyncOutlined, SendOutlined } from '@ant-design/icons';
+
+const { Text } = Typography;
+
+// Dummy data baru untuk user history
+const getDummyUsers = (notification) => {
+ if (!notification) return [];
+ return [
+ {
+ id: '1',
+ name: 'Budi Santoso',
+ phone: '081234567890',
+ status: 'delivered',
+ },
+ {
+ id: '2',
+ name: 'Citra Lestari',
+ phone: '082345678901',
+ status: 'sent',
+ },
+ {
+ id: '3',
+ name: 'Agus Wijaya',
+ phone: '083456789012',
+ status: 'failed',
+ },
+ {
+ id: '4',
+ name: 'Dewi Anggraini',
+ phone: '084567890123',
+ status: 'delivered',
+ },
+ ];
+};
+
+const UserHistoryModal = ({ visible, onCancel, notificationData }) => {
+ const userData = getDummyUsers(notificationData);
+
+ const getStatusTag = (status) => {
+ switch (status) {
+ case 'delivered':
+ return } color="success">Delivered;
+ case 'sent':
+ return } color="processing">Sent;
+ case 'failed':
+ return Failed;
+ default:
+ return {status};
+ }
+ };
+
+ return (
+
+ User History Notification
+
+ }
+ open={visible}
+ onCancel={onCancel}
+ footer={[
+ ,
+ ]}
+ width={600}
+ destroyOnClose
+ >
+
+
+ {userData.map((user) => (
+
+
+
+
+ } />
+
+
+
+
+
+ {getStatusTag(user.status)}
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ console.log(`Resend to ${user.name}`);
+ }}
+ >
+ Resend
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default UserHistoryModal;
diff --git a/src/pages/notification/detail/UserHistory.jsx b/src/pages/notification/detail/UserHistory.jsx
new file mode 100644
index 0000000..9ec87bb
--- /dev/null
+++ b/src/pages/notification/detail/UserHistory.jsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { Button, Row, Col, Card, Badge, Typography, Space, Divider } from 'antd';
+import { SendOutlined, MobileOutlined, CheckCircleFilled, ArrowLeftOutlined } from '@ant-design/icons';
+
+const { Text } = Typography;
+
+// Dummy data for user history
+const userHistoryData = [
+ { id: 1, name: 'John Doe', phone: '081234567890', status: 'Delivered', timestamp: '04-11-2025 11:40 WIB' },
+ { id: 2, name: 'Jane Smith', phone: '087654321098', status: 'Delivered', timestamp: '04-11-2025 11:41 WIB' },
+ { id: 3, name: 'Peter Jones', phone: '082345678901', status: 'Delivered', timestamp: '04-11-2025 11:42 WIB' },
+];
+
+const UserHistory = ({ notification, onBack }) => {
+ return (
+
+
+
+
+ } onClick={onBack} />
+ User History Notification
+
+
+ {notification.title} - {notification.issue}
+
+
+
+
+
+ {userHistoryData.map(user => (
+
+
+
+
+ {user.name}
+ |
+ {user.phone}
+ |
+
+
+
+
+
+ Success Delivered at {user.timestamp}
+
+
+
+ }>Resend
+
+
+
+ ))}
+
+
+ );
+};
+
+export default UserHistory;