feat: add detail notification page and update notification links
This commit is contained in:
@@ -34,6 +34,7 @@ import IndexNotification from './pages/notification/IndexNotification';
|
||||
import IndexRole from './pages/role/IndexRole';
|
||||
import IndexUser from './pages/user/IndexUser';
|
||||
import IndexContact from './pages/contact/IndexContact';
|
||||
import DetailNotification from './pages/sparepartGudang/IndexSparepartGudang'
|
||||
|
||||
import SvgTest from './pages/home/SvgTest';
|
||||
import SvgOverviewCompressor from './pages/home/SvgOverviewCompressor';
|
||||
@@ -57,6 +58,8 @@ const App = () => {
|
||||
<Route path="/signin" element={<SignIn />} />
|
||||
<Route path="/signup" element={<SignUp />} />
|
||||
<Route path="/svg" element={<SvgTest />} />
|
||||
<Route path="/detail-notification/:notificationId" element={<DetailNotification />} />
|
||||
|
||||
|
||||
{/* Protected Routes */}
|
||||
<Route path="/dashboard" element={<ProtectedRoute />}>
|
||||
|
||||
@@ -34,10 +34,10 @@ import {
|
||||
PlusOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, Link as RouterLink } from 'react-router-dom';
|
||||
import { getAllNotification } from '../../../api/notification';
|
||||
|
||||
const { Text, Paragraph, Link } = Typography;
|
||||
const { Text, Paragraph, Link: AntdLink } = Typography;
|
||||
|
||||
// Transform API response to component format
|
||||
const transformNotificationData = (apiData) => {
|
||||
@@ -57,7 +57,7 @@ const transformNotificationData = (apiData) => {
|
||||
}) + ' WIB',
|
||||
location: item.device_location || 'Location not specified',
|
||||
details: item.message_error_issue || 'No details available',
|
||||
link: '#', // Will be updated when API provides link
|
||||
link: `/verification/spare-part/${item.notification_error_id}`, // Dummy URL untuk verifikasi spare part
|
||||
subsection: item.solution_name || 'N/A',
|
||||
isRead: item.is_read,
|
||||
status: item.is_read ? 'Resolved' : item.is_delivered ? 'Delivered' : 'Pending',
|
||||
@@ -333,9 +333,9 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
</Space>
|
||||
<Space>
|
||||
<LinkOutlined />
|
||||
<Link href={notification.link} target="_blank">
|
||||
<AntdLink href={notification.link} target="_blank">
|
||||
{notification.link}
|
||||
</Link>
|
||||
</AntdLink>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<SendOutlined />}
|
||||
@@ -380,22 +380,22 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
setModalContent('user');
|
||||
}}
|
||||
/>
|
||||
<RouterLink
|
||||
to={`/detail-notification/${notification.id.split('-')[1]}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
<EyeOutlined style={{ color: '#1890ff' }} />
|
||||
}
|
||||
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
||||
title="Details"
|
||||
style={{
|
||||
border: '1px solid #1890ff',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setModalContent('details');
|
||||
setSelectedNotification(notification);
|
||||
}}
|
||||
/>
|
||||
</RouterLink>
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
|
||||
237
src/pages/sparepartGudang/IndexSparepartGudang.jsx
Normal file
237
src/pages/sparepartGudang/IndexSparepartGudang.jsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Layout,
|
||||
Card,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Space,
|
||||
Button,
|
||||
Spin,
|
||||
Result,
|
||||
Input,
|
||||
} from 'antd';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
CloseCircleFilled,
|
||||
WarningFilled,
|
||||
CheckCircleFilled,
|
||||
InfoCircleFilled,
|
||||
CloseOutlined,
|
||||
BookOutlined,
|
||||
ToolOutlined,
|
||||
HistoryOutlined,
|
||||
FilePdfOutlined,
|
||||
PlusOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
// Path disesuaikan karena lokasi file berubah
|
||||
// import { getNotificationById } from '../../api/notification'; // Dihapus karena belum ada di file API
|
||||
import UserHistoryModal from '../notification/component/UserHistoryModal';
|
||||
import LogHistoryModal from '../notification/component/LogHistoryModal';
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Text, Paragraph, Link } = Typography;
|
||||
|
||||
// Menggunakan kembali fungsi transform dari ListNotification untuk konsistensi data
|
||||
const transformNotificationData = (item) => ({
|
||||
id: `notification-${item.notification_error_id || 'dummy'}-0`,
|
||||
type: item.is_read ? 'resolved' : item.is_delivered ? 'warning' : 'critical',
|
||||
title: item.device_name || 'Unknown Device',
|
||||
issue: item.error_code_name || 'Unknown Error',
|
||||
description: `${item.error_code} - ${item.error_code_name}`,
|
||||
timestamp: new Date(item.created_at || Date.now()).toLocaleString('id-ID'),
|
||||
location: item.device_location || 'Location not specified',
|
||||
details: item.message_error_issue || 'No details available',
|
||||
link: '#',
|
||||
subsection: item.solution_name || 'N/A',
|
||||
isRead: item.is_read || false,
|
||||
status: item.is_read ? 'Resolved' : item.is_delivered ? 'Delivered' : 'Pending',
|
||||
tag: item.error_code,
|
||||
plc: item.plc || 'N/A',
|
||||
});
|
||||
|
||||
const getDummyNotificationById = (id) => {
|
||||
console.log("Fetching dummy data for ID:", id);
|
||||
// Data mentah dummy, seolah-olah dari API
|
||||
const rawDummyData = { device_name: 'Compressor C-101', error_code_name: 'High Temperature', error_code: 'TEMP-H-303', device_location: 'Gudang Produksi A', message_error_issue: 'Suhu kompresor terdeteksi melebihi ambang batas aman.', is_delivered: true, plc: 'PLC-UTL-01' };
|
||||
// Mengolah data mentah dummy menggunakan transform function
|
||||
return transformNotificationData(rawDummyData);
|
||||
};
|
||||
|
||||
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 IndexSparepartGudang = () => {
|
||||
const { notificationId } = useParams(); // Mungkin perlu disesuaikan jika route berbeda
|
||||
const navigate = useNavigate();
|
||||
const [notification, setNotification] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [modalContent, setModalContent] = useState(null); // 'user', 'log', atau null
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Ganti dengan fungsi API asli Anda
|
||||
// const response = await getNotificationById(notificationId);
|
||||
// setNotification(response.data);
|
||||
|
||||
// Menggunakan data dummy untuk sekarang
|
||||
const dummyData = getDummyNotificationById(notificationId);
|
||||
if (dummyData) {
|
||||
setNotification(dummyData);
|
||||
} else {
|
||||
throw new Error('Notification not found');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDetail();
|
||||
}, [notificationId]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Spin size="large" />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !notification) {
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the notification you visited does not exist."
|
||||
extra={<Button type="primary" onClick={() => navigate('/notification')}>Back to List</Button>}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
const { color } = getIconAndColor(notification.type);
|
||||
|
||||
return (
|
||||
<Layout style={{ padding: '24px', backgroundColor: '#f0f2f5' }}>
|
||||
<Content>
|
||||
<Card>
|
||||
<div style={{ borderBottom: '1px solid #f0f0f0', paddingBottom: '16px', marginBottom: '24px' }}>
|
||||
<Row justify="space-between" align="middle">
|
||||
<Col>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/notification')}
|
||||
style={{ paddingLeft: 0 }}
|
||||
>
|
||||
Back to notification list
|
||||
</Button>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button
|
||||
icon={<UserOutlined />}
|
||||
onClick={() => setModalContent('user')}
|
||||
>
|
||||
User History
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<div style={{ backgroundColor: '#fffbe6', border: '1px solid #ffe58f', borderRadius: '4px', padding: '8px 16px', textAlign: 'center', marginTop: '16px' }}>
|
||||
<Typography.Title level={4} style={{ margin: 0, color: '#262626' }}>
|
||||
Error Notification Detail
|
||||
</Typography.Title>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<Row gutter={[24, 24]}>
|
||||
{/* Kolom Kiri: Data Kompresor */}
|
||||
<Col xs={24} lg={12}>
|
||||
<Card size="small" style={{ height: '100%', borderColor: '#d4380d' }} bodyStyle={{ padding: '16px' }}>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<Row gutter={16} align="middle">
|
||||
<Col>
|
||||
<div style={{ width: '32px', height: '32px', borderRadius: '50%', backgroundColor: '#d4380d', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff', fontSize: '18px' }}><CloseOutlined /></div>
|
||||
</Col>
|
||||
<Col>
|
||||
<Text>{notification.title}</Text>
|
||||
<div style={{ marginTop: '2px' }}><Text strong style={{ fontSize: '16px' }}>{notification.issue}</Text></div>
|
||||
</Col>
|
||||
</Row>
|
||||
<div>
|
||||
<Text strong>Plant Subsection</Text>
|
||||
<div>{notification.subsection}</div>
|
||||
<Text strong style={{ display: 'block', marginTop: '8px' }}>Time</Text>
|
||||
<div>{notification.timestamp}</div>
|
||||
</div>
|
||||
<div style={{ border: '1px solid #d4380d', borderRadius: '4px', padding: '8px', background: 'linear-gradient(to right, #ffe7e6, #ffffff)' }}>
|
||||
<Row justify="space-around" align="middle">
|
||||
<Col><Text style={{ fontSize: '12px', color: color }}>Value</Text><div style={{ fontWeight: 'bold', fontSize: '16px', color: color }}>N/A</div></Col>
|
||||
<Col><Text type="secondary" style={{ fontSize: '12px' }}>Treshold</Text><div style={{ fontWeight: 500 }}>N/A</div></Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* Kolom Kanan: Informasi Teknis */}
|
||||
<Col xs={24} lg={12}>
|
||||
<Card title="Informasi Teknis" size="small" style={{ height: '100%' }}>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<div><Text strong>PLC</Text><div>{notification.plc || 'N/A'}</div></div>
|
||||
<div><Text strong>Status</Text><div style={{ color: '#faad14', fontWeight: 500 }}>{notification.status}</div></div>
|
||||
<div><Text strong>Tag</Text><div style={{ fontFamily: 'monospace', backgroundColor: '#f0f0f0', padding: '2px 6px', borderRadius: '4px', display: 'inline-block' }}>{notification.tag}</div></div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<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><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>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} md={8}><Card size="small" title="Guideline Documents" style={{ height: '100%' }}><Space direction="vertical" size="small" style={{ width: '100%' }}><Card size="small" hoverable><Text><FilePdfOutlined style={{ marginRight: '8px' }} /> Error 303.pdf</Text><Link href="#" target="_blank" style={{ fontSize: '12px', display: 'block', marginLeft: '24px' }}>lihat disini</Link></Card><Card size="small" hoverable><Text><FilePdfOutlined style={{ marginRight: '8px' }} /> SOP Kompresor.pdf</Text><Link href="#" target="_blank" style={{ fontSize: '12px', display: 'block', marginLeft: '24px' }}>lihat disini</Link></Card></Space></Card></Col>
|
||||
<Col xs={24} md={8}><Card size="small" title="Required Spare Parts" style={{ height: '100%' }}><Space direction="vertical" size="small" style={{ width: '100%' }}><Card size="small"><Row gutter={16} align="top"><Col span={7} style={{ textAlign: 'center' }}><div style={{ width: '100%', height: '60px', backgroundColor: '#f0f0f0', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '4px', marginBottom: '8px' }}><ToolOutlined style={{ fontSize: '24px', color: '#bfbfbf' }} /></div><Text style={{ fontSize: '12px', color: '#52c41a', fontWeight: 500 }}>Available</Text></Col><Col span={17}><Text strong>Air Filter</Text><Paragraph style={{ fontSize: '12px', margin: 0, color: '#595959' }}>Filters incoming air to remove dust.</Paragraph></Col></Row></Card></Space></Card></Col>
|
||||
<Col xs={24} md={8}><Card size="small" title="Activity Log" style={{ height: '100%' }} extra={<Button type="primary" onClick={() => setModalContent('log')}>View Logs</Button>}><div style={{ textAlign: 'center', padding: '20px' }}><HistoryOutlined style={{ fontSize: '32px', color: '#bfbfbf' }} /><Text block type="secondary" style={{ marginTop: '8px' }}>Click 'View Logs' to see the full activity history.</Text></div></Card></Col>
|
||||
</Row>
|
||||
</Space>
|
||||
</Card>
|
||||
</Content>
|
||||
|
||||
<UserHistoryModal
|
||||
visible={modalContent === 'user'}
|
||||
onCancel={() => setModalContent(null)}
|
||||
notificationData={notification}
|
||||
/>
|
||||
<LogHistoryModal
|
||||
visible={modalContent === 'log'}
|
||||
onCancel={() => setModalContent(null)}
|
||||
notificationData={notification}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexSparepartGudang;
|
||||
Reference in New Issue
Block a user