add notification management with list and detail views, including modal handling and state management
This commit is contained in:
@@ -1,29 +1,71 @@
|
|||||||
import React, { memo, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
|
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
|
||||||
import { Typography } from 'antd';
|
import { Form, Typography } from 'antd';
|
||||||
|
import ListNotification from './component/ListNotification';
|
||||||
|
import DetailNotification from './component/DetailNotification';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const IndexNotification = memo(function IndexNotification() {
|
const IndexNotification = memo(function IndexNotification() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setBreadcrumbItems } = useBreadcrumb();
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const [actionMode, setActionMode] = useState('list');
|
||||||
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
setBreadcrumbItems([
|
setBreadcrumbItems([
|
||||||
{ title: <Text strong style={{ fontSize: '14px' }}>• Notifikasi</Text> }
|
{
|
||||||
|
title: (
|
||||||
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
|
• Notifikasi
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
navigate('/signin');
|
navigate('/signin');
|
||||||
}
|
}
|
||||||
}, []);
|
}, [navigate, setBreadcrumbItems]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (actionMode === 'preview') {
|
||||||
|
setIsModalVisible(true);
|
||||||
|
if (selectedData) {
|
||||||
|
form.setFieldsValue(selectedData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [actionMode, selectedData, form]);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setActionMode('list');
|
||||||
|
setSelectedData(null);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<React.Fragment>
|
||||||
<h1>Notifikasi Page</h1>
|
<ListNotification
|
||||||
</div>
|
actionMode={actionMode}
|
||||||
|
setActionMode={setActionMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
/>
|
||||||
|
<DetailNotification
|
||||||
|
visible={isModalVisible}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
form={form}
|
||||||
|
selectedData={selectedData}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
169
src/pages/notification/component/DetailNotification.jsx
Normal file
169
src/pages/notification/component/DetailNotification.jsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Modal, Row, Col, Tag, Divider } from 'antd';
|
||||||
|
import { CloseCircleFilled, WarningFilled, CheckCircleFilled, InfoCircleFilled } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const DetailNotification = memo(function DetailNotification({ visible, onCancel, form, selectedData }) {
|
||||||
|
const getIconAndColor = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'critical':
|
||||||
|
return {
|
||||||
|
IconComponent: CloseCircleFilled,
|
||||||
|
color: '#ff4d4f',
|
||||||
|
bgColor: '#fff1f0',
|
||||||
|
tagColor: 'error',
|
||||||
|
};
|
||||||
|
case 'warning':
|
||||||
|
return {
|
||||||
|
IconComponent: WarningFilled,
|
||||||
|
color: '#faad14',
|
||||||
|
bgColor: '#fffbe6',
|
||||||
|
tagColor: 'warning',
|
||||||
|
};
|
||||||
|
case 'resolved':
|
||||||
|
return {
|
||||||
|
IconComponent: CheckCircleFilled,
|
||||||
|
color: '#52c41a',
|
||||||
|
bgColor: '#f6ffed',
|
||||||
|
tagColor: 'success',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
IconComponent: InfoCircleFilled,
|
||||||
|
color: '#1890ff',
|
||||||
|
bgColor: '#e6f7ff',
|
||||||
|
tagColor: 'processing',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { IconComponent, color, bgColor, tagColor } = selectedData ? getIconAndColor(selectedData.type) : {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Detail Notifikasi"
|
||||||
|
open={visible}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onOk={onCancel}
|
||||||
|
okText="Tutup"
|
||||||
|
cancelButtonProps={{ style: { display: 'none' } }}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
{selectedData && (
|
||||||
|
<div>
|
||||||
|
{/* Header with Icon and Status */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '16px',
|
||||||
|
marginBottom: '24px',
|
||||||
|
padding: '16px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '64px',
|
||||||
|
height: '64px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
color: color,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '32px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{IconComponent && <IconComponent style={{ fontSize: '32px' }} />}
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Tag color={tagColor} style={{ marginBottom: '8px', fontSize: '12px' }}>
|
||||||
|
{selectedData.type.toUpperCase()}
|
||||||
|
</Tag>
|
||||||
|
<div style={{ fontSize: '16px', fontWeight: 600, color: '#262626' }}>
|
||||||
|
{selectedData.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
|
|
||||||
|
{/* Information Grid */}
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={12}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginBottom: '4px' }}>
|
||||||
|
PLC
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', color: '#262626', fontWeight: 500 }}>
|
||||||
|
{selectedData.plc}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginBottom: '4px' }}>Tag</div>
|
||||||
|
<div style={{ fontSize: '14px', color: '#262626', fontWeight: 500 }}>
|
||||||
|
{selectedData.tag}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={12}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginBottom: '4px' }}>
|
||||||
|
Engineer
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', color: '#262626', fontWeight: 500 }}>
|
||||||
|
{selectedData.engineer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginBottom: '4px' }}>
|
||||||
|
Waktu
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', color: '#262626', fontWeight: 500 }}>
|
||||||
|
{selectedData.time}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginBottom: '8px' }}>Status</div>
|
||||||
|
<Tag color={selectedData.isRead ? 'default' : 'blue'}>
|
||||||
|
{selectedData.isRead ? 'Sudah Dibaca' : 'Belum Dibaca'}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Additional Info */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '16px',
|
||||||
|
padding: '12px',
|
||||||
|
backgroundColor: '#f6f9ff',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #d6e4ff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: '12px', color: '#595959' }}>
|
||||||
|
<strong>Catatan:</strong> Notifikasi ini telah dikirim ke engineer yang bersangkutan
|
||||||
|
untuk ditindaklanjuti sesuai dengan prosedur yang berlaku.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DetailNotification;
|
||||||
399
src/pages/notification/component/ListNotification.jsx
Normal file
399
src/pages/notification/component/ListNotification.jsx
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { Button, Row, Col, Card, Badge } from 'antd';
|
||||||
|
import {
|
||||||
|
CloseCircleFilled,
|
||||||
|
WarningFilled,
|
||||||
|
CheckCircleFilled,
|
||||||
|
InfoCircleFilled,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Dummy data untuk notifikasi
|
||||||
|
const initialNotifications = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'critical',
|
||||||
|
title: 'Compressor Unit A - Overheat detected (85°C)',
|
||||||
|
plc: 'PLC-001',
|
||||||
|
tag: 'A1-TEMP',
|
||||||
|
engineer: 'Siti Nurhaliza',
|
||||||
|
time: '2 menit lalu',
|
||||||
|
status: 'unread',
|
||||||
|
isRead: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'warning',
|
||||||
|
title: 'Compressor Unit C - Pressure slightly high (7.2 bar)',
|
||||||
|
plc: 'PLC-003',
|
||||||
|
tag: 'C3-PRESS',
|
||||||
|
engineer: 'Joko Widodo',
|
||||||
|
time: '15 menit lalu',
|
||||||
|
status: 'unread',
|
||||||
|
isRead: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: 'resolved',
|
||||||
|
title: 'Compressor Unit B - Vibration issue resolved',
|
||||||
|
plc: 'PLC-002',
|
||||||
|
tag: 'B2-VIB',
|
||||||
|
engineer: 'Rudi Santoso',
|
||||||
|
time: '1 jam lalu',
|
||||||
|
status: 'read',
|
||||||
|
isRead: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ListNotification = memo(function ListNotification(props) {
|
||||||
|
const [notifications, setNotifications] = useState(initialNotifications);
|
||||||
|
const [activeTab, setActiveTab] = useState('all');
|
||||||
|
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 filterNotifications = (status) => {
|
||||||
|
if (status === 'all') return notifications;
|
||||||
|
if (status === 'unread') return notifications.filter((n) => !n.isRead);
|
||||||
|
if (status === 'read') return notifications.filter((n) => n.isRead);
|
||||||
|
return notifications;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnreadCount = () => {
|
||||||
|
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) => ({
|
||||||
|
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',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Notifikasi
|
||||||
|
</h2>
|
||||||
|
<p style={{ margin: '0 0 24px 0', color: '#8c8c8c', fontSize: '14px' }}>
|
||||||
|
Riwayat notifikasi yang dikirim ke engineer
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<div style={{ borderBottom: '1px solid #f0f0f0', marginBottom: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('all')}
|
||||||
|
style={tabButtonStyle(activeTab === 'all')}
|
||||||
|
>
|
||||||
|
Semua
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('unread')}
|
||||||
|
style={{
|
||||||
|
...tabButtonStyle(activeTab === 'unread'),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Belum Dibaca
|
||||||
|
{getUnreadCount() > 0 && (
|
||||||
|
<Badge
|
||||||
|
count={getUnreadCount()}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('read')}
|
||||||
|
style={tabButtonStyle(activeTab === 'read')}
|
||||||
|
>
|
||||||
|
Sudah Dibaca
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notification List */}
|
||||||
|
<div>
|
||||||
|
{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 (
|
||||||
|
<div
|
||||||
|
key={notification.id}
|
||||||
|
style={{
|
||||||
|
marginBottom: '12px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
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';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Dot for unread */}
|
||||||
|
{!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 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
color: color,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '24px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent style={{ fontSize: '24px' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: '4px',
|
||||||
|
color: '#262626',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{notification.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{notification.plc}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#d9d9d9',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tag: {notification.tag}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
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>
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={() => handleViewDetail(notification)}
|
||||||
|
style={{
|
||||||
|
color: '#FF6B35',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Lihat Detail
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListNotification;
|
||||||
Reference in New Issue
Block a user