refactor: improve timestamp display and code formatting in notification detail

This commit is contained in:
2025-12-18 19:34:20 +07:00
parent 8cf21643ea
commit e4af2d6e18
2 changed files with 441 additions and 145 deletions

View File

@@ -694,9 +694,9 @@ const ListNotification = memo(function ListNotification(props) {
<Text strong>Plant Subsection</Text>
<div>{selectedNotification.subsection}</div>
<Text strong style={{ display: 'block', marginTop: '8px' }}>
Time
Date & Time
</Text>
<div>{selectedNotification.timestamp.split(' ')[1]} WIB</div>
<div>{selectedNotification.timestamp}</div>
</div>
<div

View File

@@ -1,17 +1,6 @@
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 { Layout, Card, Row, Col, Typography, Space, Button, Spin, Result, Input } from 'antd';
import {
ArrowLeftOutlined,
CloseCircleFilled,
@@ -38,7 +27,8 @@ const transformNotificationData = (apiData) => {
// Extract nested data
const errorCodeData = apiData.error_code;
// Get active solution (is_active: true)
const activeSolution = errorCodeData?.solution?.find(sol => sol.is_active) || errorCodeData?.solution?.[0] || {};
const activeSolution =
errorCodeData?.solution?.find((sol) => sol.is_active) || errorCodeData?.solution?.[0] || {};
return {
id: `notification-${apiData.notification_error_id}-0`,
@@ -46,13 +36,15 @@ const transformNotificationData = (apiData) => {
title: errorCodeData?.error_code_name || 'Unknown Error',
issue: errorCodeData?.error_code || 'Unknown Error',
description: apiData.message_error_issue || 'No details available',
timestamp: apiData.created_at ? new Date(apiData.created_at).toLocaleString('id-ID', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
}) + ' WIB' : 'N/A',
timestamp: apiData.created_at
? new Date(apiData.created_at).toLocaleString('id-ID', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
}) + ' WIB'
: 'N/A',
location: apiData.plant_sub_section_name || 'Location not specified',
details: apiData.message_error_issue || 'No details available',
isRead: apiData.is_read || false,
@@ -67,15 +59,20 @@ const transformNotificationData = (apiData) => {
spareparts: errorCodeData?.spareparts || [],
solution: {
...activeSolution,
path_document: activeSolution.path_document ? activeSolution.path_document.replace('/detail-notification/pdf/', '/notification-detail/pdf/') : activeSolution.path_document
path_document: activeSolution.path_document
? activeSolution.path_document.replace(
'/detail-notification/pdf/',
'/notification-detail/pdf/'
)
: activeSolution.path_document,
}, // Include the active solution data with fixed URL
error_code: errorCodeData,
device_info: {
device_code: apiData.device_code,
device_name: apiData.device_name,
device_location: apiData.device_location,
brand_name: apiData.brand_name
}
brand_name: apiData.brand_name,
},
};
};
@@ -145,7 +142,6 @@ const NotificationDetailTab = () => {
} else {
throw new Error('Notification not found');
}
} catch (err) {
setError(err.message);
console.error('Error fetching notification detail:', err);
@@ -159,7 +155,14 @@ const NotificationDetailTab = () => {
if (loading) {
return (
<Layout style={{ minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Layout
style={{
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Spin size="large" />
</Layout>
);
@@ -167,12 +170,23 @@ const NotificationDetailTab = () => {
if (error || !notification) {
return (
<Layout style={{ minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<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>}
extra={
<Button type="primary" onClick={() => navigate('/notification')}>
Back to List
</Button>
}
/>
</Layout>
);
@@ -184,12 +198,18 @@ const NotificationDetailTab = () => {
<Layout style={{ padding: '24px', backgroundColor: '#f0f2f5' }}>
<Content>
<Card>
<div style={{ borderBottom: '1px solid #f0f0f0', paddingBottom: '16px', marginBottom: '24px' }}>
<div
style={{
borderBottom: '1px solid #f0f0f0',
paddingBottom: '16px',
marginBottom: '24px',
}}
>
<Row justify="space-between" align="middle">
<Col>
<Button
type="text"
icon={<ArrowLeftOutlined />}
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/notification')}
style={{ paddingLeft: 0 }}
>
@@ -197,15 +217,24 @@ const NotificationDetailTab = () => {
</Button>
</Col>
<Col>
<Button
icon={<UserOutlined />}
<Button
icon={<UserOutlined />}
onClick={() => setModalContent('user')}
>
User History
</Button>
</Col>
</Row>
<div style={{ backgroundColor: '#f6ffed', border: '1px solid #b7eb8f', borderRadius: '4px', padding: '8px 16px', textAlign: 'center', marginTop: '16px' }}>
<div
style={{
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '4px',
padding: '8px 16px',
textAlign: 'center',
marginTop: '16px',
}}
>
<Typography.Title level={4} style={{ margin: 0, color: '#262626' }}>
Error Notification Detail
</Typography.Title>
@@ -216,22 +245,53 @@ const NotificationDetailTab = () => {
<Row gutter={[24, 24]}>
{/* Kolom Kiri: Data Kompresor */}
<Col xs={24} lg={8}>
<Card size="small" style={{ height: '100%', borderColor: '#d4380d' }} bodyStyle={{ padding: '16px' }}>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<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>
<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>
<div style={{ marginTop: '2px' }}>
<Text strong style={{ fontSize: '16px' }}>
{notification.issue}
</Text>
</div>
</Col>
</Row>
<div>
<Text strong>Plant Subsection</Text>
<div>{notification.location}</div>
<Text strong style={{ display: 'block', marginTop: '8px' }}>Time</Text>
<div>{notification.timestamp.split(' ')[1]} WIB</div>
<Text
strong
style={{ display: 'block', marginTop: '8px' }}
>
Date & Time
</Text>
<div>{notification.timestamp}</div>
</div>
</Space>
</Card>
@@ -239,13 +299,44 @@ const NotificationDetailTab = () => {
{/* Kolom Tengah: Informasi Teknis */}
<Col xs={24} lg={8}>
<Card title="Informasi Teknis" size="small" style={{ height: '100%' }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<div><Text strong>Error Channel</Text><div>{notification.error_chanel || 'N/A'}</div></div>
<div><Text strong>Device Code</Text><div>{notification.device_info?.device_code || 'N/A'}</div></div>
<div><Text strong>Device Name</Text><div>{notification.device_info?.device_name || 'N/A'}</div></div>
<div><Text strong>Device Location</Text><div>{notification.device_info?.device_location || 'N/A'}</div></div>
<div><Text strong>Brand</Text><div>{notification.device_info?.brand_name || 'N/A'}</div></div>
<Card
title="Device Information"
size="small"
style={{ height: '100%' }}
>
<Space
direction="vertical"
size="middle"
style={{ width: '100%' }}
>
<div>
<Text strong>Error Channel</Text>
<div>{notification.error_chanel || 'N/A'}</div>
</div>
<div>
<Text strong>Device Code</Text>
<div>
{notification.device_info?.device_code || 'N/A'}
</div>
</div>
<div>
<Text strong>Device Name</Text>
<div>
{notification.device_info?.device_name || 'N/A'}
</div>
</div>
<div>
<Text strong>Device Location</Text>
<div>
{notification.device_info?.device_location || 'N/A'}
</div>
</div>
<div>
<Text strong>Brand</Text>
<div>
{notification.device_info?.brand_name || 'N/A'}
</div>
</div>
</Space>
</Card>
</Col>
@@ -257,45 +348,181 @@ const NotificationDetailTab = () => {
</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} 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}>
<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} 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%' }}>
{notification.error_code?.solution && notification.error_code.solution.length > 0 ? (
<Card
size="small"
title="Guideline Documents"
style={{ height: '100%' }}
>
<Space
direction="vertical"
size="small"
style={{ width: '100%' }}
>
{notification.error_code?.solution &&
notification.error_code.solution.length > 0 ? (
<>
{notification.error_code.solution
.filter(sol => sol.is_active) // Hanya tampilkan solusi yang aktif
.filter((sol) => sol.is_active) // Hanya tampilkan solusi yang aktif
.map((sol, index) => (
<div key={sol.brand_code_solution_id || index}>
<div
key={
sol.brand_code_solution_id || index
}
>
{sol.path_document ? (
<Card size="small" bodyStyle={{ padding: '8px 12px' }} hoverable extra={<Text type="secondary" style={{ fontSize: '10px' }}>PDF</Text>}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Card
size="small"
bodyStyle={{
padding: '8px 12px',
}}
hoverable
extra={
<Text
type="secondary"
style={{
fontSize: '10px',
}}
>
PDF
</Text>
}
>
<div
style={{
display: 'flex',
justifyContent:
'space-between',
alignItems: 'center',
}}
>
<div>
<Text style={{ fontSize: '12px', color: '#262626' }}><FilePdfOutlined style={{ marginRight: '8px' }} /> {sol.file_upload_name || 'Solution Document.pdf'}</Text>
<Link href={sol.path_document.replace('/detail-notification/pdf/', '/notification-detail/pdf/')} target="_blank" style={{ fontSize: '12px', display: 'block' }}>lihat disini</Link>
<Text
style={{
fontSize:
'12px',
color: '#262626',
}}
>
<FilePdfOutlined
style={{
marginRight:
'8px',
}}
/>{' '}
{sol.file_upload_name ||
'Solution Document.pdf'}
</Text>
<Link
href={sol.path_document.replace(
'/detail-notification/pdf/',
'/notification-detail/pdf/'
)}
target="_blank"
style={{
fontSize:
'12px',
display:
'block',
}}
>
lihat disini
</Link>
</div>
</div>
</Card>
) : null}
{sol.type_solution === 'text' && sol.text_solution ? (
<Card size="small" bodyStyle={{ padding: '8px 12px' }} extra={<Text type="secondary" style={{ fontSize: '10px' }}>{sol.type_solution.toUpperCase()}</Text>}>
{sol.type_solution === 'text' &&
sol.text_solution ? (
<Card
size="small"
bodyStyle={{
padding: '8px 12px',
}}
extra={
<Text
type="secondary"
style={{
fontSize: '10px',
}}
>
{sol.type_solution.toUpperCase()}
</Text>
}
>
<div>
<Text strong>{sol.solution_name}:</Text>
<div style={{ marginTop: '4px' }}>{sol.text_solution}</div>
<Text strong>
{sol.solution_name}:
</Text>
<div
style={{
marginTop: '4px',
}}
>
{sol.text_solution}
</div>
</div>
</Card>
) : null}
</div>
))
}
))}
</>
) : (
<div style={{ textAlign: 'center', padding: '20px', color: '#8c8c8c' }}>
<div
style={{
textAlign: 'center',
padding: '20px',
color: '#8c8c8c',
}}
>
Tidak ada dokumen solusi tersedia
</div>
)}
@@ -303,39 +530,98 @@ const NotificationDetailTab = () => {
</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%' }}>
{notification.spareparts && notification.spareparts.length > 0 ? (
<Card
size="small"
title="Required Spare Parts"
style={{ height: '100%' }}
>
<Space
direction="vertical"
size="small"
style={{ width: '100%' }}
>
{notification.spareparts &&
notification.spareparts.length > 0 ? (
notification.spareparts.map((sparepart, index) => (
<Card size="small" key={index} bodyStyle={{ padding: '12px' }} hoverable>
<Card
size="small"
key={index}
bodyStyle={{ padding: '12px' }}
hoverable
>
<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' }} />
<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: sparepart.sparepart_stok === 'Available' || sparepart.sparepart_stok === 'available' ? '#52c41a' : '#ff4d4f',
fontWeight: 500
}}>
<Text
style={{
fontSize: '12px',
color:
sparepart.sparepart_stok ===
'Available' ||
sparepart.sparepart_stok ===
'available'
? '#52c41a'
: '#ff4d4f',
fontWeight: 500,
}}
>
{sparepart.sparepart_stok}
</Text>
</Col>
<Col span={17}>
<Space direction="vertical" size={4} style={{ width: '100%' }}>
<Text strong>{sparepart.sparepart_name}</Text>
<Paragraph style={{ fontSize: '12px', margin: 0, color: '#595959' }}>
{sparepart.sparepart_description || 'Deskripsi tidak tersedia'}
<Space
direction="vertical"
size={4}
style={{ width: '100%' }}
>
<Text strong>
{sparepart.sparepart_name}
</Text>
<Paragraph
style={{
fontSize: '12px',
margin: 0,
color: '#595959',
}}
>
{sparepart.sparepart_description ||
'Deskripsi tidak tersedia'}
</Paragraph>
<div style={{
border: '1px solid #d9d9d9',
borderRadius: '4px',
padding: '4px 8px',
fontSize: '11px',
color: '#8c8c8c',
marginTop: '8px'
}}>
Kode: {sparepart.sparepart_code} | Qty: {sparepart.sparepart_qty} | Unit: {sparepart.sparepart_unit}
<div
style={{
border: '1px solid #d9d9d9',
borderRadius: '4px',
padding: '4px 8px',
fontSize: '11px',
color: '#8c8c8c',
marginTop: '8px',
}}
>
Kode: {sparepart.sparepart_code}{' '}
| Qty: {sparepart.sparepart_qty}{' '}
| Unit:{' '}
{sparepart.sparepart_unit}
</div>
</Space>
</Col>
@@ -343,7 +629,13 @@ const NotificationDetailTab = () => {
</Card>
))
) : (
<div style={{ textAlign: 'center', padding: '20px', color: '#8c8c8c' }}>
<div
style={{
textAlign: 'center',
padding: '20px',
color: '#8c8c8c',
}}
>
Tidak ada spare parts terkait
</div>
)}
@@ -351,63 +643,67 @@ const NotificationDetailTab = () => {
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ height: '100%' }}>
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Card
<Card size="small" style={{ height: '100%' }}>
<Space
direction="vertical"
size="small"
bodyStyle={{
padding: '8px 12px',
backgroundColor: isAddingLog ? '#fafafa' : '#fff',
}}
style={{ width: '100%' }}
>
<Space
direction="vertical"
style={{ width: '100%' }}
size="small"
>
{isAddingLog && (
<>
<Text strong style={{ fontSize: '12px' }}>
Add New Log / Update Progress
</Text>
<Input.TextArea
rows={2}
placeholder="Tuliskan update penanganan di sini..."
/>
</>
)}
<Button
type={isAddingLog ? 'primary' : 'dashed'}
size="small"
block
icon={!isAddingLog && <PlusOutlined />}
onClick={() => setIsAddingLog(!isAddingLog)}
>
{isAddingLog ? 'Submit Log' : 'Add Log'}
</Button>
</Space>
</Card>
{logHistoryData.map((log) => (
<Card
key={log.id}
size="small"
bodyStyle={{ padding: '8px 12px' }}
bodyStyle={{
padding: '8px 12px',
backgroundColor: isAddingLog ? '#fafafa' : '#fff',
}}
>
<Paragraph
style={{ fontSize: '12px', margin: 0 }}
ellipsis={{ rows: 2 }}
<Space
direction="vertical"
style={{ width: '100%' }}
size="small"
>
<Text strong>{log.addedBy.name}:</Text>{' '}
{log.description}
</Paragraph>
<Text type="secondary" style={{ fontSize: '11px' }}>
{log.timestamp}
</Text>
{isAddingLog && (
<>
<Text strong style={{ fontSize: '12px' }}>
Add New Log / Update Progress
</Text>
<Input.TextArea
rows={2}
placeholder="Tuliskan update penanganan di sini..."
/>
</>
)}
<Button
type={isAddingLog ? 'primary' : 'dashed'}
size="small"
block
icon={!isAddingLog && <PlusOutlined />}
onClick={() => setIsAddingLog(!isAddingLog)}
>
{isAddingLog ? 'Submit Log' : 'Add Log'}
</Button>
</Space>
</Card>
))}
</Space>
</Card>
</Col>
{logHistoryData.map((log) => (
<Card
key={log.id}
size="small"
bodyStyle={{ padding: '8px 12px' }}
>
<Paragraph
style={{ fontSize: '12px', margin: 0 }}
ellipsis={{ rows: 2 }}
>
<Text strong>{log.addedBy.name}:</Text>{' '}
{log.description}
</Paragraph>
<Text type="secondary" style={{ fontSize: '11px' }}>
{log.timestamp}
</Text>
</Card>
))}
</Space>
</Card>
</Col>
</Row>
</Space>
</Card>
@@ -422,4 +718,4 @@ const NotificationDetailTab = () => {
);
};
export default NotificationDetailTab;
export default NotificationDetailTab;