feat: replace LogHistoryModal with LogHistoryCard and update DetailNotification for improved log history display

This commit is contained in:
2025-12-09 13:32:53 +07:00
parent 3225a0865e
commit a014d6b370
4 changed files with 238 additions and 152 deletions

View File

@@ -29,7 +29,7 @@ import {
// Path disesuaikan karena lokasi file berubah // Path disesuaikan karena lokasi file berubah
// import { getNotificationById } from '../../api/notification'; // Dihapus karena belum ada di file API // import { getNotificationById } from '../../api/notification'; // Dihapus karena belum ada di file API
import UserHistoryModal from '../notification/component/UserHistoryModal'; import UserHistoryModal from '../notification/component/UserHistoryModal';
import LogHistoryModal from '../notification/component/LogHistoryModal'; import LogHistoryCard from '../notification/component/LogHistoryCard'; // Ganti LogHistoryModal dengan LogHistoryCard
const { Content } = Layout; const { Content } = Layout;
const { Text, Paragraph, Link } = Typography; const { Text, Paragraph, Link } = Typography;
@@ -79,7 +79,7 @@ const DetailNotificationTab = () => {
const [notification, setNotification] = useState(null); const [notification, setNotification] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [modalContent, setModalContent] = useState(null); // 'user', 'log', atau null const [modalContent, setModalContent] = useState(null); // 'user', atau null
const [isAddingLog, setIsAddingLog] = useState(false); const [isAddingLog, setIsAddingLog] = useState(false);
const logHistoryData = [ const logHistoryData = [
@@ -196,7 +196,7 @@ const DetailNotificationTab = () => {
<Space direction="vertical" size="large" style={{ width: '100%' }}> <Space direction="vertical" size="large" style={{ width: '100%' }}>
<Row gutter={[24, 24]}> <Row gutter={[24, 24]}>
{/* Kolom Kiri: Data Kompresor */} {/* Kolom Kiri: Data Kompresor */}
<Col xs={24} lg={12}> <Col xs={24} lg={8}>
<Card size="small" style={{ height: '100%', borderColor: '#d4380d' }} bodyStyle={{ padding: '16px' }}> <Card size="small" style={{ height: '100%', borderColor: '#d4380d' }} bodyStyle={{ padding: '16px' }}>
<Space direction="vertical" size="large" style={{ width: '100%' }}> <Space direction="vertical" size="large" style={{ width: '100%' }}>
<Row gutter={16} align="middle"> <Row gutter={16} align="middle">
@@ -224,8 +224,8 @@ const DetailNotificationTab = () => {
</Card> </Card>
</Col> </Col>
{/* Kolom Kanan: Informasi Teknis */} {/* Kolom Tengah: Informasi Teknis */}
<Col xs={24} lg={12}> <Col xs={24} lg={8}>
<Card title="Informasi Teknis" size="small" style={{ height: '100%' }}> <Card title="Informasi Teknis" size="small" style={{ height: '100%' }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space direction="vertical" size="middle" style={{ width: '100%' }}>
<div><Text strong>PLC</Text><div>{notification.plc || 'N/A'}</div></div> <div><Text strong>PLC</Text><div>{notification.plc || 'N/A'}</div></div>
@@ -234,12 +234,17 @@ const DetailNotificationTab = () => {
</Space> </Space>
</Card> </Card>
</Col> </Col>
{/* Kolom Kanan: Log History */}
<Col xs={24} lg={8}>
<LogHistoryCard notificationData={notification} />
</Col>
</Row> </Row>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col xs={24} md={8}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><BookOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Handling Guideline</Text></Space></Card></Col> <Col xs={24} md={8}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><BookOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Handling Guideline</Text></Space></Card></Col>
<Col xs={24} md={8}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><ToolOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Spare Part</Text></Space></Card></Col> <Col xs={24} md={8}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><ToolOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Spare Part</Text></Space></Card></Col>
<Col xs={24} md={8} onClick={() => setModalContent('log')} style={{ cursor: 'pointer' }}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><HistoryOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Log Activity</Text></Space></Card></Col> <Col xs={24} md={8} style={{ cursor: 'pointer' }}><Card hoverable bodyStyle={{ padding: '12px', textAlign: 'center' }}><Space><HistoryOutlined style={{ fontSize: '16px', color: '#1890ff' }} /><Text strong style={{ fontSize: '16px', color: '#262626' }}>Log Activity</Text></Space></Card></Col>
</Row> </Row>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
@@ -345,11 +350,6 @@ const DetailNotificationTab = () => {
onCancel={() => setModalContent(null)} onCancel={() => setModalContent(null)}
notificationData={notification} notificationData={notification}
/> />
<LogHistoryModal
visible={modalContent === 'log'}
onCancel={() => setModalContent(null)}
notificationData={notification}
/>
</Layout> </Layout>
); );
}; };

View File

@@ -1,7 +1,7 @@
import React, { memo, useState, 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 { Typography, Row, Col } from 'antd';
import ListNotification from './component/ListNotification'; import ListNotification from './component/ListNotification';
import DetailNotification from './component/DetailNotification'; import DetailNotification from './component/DetailNotification';
@@ -10,10 +10,7 @@ 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 [actionMode, setActionMode] = useState('list');
const [selectedData, setSelectedData] = useState(null); const [selectedData, setSelectedData] = useState(null);
const [isModalVisible, setIsModalVisible] = useState(false);
useEffect(() => { useEffect(() => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@@ -32,33 +29,34 @@ const IndexNotification = memo(function IndexNotification() {
} }
}, [navigate, setBreadcrumbItems]); }, [navigate, setBreadcrumbItems]);
useEffect(() => { const handleCloseDetail = () => {
if (actionMode === 'preview') {
setIsModalVisible(true);
} else {
setIsModalVisible(false);
}
}, [actionMode]);
const handleCancel = () => {
setActionMode('list');
setSelectedData(null); setSelectedData(null);
}; };
// This handler will be passed to ListNotification to update the selected item
const handleSelectNotification = (data) => {
setSelectedData(data);
};
return ( return (
<React.Fragment> <Row gutter={16}>
<ListNotification <Col span={selectedData ? 16 : 24}>
actionMode={actionMode} <ListNotification
setActionMode={setActionMode} // The setActionMode is likely not needed anymore,
selectedData={selectedData} // but we pass the selection handler
setSelectedData={setSelectedData} setActionMode={() => {}} // Keep prop for safety, but can be empty
/> setSelectedData={handleSelectNotification}
<DetailNotification />
visible={isModalVisible} </Col>
onCancel={handleCancel} {selectedData && (
selectedData={selectedData} <Col span={8}>
/> <DetailNotification
</React.Fragment> selectedData={selectedData}
onClose={handleCloseDetail}
/>
</Col>
)}
</Row>
); );
}); });

View File

@@ -1,8 +1,12 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { Modal, Row, Col, Tag, Divider } from 'antd'; import { Row, Col, Tag, Divider, Card, Button } from 'antd';
import { CloseCircleFilled, WarningFilled, CheckCircleFilled, InfoCircleFilled } from '@ant-design/icons'; import { CloseCircleFilled, WarningFilled, CheckCircleFilled, InfoCircleFilled } from '@ant-design/icons';
const DetailNotification = memo(function DetailNotification({ visible, onCancel, form, selectedData }) { const DetailNotification = memo(function DetailNotification({ selectedData, onClose }) {
if (!selectedData) {
return null;
}
const getIconAndColor = (type) => { const getIconAndColor = (type) => {
switch (type) { switch (type) {
case 'critical': case 'critical':
@@ -36,133 +40,127 @@ const DetailNotification = memo(function DetailNotification({ visible, onCancel,
} }
}; };
const { IconComponent, color, bgColor, tagColor } = selectedData ? getIconAndColor(selectedData.type) : {}; const { IconComponent, color, bgColor, tagColor } = getIconAndColor(selectedData.type);
return ( return (
<Modal <Card
title="Detail Notifikasi" title="Detail Notifikasi"
open={visible} extra={<Button onClick={onClose}>Tutup</Button>}
onCancel={onCancel} style={{ height: '100%' }}
onOk={onCancel}
okText="Tutup"
cancelButtonProps={{ style: { display: 'none' } }}
width={700}
> >
{selectedData && ( <div>
<div> {/* Header with Icon and Status */}
{/* Header with Icon and Status */} <div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
marginBottom: '24px',
padding: '16px',
backgroundColor: '#fafafa',
borderRadius: '8px',
}}
>
<div <div
style={{ style={{
width: '64px',
height: '64px',
borderRadius: '50%',
backgroundColor: bgColor,
color: color,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '16px', justifyContent: 'center',
marginBottom: '24px', fontSize: '32px',
padding: '16px', flexShrink: 0,
backgroundColor: '#fafafa',
borderRadius: '8px',
}} }}
> >
<div {IconComponent && <IconComponent style={{ fontSize: '32px' }} />}
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> </div>
<div style={{ flex: 1 }}>
<Divider style={{ margin: '16px 0' }} /> <Tag color={tagColor} style={{ marginBottom: '8px', fontSize: '12px' }}>
{selectedData.type.toUpperCase()}
{/* 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> </Tag>
</div> <div style={{ fontSize: '16px', fontWeight: 600, color: '#262626' }}>
{selectedData.title}
{/* 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> </div>
</div> </div>
)}
</Modal> <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>
</Card>
); );
}); });

View File

@@ -0,0 +1,90 @@
import React from 'react';
import { Card, Table, Tag, Typography } from 'antd';
import { ClockCircleOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
const { Text } = Typography;
const getDummyLogHistory = (notification) => {
if (!notification) return [];
return [
{
key: '1',
timestamp: dayjs().subtract(2, 'hour').format('DD-MM-YYYY HH:mm:ss'),
activity: 'Notification Created',
details: `System generated a ${notification.type} notification for: ${notification.issue}`,
},
{
key: '2',
timestamp: dayjs().subtract(1, 'hour').format('DD-MM-YYYY HH:mm:ss'),
activity: 'Notification Sent',
details: 'Sent to 2 engineers',
},
{
key: '3',
timestamp: dayjs().subtract(30, 'minute').format('DD-MM-YYYY HH:mm:ss'),
activity: 'Notification Read',
details: 'Read by Engineer A',
},
{
key: '4',
timestamp: dayjs().subtract(5, 'minute').format('DD-MM-YYYY HH:mm:ss'),
activity: 'Resend Triggered',
details: 'Notification resent by Admin',
},
];
};
const columns = [
{
title: 'Timestamp',
dataIndex: 'timestamp',
key: 'timestamp',
render: (text) => (
<span>
<ClockCircleOutlined style={{ marginRight: 8 }} />
{text}
</span>
),
},
{
title: 'Activity',
dataIndex: 'activity',
key: 'activity',
render: (text) => {
let color = 'blue';
if (text.includes('Created')) {
color = 'geekblue';
} else if (text.includes('Sent')) {
color = 'purple';
} else if (text.includes('Read')) {
color = 'green';
} else if (text.includes('Triggered')) {
color = 'orange';
}
return <Tag color={color}>{text.toUpperCase()}</Tag>;
},
},
{
title: 'Details',
dataIndex: 'details',
key: 'details',
},
];
const LogHistoryCard = ({ notificationData }) => {
const logHistoryData = getDummyLogHistory(notificationData);
return (
<Card title="Log History" size="small" style={{ height: '100%' }}>
<Table
columns={columns}
dataSource={logHistoryData}
pagination={{ pageSize: 3, size: 'small' }}
size="small"
/>
</Card>
);
};
export default LogHistoryCard;