import React, { memo, useState, useEffect } from 'react'; import { Button, Row, Col, Card, Badge, Input, Typography, Space, Divider, Modal, Tag, message, Spin, Pagination, } from 'antd'; import { CloseCircleFilled, WarningFilled, CheckCircleFilled, InfoCircleFilled, ClockCircleOutlined, EnvironmentOutlined, LinkOutlined, SendOutlined, MailOutlined, UserOutlined, HistoryOutlined, EyeOutlined, MobileOutlined, CloseOutlined, BookOutlined, ToolOutlined, FilePdfOutlined, PlusOutlined, ExclamationCircleOutlined, SearchOutlined, } from '@ant-design/icons'; import { useNavigate, Link as RouterLink } from 'react-router-dom'; import { getAllNotification, getNotificationLogByNotificationId } from '../../../api/notification'; const { Text, Paragraph, Link: AntdLink } = Typography; // 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.error_code_name || 'Unknown Error', issue: item.error_code || item.error_code_name || 'Unknown Error', description: `${item.error_code} - ${item.error_code_name || ''}`, timestamp: item.created_at ? new Date(item.created_at).toLocaleString('id-ID', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }) + ' WIB' : 'N/A', location: item.plant_sub_section_name || item.device_location || 'Location not specified', details: item.message_error_issue || 'No details available', link: `/verification-sparepart/${item.notification_error_id}`, // Dummy URL untuk verifikasi spare part subsection: item.plant_sub_section_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.error_code?.solution?.[0]?.solution_name || 'N/A', typeSolution: item.error_code?.solution?.[0]?.type_solution || 'N/A', pathSolution: item.error_code?.solution?.[0]?.path_document || item.error_code?.solution?.[0]?.path_solution || 'N/A', error_code: item.error_code, })); }; // 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', }, ]; const ListNotification = memo(function ListNotification(props) { const [notifications, setNotifications] = useState([]); const [activeTab, setActiveTab] = useState('all'); const [searchTerm, setSearchTerm] = useState(''); const [searchValue, setSearchValue] = useState(''); const [loading, setLoading] = useState(false); const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null const [isAddingLog, setIsAddingLog] = useState(false); const [selectedNotification, setSelectedNotification] = useState(null); const [logHistoryData, setLogHistoryData] = useState([]); const [logLoading, setLogLoading] = useState(false); const [pagination, setPagination] = useState({ current_page: 1, current_limit: 10, total_limit: 0, total_page: 1, }); const navigate = useNavigate(); // Fetch notifications from API const fetchNotifications = async (page = 1, limit = 10, isRead = null) => { setLoading(true); try { const queryParams = new URLSearchParams({ page: page.toString(), limit: limit.toString(), }); if (isRead !== null) { queryParams.append('is_read', isRead.toString()); } const response = await getAllNotification(queryParams); if (response && response.data) { const transformedData = transformNotificationData(response.data); setNotifications(transformedData); // Update pagination with API response or calculate from data if (response.paging) { setPagination({ current_page: response.paging.current_page || page, current_limit: response.paging.current_limit || limit, total_limit: response.paging.total_limit || transformedData.length, total_page: response.paging.total_page || Math.ceil(transformedData.length / limit), }); } else { // Fallback: calculate pagination from data const totalItems = transformedData.length; setPagination((prev) => ({ ...prev, current_page: page, current_limit: limit, total_limit: totalItems, total_page: Math.ceil(totalItems / limit), })); } } } catch (error) { console.error('Error fetching notifications:', error); setNotifications([]); } finally { setTimeout(() => { setLoading(false); }, 500); } }; const handlePaginationChange = (page, pageSize) => { setPagination((prev) => ({ ...prev, current_page: page, current_limit: pageSize, })); // Fetch notifications with new pagination const isReadFilter = activeTab === 'read' ? 1 : activeTab === 'unread' ? 0 : null; fetchNotifications(page, pageSize, isReadFilter); }; useEffect(() => { const token = localStorage.getItem('token'); if (!token) { navigate('/signin'); return; } // Fetch notifications on component mount and when tab changes const isReadFilter = activeTab === 'read' ? 1 : activeTab === 'unread' ? 0 : null; fetchNotifications(pagination.current_page, pagination.current_limit, isReadFilter); }, [activeTab]); 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 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); 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, type: 'resolved', status: 'Resolved' } : n ) ); }; const handleSearch = () => { setSearchTerm(searchValue); }; const handleSearchClear = () => { setSearchValue(''); setSearchTerm(''); }; const getUnreadCount = () => notifications.filter((n) => !n.isRead).length; // Filter notifications based on search term const getFilteredNotifications = () => { if (!searchTerm) return notifications; // Search by title and error code name return notifications.filter((n) => { const searchableText = `${n.title} ${n.issue}`.toLowerCase(); return searchableText.includes(searchTerm.toLowerCase()); }); }; // Fetch log history from API const fetchLogHistory = async (notificationId) => { try { setLogLoading(true); const response = await getNotificationLogByNotificationId(notificationId); if (response && response.data) { // Transform API data to component format const transformedLogs = response.data.map((log) => ({ id: log.notification_error_log_id, timestamp: log.created_at ? new Date(log.created_at).toLocaleString('id-ID', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }) + ' WIB' : 'N/A', addedBy: { name: log.contact_name || 'Unknown', phone: log.contact_phone || 'N/A', }, description: log.notification_error_log_description || '', })); setLogHistoryData(transformedLogs); } } catch (err) { console.error('Error fetching log history:', err); setLogHistoryData([]); // Set empty array on error } finally { setLogLoading(false); } }; 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', }); const renderDeviceNotifications = () => { const filteredNotifications = getFilteredNotifications(); return ( {filteredNotifications.length === 0 ? (
Tidak ada notifikasi
) : ( filteredNotifications.map((notification) => { const { IconComponent, color, bgColor } = getIconAndColor( notification.type ); return (
{notification.title}
{notification.issue}
{!notification.isRead && ( )}
{notification.details}
{notification.timestamp} {notification.location} {notification.link}
); }) )}
); }; const renderUserHistory = () => ( <> {userHistoryData.map((user) => ( {user.name} | {user.phone} | Success Delivered at {user.timestamp} ))} ); const renderLogHistory = () => ( <> {logLoading ? (
) : logHistoryData.length === 0 ? (
Tidak ada log history
) : (
{/* 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}
Date & Time
{selectedNotification.timestamp}
Value
N/A
Treshold
N/A
{/* Kolom Kanan: Informasi Teknis */}
PLC
{selectedNotification.plc}
Status
{selectedNotification.status}
Tag
{selectedNotification.tag}
Handling Guideline Spare Part { // Set the selected notification for the log history if not already set if (selectedNotification) { const notificationId = selectedNotification.id.split('-')[1]; // Fetch log history for the selected notification fetchLogHistory(notificationId); } 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 )} {logLoading ? (
) : logHistoryData.length === 0 ? (
Tidak ada log history
) : ( logHistoryData.map((log) => ( {log.addedBy.name}:{' '} {log.description} {log.timestamp} )) )}
); }; return (

Notification

Riwayat notifikasi yang dikirim ke engineer

{ const value = e.target.value; setSearchValue(value); if (value === '') { handleSearchClear(); } }} onSearch={handleSearch} allowClear={{ clearIcon: , }} enterButton={ } size="large" />
{renderDeviceNotifications()} {/* PAGINATION */}
Menampilkan {pagination.current_limit} data halaman{' '} {pagination.current_page} dari total {pagination.total_limit}{' '} data
{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;