Files
cod-fe/src/pages/notification/component/ListNotification.jsx

324 lines
16 KiB
JavaScript

import React, { memo, useState, useEffect } from 'react';
import { Button, Row, Col, Card, Badge, Input, Typography, Space } from 'antd';
import {
CloseCircleFilled,
WarningFilled,
CheckCircleFilled,
InfoCircleFilled,
ClockCircleOutlined,
EnvironmentOutlined,
LinkOutlined,
SendOutlined,
MailOutlined,
UserOutlined,
FileTextOutlined,
HistoryOutlined,
EyeOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
const { Text, Paragraph, Link } = Typography;
// Dummy data untuk notifikasi
const initialNotifications = [
{
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',
isRead: false,
},
{
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',
isRead: false,
},
{
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',
isRead: true,
},
];
const ListNotification = memo(function ListNotification(props) {
const [notifications, setNotifications] = useState(initialNotifications);
const [activeTab, setActiveTab] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
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 handleMarkAsRead = (id) => {
setNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
);
};
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) => {
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 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 (
<React.Fragment>
<Card>
<Row>
<Col xs={24}>
<h2
style={{
fontSize: '20px',
fontWeight: 600,
margin: '0 0 4px 0',
color: '#262626',
}}
>
Notification
</h2>
<p style={{ margin: '0 0 16px 0', color: '#8c8c8c', fontSize: '14px' }}>
Riwayat notifikasi yang dikirim ke engineer
</p>
<Row>
<Col span={6}>
<Input.Search
placeholder="Search notifications..."
onSearch={(value) => setSearchTerm(value)}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ marginBottom: '24px', width: 300 }}
/>
</Col>
</Row>
{/* Tabs */}
<div style={{ borderBottom: '1px solid #f0f0f0', marginBottom: '24px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => setActiveTab('all')}
style={tabButtonStyle(activeTab === 'all')}
>
All
</button>
<button
onClick={() => setActiveTab('unread')}
style={{
...tabButtonStyle(activeTab === 'unread'),
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
Not read yet
{getUnreadCount() > 0 && (
<Badge
count={getUnreadCount()}
style={{
backgroundColor: '#ff4d4f',
}}
/>
)}
</button>
<button
onClick={() => setActiveTab('read')}
style={tabButtonStyle(activeTab === 'read')}
>
Already read
</button>
</div>
</div>
{/* Notification List */}
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
{filteredNotifications.length === 0 ? (
<div
style={{
textAlign: 'center',
padding: '40px 0',
color: '#8c8c8c',
}}
>
Tidak ada notifikasi
</div>
) : (
filteredNotifications.map((notification) => {
const { IconComponent, color, bgColor } = getIconAndColor(
notification.type
);
return (
<Card
key={notification.id}
style={{
backgroundColor: notification.isRead ? '#ffffff' : '#f6f9ff',
borderColor: notification.isRead ? '#f0f0f0' : '#d6e4ff',
}}
onClick={() => handleMarkAsRead(notification.id)}
>
<div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>
{/* Icon */}
<div
style={{
width: '40px',
height: '40px',
borderRadius: '50%',
backgroundColor: bgColor,
color: color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '22px',
flexShrink: 0,
}}
>
<IconComponent style={{ fontSize: '22px' }} />
</div>
{/* Content */}
<div style={{ flex: 1 }}>
<Row align="top">
{/* Left: Title and Issue */}
<Col flex="220px">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<Text strong>{notification.title}</Text>
<div style={{ marginTop: '4px' }}>
<Text style={{ color }}>{notification.issue}</Text>
</div>
</div>
{!notification.isRead && (
<Badge color="red" status="processing" style={{ marginLeft: '8px', marginTop: '4px' }} />
)}
</div>
</Col>
{/* Middle: Description and Details */}
<Col flex="auto">
<div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>
<MailOutlined style={{ marginTop: '4px', color: '#1890ff' }} />
<Paragraph style={{ color: '#595959', margin: 0, flex: 1 }}>
<Text strong>{notification.description}</Text>
<br />
{notification.details}
</Paragraph>
</div>
<Space direction="vertical" size={4} style={{ fontSize: '13px', color: '#8c8c8c' }}>
<Space>
<ClockCircleOutlined />
<Text type="secondary">{notification.timestamp}</Text>
</Space>
<Space>
<EnvironmentOutlined />
<Text type="secondary">{notification.location}</Text>
</Space>
<Space>
<LinkOutlined />
<Link href={notification.link} target="_blank">{notification.link}</Link>
<Button type="link" icon={<SendOutlined />} style={{ paddingLeft: '8px' }}>
Resend
</Button>
</Space>
</Space>
</Col>
{/* Right: Action Icons */}
<Col flex="120px" style={{ textAlign: 'center' }} align="bottom">
<Space style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
<Button type="text" icon={<UserOutlined style={{ color: '#1890ff' }} />} title="User History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
<Button type="text" icon={<EyeOutlined style={{ color: '#1890ff' }} />} title="Details" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
<Button type="text" icon={<HistoryOutlined style={{ color: '#1890ff' }} />} title="Log History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
</Space>
</Col>
</Row>
</div>
</div>
</Card>
);
})
)}
</Space>
</Col>
</Row>
</Card>
</React.Fragment>
);
});
export default ListNotification;