From f4caac55e6cf2d18a0062f8e2963e711fb46d94e Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Tue, 18 Nov 2025 19:55:11 +0700 Subject: [PATCH] feat: add API function to retrieve all notifications --- src/api/notification.jsx | 9 + src/pages/notification/IndexNotification.jsx | 11 +- .../component/ListNotification.jsx | 980 ++++++++++++++---- 3 files changed, 768 insertions(+), 232 deletions(-) create mode 100644 src/api/notification.jsx diff --git a/src/api/notification.jsx b/src/api/notification.jsx new file mode 100644 index 0000000..9aab0c5 --- /dev/null +++ b/src/api/notification.jsx @@ -0,0 +1,9 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +export const getAllNotification = async () => { + const response = await SendRequest({ + method: 'get', + prefix: 'notification', + }); + return response.data; +}; diff --git a/src/pages/notification/IndexNotification.jsx b/src/pages/notification/IndexNotification.jsx index 1c5ab9f..664cd45 100644 --- a/src/pages/notification/IndexNotification.jsx +++ b/src/pages/notification/IndexNotification.jsx @@ -1,7 +1,7 @@ import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useBreadcrumb } from '../../layout/LayoutBreadcrumb'; -import { Form, Typography } from 'antd'; +import { Typography } from 'antd'; import ListNotification from './component/ListNotification'; import DetailNotification from './component/DetailNotification'; @@ -10,7 +10,6 @@ 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); @@ -36,19 +35,14 @@ const IndexNotification = memo(function IndexNotification() { useEffect(() => { if (actionMode === 'preview') { setIsModalVisible(true); - if (selectedData) { - form.setFieldsValue(selectedData); - } } else { setIsModalVisible(false); - form.resetFields(); } - }, [actionMode, selectedData, form]); + }, [actionMode]); const handleCancel = () => { setActionMode('list'); setSelectedData(null); - form.resetFields(); }; return ( @@ -62,7 +56,6 @@ const IndexNotification = memo(function IndexNotification() { diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx index 433bdc6..2fb225c 100644 --- a/src/pages/notification/component/ListNotification.jsx +++ b/src/pages/notification/component/ListNotification.jsx @@ -1,5 +1,18 @@ import React, { memo, useState, useEffect } from 'react'; -import { Button, Row, Col, Card, Badge, Input, Typography, Space, Divider, Modal, Tag, message } from 'antd'; +import { + Button, + Row, + Col, + Card, + Badge, + Input, + Typography, + Space, + Divider, + Modal, + Tag, + message, +} from 'antd'; import { CloseCircleFilled, WarningFilled, @@ -22,72 +35,63 @@ import { ExclamationCircleOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; +import { getAllNotification } from '../../../api/notification'; const { Text, Paragraph, Link } = Typography; -// Dummy data untuk notifikasi device -const initialDeviceNotifications = [ - { - id: 1, - type: 'critical', - title: 'Compressor Unit A', - issue: 'Overheat detected (85°C)', - description: '⚠️ Compressor Unit A - Overheat Detected (85°C) 🚨', - timestamp: '04-11-2025 11.39 WIB', - 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, - type: 'warning', - title: 'Compressor Unit C', - issue: 'Pressure slightly high (7.2 bar)', - description: '🔧 Compressor Unit C - Pressure High (7.2 bar)', - timestamp: '04-11-2025 11.30 WIB', - 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, - type: 'resolved', - title: 'Compressor Unit B', - issue: 'Vibration issue resolved', - description: '✅ Compressor Unit B - Vibration Resolved', - timestamp: '04-11-2025 10.05 WIB', - 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', - }, -]; +// Transform API response to component format +const transformNotificationData = (apiData) => { + return apiData.map((item, index) => ({ + id: `notification-${item.notification_error_id}-${index}`, // Unique key prefix with array index + type: item.is_read ? 'resolved' : item.is_delivered ? 'warning' : 'critical', + title: item.device_name || 'Unknown Device', + issue: item.error_code_name || 'Unknown Error', + description: `${item.error_code} - ${item.error_code_name}`, + timestamp: + new Date(item.created_at).toLocaleString('id-ID', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }) + ' WIB', + location: item.device_location || 'Location not specified', + details: item.message_error_issue || 'No details available', + link: '#', // Will be updated when API provides link + subsection: item.solution_name || 'N/A', + isRead: item.is_read, + status: item.is_read ? 'Resolved' : item.is_delivered ? 'Delivered' : 'Pending', + tag: item.error_code, + errorCode: item.error_code, + solutionName: item.solution_name, + typeSolution: item.type_solution, + pathSolution: item.path_solution, + })); +}; // 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' }, + { + 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 @@ -97,16 +101,16 @@ const logHistoryData = [ timestamp: '04-11-2025 11:55 WIB', addedBy: { name: 'Budi Santoso', - phone: '081122334455' + phone: '081122334455', }, - description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.' + 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' + phone: '081234567890', }, description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.', }, @@ -115,15 +119,14 @@ const logHistoryData = [ timestamp: '04-11-2025 11:40 WIB', addedBy: { name: 'Jane Smith', - phone: '087654321098' + phone: '087654321098', }, description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.', - } + }, ]; - const ListNotification = memo(function ListNotification(props) { - const [notifications, setNotifications] = useState(initialDeviceNotifications); + const [notifications, setNotifications] = useState([]); const [activeTab, setActiveTab] = useState('all'); const [searchTerm, setSearchTerm] = useState(''); const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null @@ -131,19 +134,41 @@ const ListNotification = memo(function ListNotification(props) { const [selectedNotification, setSelectedNotification] = useState(null); const navigate = useNavigate(); + // Fetch notifications from API + const fetchNotifications = async () => { + try { + const response = await getAllNotification(); + if (response && response.data) { + const transformedData = transformNotificationData(response.data); + setNotifications(transformedData); + } + } catch (error) { + console.error('Error fetching notifications:', error); + setNotifications([]); + } + }; + useEffect(() => { const token = localStorage.getItem('token'); if (!token) { navigate('/signin'); + return; } - }, [navigate]); + + // Fetch notifications on component mount + fetchNotifications(); + }, []); 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' }; } }; @@ -156,25 +181,37 @@ const ListNotification = memo(function ListNotification(props) { 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.`); + + 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)) + prev.map((n) => + n.id === id ? { ...n, isRead: true, type: 'resolved', status: 'Resolved' } : n + ) ); }; const filteredNotifications = notifications - .filter(n => (activeTab === 'all' || (activeTab === 'unread' && !n.isRead) || (activeTab === 'read' && n.isRead))) - .filter(n => { + .filter((n) => { + const matchesTab = + activeTab === 'all' || + (activeTab === 'unread' && !n.isRead) || + (activeTab === 'read' && n.isRead); + return matchesTab; + }) + .filter((n) => { if (!searchTerm) return true; - const searchableText = `${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase(); + const searchableText = + `${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase(); return searchableText.includes(searchTerm.toLowerCase()); }); @@ -196,7 +233,9 @@ const ListNotification = memo(function ListNotification(props) { const renderDeviceNotifications = () => ( {filteredNotifications.length === 0 ? ( -
Tidak ada notifikasi
+
+ Tidak ada notifikasi +
) : ( filteredNotifications.map((notification) => { const { IconComponent, color, bgColor } = getIconAndColor(notification.type); @@ -211,47 +250,169 @@ const ListNotification = memo(function ListNotification(props) { onClick={() => handleMarkAsRead(notification.id)} >
-
+
-
+
{notification.title} -
{notification.issue}
+
+ + {notification.issue} + +
- {!notification.isRead && } + {!notification.isRead && ( + + )}
-
- - - {notification.description}
+
+ + {notification.details}
- - {notification.timestamp} - {notification.location} + - {notification.link} + + + {notification.timestamp} + + + + + + {notification.location} + + + + + + {notification.link} + - - - + @@ -298,19 +465,45 @@ const ListNotification = memo(function ListNotification(props) { <>
{/* Garis vertikal yang menyambung */} -
+
{logHistoryData.map((log, index) => ( - + {/* Kolom Kiri: Branch/Timeline */} - -
+ +
{/* Kolom Kanan: Card */} @@ -321,18 +514,35 @@ const ListNotification = memo(function ListNotification(props) { - Added at {log.timestamp} + + Added at {log.timestamp} +
Added by: {log.addedBy.name} - + {log.addedBy.phone}
- + {log.description} @@ -355,48 +565,79 @@ const ListNotification = memo(function ListNotification(props) { {/* Kolom Kiri: Data Kompresor */} - + -
+
+ +
{selectedNotification.title} -
{selectedNotification.issue}
+
+ + {selectedNotification.issue} + +
Plant Subsection
{selectedNotification.subsection}
- Time + + Time +
{selectedNotification.timestamp.split(' ')[1]} WIB
-
+
- Value -
{selectedNotification.value.includes('°') ? selectedNotification.value : `${selectedNotification.value}° C`}
+ + Value + +
+ N/A +
- Treshold -
{selectedNotification.threshold.includes('°') ? selectedNotification.threshold : `${selectedNotification.threshold}° C`}
+ + Treshold + +
+ N/A +
@@ -414,11 +655,23 @@ const ListNotification = memo(function ListNotification(props) {
Status -
{selectedNotification.status}
+
+ {selectedNotification.status} +
Tag -
{selectedNotification.tag}
+
+ {selectedNotification.tag} +
@@ -427,26 +680,59 @@ const ListNotification = memo(function ListNotification(props) {
- + - Handling Guideline + + Handling Guideline + - + - Spare Part + + Spare Part + - setModalContent('log')}> + setModalContent('log')} + > - - Log Activity + + + Log Activity + @@ -456,56 +742,125 @@ const ListNotification = memo(function ListNotification(props) { PDF} + size="small" + bodyStyle={{ padding: '8px 12px' }} + hoverable + extra={ + + PDF + + } > -
+
- - Error 303.pdf + + {' '} + Error 303.pdf - + lihat disini
PDF} + size="small" + bodyStyle={{ padding: '8px 12px' }} + hoverable + extra={ + + PDF + + } > -
+
- - Error 303.pdf + + {' '} + Error 303.pdf - + lihat disini
PDF} + size="small" + bodyStyle={{ padding: '8px 12px' }} + hoverable + extra={ + + PDF + + } > -
+
- - Error 303.pdf + + {' '} + Error 303.pdf - + lihat disini
Text} + size="small" + bodyStyle={{ padding: '8px 12px' }} + extra={ + + Text + + } > - Error 303: Sensor suhu tidak merespon. Lakukan reset manual pada panel kontrol. + Error 303: Sensor suhu tidak + merespon. Lakukan reset manual pada panel kontrol. @@ -517,19 +872,64 @@ const ListNotification = memo(function ListNotification(props) { -
- +
+
- Available + + Available + - + Air Filter - - Filters incoming air to remove dust and impurities before compression. + + Filters incoming air to remove dust and + impurities before compression. -
- Part No: AF-2024-X2 | Condition: New | Compatible with Model XR-60 +
+ Part No: AF-2024-X2 | Condition: New | + Compatible with Model XR-60
@@ -538,19 +938,64 @@ const ListNotification = memo(function ListNotification(props) { -
- +
+
- Not Available + + Not Available + - + Compressor Oil - - Special synthetic oil to lubricate and cool down the compressor unit. + + Special synthetic oil to lubricate and cool + down the compressor unit. -
- Part No: OL-SYN-550 | Condition: New | Compatible with Model XR-60 +
+ Part No: OL-SYN-550 | Condition: New | + Compatible with Model XR-60
@@ -562,29 +1007,68 @@ const ListNotification = memo(function ListNotification(props) { - - + + {isAddingLog && ( <> - Add New Log / Update Progress - + + Add New Log / Update Progress + + )} - - {logHistoryData.slice(0, 2).map(log => ( // Menampilkan 2 log terbaru sebagai pratinjau - - - {log.addedBy.name}: {log.description} - - {log.timestamp} - - ))} + {logHistoryData.slice(0, 2).map( + ( + log // Menampilkan 2 log terbaru sebagai pratinjau + ) => ( + + + {log.addedBy.name}:{' '} + {log.description} + + + {log.timestamp} + + + ) + )}
-
@@ -602,31 +1086,67 @@ const ListNotification = memo(function ListNotification(props) { -

Notification

+

+ Notification +

Riwayat notifikasi yang dikirim ke engineer

- + setSearchTerm(e.target.value)} - style={{ width: 300 }} + style={{ width: 300 }} /> -
-
- - - -
+
+
+ + +
+
{renderDeviceNotifications()} @@ -637,7 +1157,15 @@ const ListNotification = memo(function ListNotification(props) { title={
{modalContent === 'details' ? ( -
+
Error Notification Detail @@ -663,14 +1191,20 @@ const ListNotification = memo(function ListNotification(props) { width={modalContent === 'details' ? 900 : 700} footer={
- +
} > @@ -682,4 +1216,4 @@ const ListNotification = memo(function ListNotification(props) { ); }); -export default ListNotification; \ No newline at end of file +export default ListNotification;