Enhance notification list with search functionality and improved layout

This commit is contained in:
2025-11-13 13:16:31 +07:00
parent 0916ea7103
commit 85db9e0a52

View File

@@ -1,79 +1,60 @@
import React, { memo, useState, useEffect } from 'react'; import React, { memo, useState, useEffect } from 'react';
import { Button, Row, Col, Card, Badge } from 'antd'; import { Button, Row, Col, Card, Badge, Input, Typography, Space } from 'antd';
import { import {
CloseCircleFilled, CloseCircleFilled,
WarningFilled, WarningFilled,
CheckCircleFilled, CheckCircleFilled,
InfoCircleFilled, InfoCircleFilled,
ClockCircleOutlined,
EnvironmentOutlined,
LinkOutlined,
SendOutlined,
MailOutlined,
UserOutlined,
FileTextOutlined,
HistoryOutlined,
EyeOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
const { Text, Paragraph, Link } = Typography;
// Dummy data untuk notifikasi // Dummy data untuk notifikasi
const initialNotifications = [ const initialNotifications = [
{ {
id: 1, id: 1,
type: 'critical', type: 'critical',
title: 'Compressor Unit A - Overheat detected (85°C)', title: 'Compressor Unit A',
plc: 'PLC-001', issue: 'Overheat detected (85°C)',
tag: 'A1-TEMP', description: '⚠️ Compressor Unit A - Overheat Detected (85°C) 🚨',
engineer: 'Siti Nurhaliza', timestamp: '04-11-2025 11.39 WIB',
time: '2 menit lalu', location: 'Lantai 2, Area Produksi A, Zona 3',
status: 'unread', details: 'Terjadi kenaikan suhu melebihi ambang batas pada Compressor Unit A. Pengecekan potensi kerusakan dibutuhkan.',
link: 'https://tinyurl.com/compA85',
isRead: false, isRead: false,
}, },
{ {
id: 2, id: 2,
type: 'warning', type: 'warning',
title: 'Compressor Unit C - Pressure slightly high (7.2 bar)', title: 'Compressor Unit C',
plc: 'PLC-003', issue: 'Pressure slightly high (7.2 bar)',
tag: 'C3-PRESS', description: '🔧 Compressor Unit C - Pressure High (7.2 bar)',
engineer: 'Joko Widodo', timestamp: '04-11-2025 11.30 WIB',
time: '15 menit lalu', location: 'Lantai 1, Area Produksi C, Zona 1',
status: 'unread', details: 'Tekanan mendekati ambang batas atas. Perlu monitoring lebih lanjut oleh engineer.',
link: 'https://tinyurl.com/compC72',
isRead: false, isRead: false,
}, },
{ {
id: 3, id: 3,
type: 'resolved', type: 'resolved',
title: 'Compressor Unit B - Vibration issue resolved', title: 'Compressor Unit B',
plc: 'PLC-002', issue: 'Vibration issue resolved',
tag: 'B2-VIB', description: '✅ Compressor Unit B - Vibration Resolved',
engineer: 'Rudi Santoso', timestamp: '04-11-2025 10.05 WIB',
time: '1 jam lalu', location: 'Lantai 2, Area Produksi B, Zona 2',
status: 'read', details: 'Getaran pada Unit B telah kembali normal setelah perbaikan.',
isRead: true, link: 'https://tinyurl.com/compBresolved',
},
{
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, isRead: true,
}, },
]; ];
@@ -81,6 +62,7 @@ const initialNotifications = [
const ListNotification = memo(function ListNotification(props) { const ListNotification = memo(function ListNotification(props) {
const [notifications, setNotifications] = useState(initialNotifications); const [notifications, setNotifications] = useState(initialNotifications);
const [activeTab, setActiveTab] = useState('all'); const [activeTab, setActiveTab] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
@@ -119,29 +101,29 @@ const ListNotification = memo(function ListNotification(props) {
} }
}; };
const filterNotifications = (status) => { const handleMarkAsRead = (id) => {
if (status === 'all') return notifications; setNotifications((prev) =>
if (status === 'unread') return notifications.filter((n) => !n.isRead); prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
if (status === 'read') return notifications.filter((n) => n.isRead); );
return notifications;
}; };
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 = () => { const getUnreadCount = () => {
return notifications.filter((n) => !n.isRead).length; 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) => ({ const tabButtonStyle = (isActive) => ({
padding: '12px 16px', padding: '12px 16px',
border: 'none', border: 'none',
@@ -170,10 +152,21 @@ const ListNotification = memo(function ListNotification(props) {
> >
Notification Notification
</h2> </h2>
<p style={{ margin: '0 0 24px 0', color: '#8c8c8c', fontSize: '14px' }}> <p style={{ margin: '0 0 16px 0', color: '#8c8c8c', fontSize: '14px' }}>
Riwayat notifikasi yang dikirim ke engineer Riwayat notifikasi yang dikirim ke engineer
</p> </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 */} {/* Tabs */}
<div style={{ borderBottom: '1px solid #f0f0f0', marginBottom: '24px' }}> <div style={{ borderBottom: '1px solid #f0f0f0', marginBottom: '24px' }}>
<div style={{ display: 'flex', gap: '8px' }}> <div style={{ display: 'flex', gap: '8px' }}>
@@ -212,7 +205,7 @@ const ListNotification = memo(function ListNotification(props) {
</div> </div>
{/* Notification List */} {/* Notification List */}
<div> <Space direction="vertical" size="middle" style={{ display: 'flex' }}>
{filteredNotifications.length === 0 ? ( {filteredNotifications.length === 0 ? (
<div <div
style={{ style={{
@@ -230,165 +223,96 @@ const ListNotification = memo(function ListNotification(props) {
); );
return ( return (
<div <Card
key={notification.id} key={notification.id}
style={{ style={{
marginBottom: '12px', backgroundColor: notification.isRead ? '#ffffff' : '#f6f9ff',
backgroundColor: '#ffffff', borderColor: notification.isRead ? '#f0f0f0' : '#d6e4ff',
border: '1px solid #f0f0f0',
borderRadius: '8px',
padding: '16px',
position: 'relative',
transition: 'all 0.3s',
cursor: 'pointer',
}}
onMouseEnter={(e) => {
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';
}} }}
onClick={() => handleMarkAsRead(notification.id)}
> >
{/* Dot for unread */} <div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>
{!notification.isRead && (
<div
style={{
position: 'absolute',
top: '16px',
right: '16px',
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: '#ff4d4f',
}}
/>
)}
<div
style={{
display: 'flex',
gap: '16px',
alignItems: 'flex-start',
}}
>
{/* Icon */} {/* Icon */}
<div <div
style={{ style={{
width: '48px', width: '40px',
height: '48px', height: '40px',
borderRadius: '50%', borderRadius: '50%',
backgroundColor: bgColor, backgroundColor: bgColor,
color: color, color: color,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
fontSize: '24px', fontSize: '22px',
flexShrink: 0, flexShrink: 0,
}} }}
> >
<IconComponent style={{ fontSize: '24px' }} /> <IconComponent style={{ fontSize: '22px' }} />
</div> </div>
{/* Content */} {/* Content */}
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<div <Row align="top">
style={{ {/* Left: Title and Issue */}
fontSize: '15px', <Col flex="220px">
fontWeight: 600, <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
marginBottom: '4px', <div>
color: '#262626', <Text strong>{notification.title}</Text>
}} <div style={{ marginTop: '4px' }}>
> <Text style={{ color }}>{notification.issue}</Text>
{notification.title} </div>
</div> </div>
{!notification.isRead && (
<Badge color="red" status="processing" style={{ marginLeft: '8px', marginTop: '4px' }} />
)}
</div>
</Col>
<div {/* Middle: Description and Details */}
style={{ <Col flex="auto">
display: 'flex', <div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>
gap: '8px', <MailOutlined style={{ marginTop: '4px', color: '#1890ff' }} />
marginBottom: '4px', <Paragraph style={{ color: '#595959', margin: 0, flex: 1 }}>
flexWrap: 'wrap', <Text strong>{notification.description}</Text>
}} <br />
> {notification.details}
<span </Paragraph>
style={{ </div>
fontSize: '13px', <Space direction="vertical" size={4} style={{ fontSize: '13px', color: '#8c8c8c' }}>
color: '#8c8c8c', <Space>
}} <ClockCircleOutlined />
> <Text type="secondary">{notification.timestamp}</Text>
{notification.plc} </Space>
</span> <Space>
<span <EnvironmentOutlined />
style={{ <Text type="secondary">{notification.location}</Text>
fontSize: '13px', </Space>
color: '#d9d9d9', <Space>
}} <LinkOutlined />
> <Link href={notification.link} target="_blank">{notification.link}</Link>
<Button type="link" icon={<SendOutlined />} style={{ paddingLeft: '8px' }}>
</span> Resend
<span </Button>
style={{ </Space>
fontSize: '13px', </Space>
color: '#8c8c8c', </Col>
}}
>
Tag: {notification.tag}
</span>
</div>
<div {/* Right: Action Icons */}
style={{ <Col flex="120px" style={{ textAlign: 'center' }} align="bottom">
display: 'flex', <Space style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
alignItems: 'center', <Button type="text" icon={<UserOutlined style={{ color: '#1890ff' }} />} title="User History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
gap: '8px', <Button type="text" icon={<EyeOutlined style={{ color: '#1890ff' }} />} title="Details" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
flexWrap: 'wrap', <Button type="text" icon={<HistoryOutlined style={{ color: '#1890ff' }} />} title="Log History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} />
}} </Space>
> </Col>
<span </Row>
style={{
fontSize: '13px',
color: '#8c8c8c',
}}
>
Engineer:{' '}
<span style={{ color: '#595959' }}>
{notification.engineer}
</span>
</span>
<span
style={{
fontSize: '13px',
color: '#8c8c8c',
}}
>
{notification.time}
</span>
</div>
</div> </div>
{/* Button */}
<Button
type="link"
onClick={() => handleViewDetail(notification)}
style={{
color: '#FF6B35',
fontSize: '14px',
fontWeight: 500,
}}
>
View Detail
</Button>
</div> </div>
</div> </Card>
); );
}) })
)} )}
</div> </Space>
</Col> </Col>
</Row> </Row>
</Card> </Card>
@@ -397,5 +321,3 @@ const ListNotification = memo(function ListNotification(props) {
}); });
export default ListNotification; export default ListNotification;