)}
{sparepartImages[field.key] && (
-
+
- {sparepartImages[field.key].name}
+
+ {sparepartImages[field.key].name}
+
- Size: {(sparepartImages[field.key].size / 1024).toFixed(1)} KB
+ Size:{' '}
+ {(
+ sparepartImages[field.key].size / 1024
+ ).toFixed(1)}{' '}
+ KB
{!isReadOnly && (
@@ -209,24 +234,6 @@ const SparepartForm = ({
)}
-
- {/* Delete Button */}
- {!isReadOnly && sparepartFields.length > 1 && (
-
- }
- onClick={() => onRemoveSparepartField(field.key)}
- style={{
- borderColor: '#ff4d4f',
- color: '#ff4d4f'
- }}
- >
- Remove
-
-
- )}
))}
@@ -243,17 +250,9 @@ const SparepartForm = ({
)}
-
- {!isReadOnly && (
-
-
- * Add at least one sparepart for this error code.
-
-
- )}
);
};
-export default SparepartForm;
\ No newline at end of file
+export default SparepartForm;
diff --git a/src/pages/notification/IndexNotification.jsx b/src/pages/notification/IndexNotification.jsx
index 1c5ab9f..664cd45 100644
--- a/src/pages/notification/IndexNotification.jsx
+++ b/src/pages/notification/IndexNotification.jsx
@@ -1,7 +1,7 @@
import React, { memo, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
-import { Form, Typography } from 'antd';
+import { Typography } from 'antd';
import ListNotification from './component/ListNotification';
import DetailNotification from './component/DetailNotification';
@@ -10,7 +10,6 @@ const { Text } = Typography;
const IndexNotification = memo(function IndexNotification() {
const navigate = useNavigate();
const { setBreadcrumbItems } = useBreadcrumb();
- const [form] = Form.useForm();
const [actionMode, setActionMode] = useState('list');
const [selectedData, setSelectedData] = useState(null);
@@ -36,19 +35,14 @@ const IndexNotification = memo(function IndexNotification() {
useEffect(() => {
if (actionMode === 'preview') {
setIsModalVisible(true);
- if (selectedData) {
- form.setFieldsValue(selectedData);
- }
} else {
setIsModalVisible(false);
- form.resetFields();
}
- }, [actionMode, selectedData, form]);
+ }, [actionMode]);
const handleCancel = () => {
setActionMode('list');
setSelectedData(null);
- form.resetFields();
};
return (
@@ -62,7 +56,6 @@ const IndexNotification = memo(function IndexNotification() {
diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx
index 433bdc6..2fb225c 100644
--- a/src/pages/notification/component/ListNotification.jsx
+++ b/src/pages/notification/component/ListNotification.jsx
@@ -1,5 +1,18 @@
import React, { memo, useState, useEffect } from 'react';
-import { Button, Row, Col, Card, Badge, Input, Typography, Space, Divider, Modal, Tag, message } from 'antd';
+import {
+ Button,
+ Row,
+ Col,
+ Card,
+ Badge,
+ Input,
+ Typography,
+ Space,
+ Divider,
+ Modal,
+ Tag,
+ message,
+} from 'antd';
import {
CloseCircleFilled,
WarningFilled,
@@ -22,72 +35,63 @@ import {
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
+import { getAllNotification } from '../../../api/notification';
const { Text, Paragraph, Link } = Typography;
-// Dummy data untuk notifikasi device
-const initialDeviceNotifications = [
- {
- id: 1,
- type: 'critical',
- title: 'Compressor Unit A',
- issue: 'Overheat detected (85°C)',
- description: '⚠️ Compressor Unit A - Overheat Detected (85°C) 🚨',
- timestamp: '04-11-2025 11.39 WIB',
- location: 'Lantai 2, Area Produksi A, Zona 3',
- details: 'Terjadi kenaikan suhu melebihi ambang batas pada Compressor Unit A. Pengecekan potensi kerusakan dibutuhkan.',
- link: 'https://tinyurl.com/compA85',
- subsection: 'Air Dryer A',
- value: '85° C',
- threshold: '≤75° C',
- isRead: false,
- plc: 'PLC-001',
- status: 'Butuh Info lebih',
- tag: 'A1-TEMP',
- },
- {
- id: 2,
- type: 'warning',
- title: 'Compressor Unit C',
- issue: 'Pressure slightly high (7.2 bar)',
- description: '🔧 Compressor Unit C - Pressure High (7.2 bar)',
- timestamp: '04-11-2025 11.30 WIB',
- location: 'Lantai 1, Area Produksi C, Zona 1',
- details: 'Tekanan mendekati ambang batas atas. Perlu monitoring lebih lanjut oleh engineer.',
- link: 'https://tinyurl.com/compC72',
- subsection: 'Main Compressor',
- value: '7.2 bar',
- threshold: '<7.5 bar',
- isRead: false,
- plc: 'PLC-002',
- status: 'Monitoring',
- tag: 'C1-PRESSURE',
- },
- {
- id: 3,
- type: 'resolved',
- title: 'Compressor Unit B',
- issue: 'Vibration issue resolved',
- description: '✅ Compressor Unit B - Vibration Resolved',
- timestamp: '04-11-2025 10.05 WIB',
- location: 'Lantai 2, Area Produksi B, Zona 2',
- details: 'Getaran pada Unit B telah kembali normal setelah perbaikan.',
- link: 'https://tinyurl.com/compBresolved',
- subsection: 'Motor Bearing',
- value: 'Normal',
- threshold: 'N/A',
- isRead: true,
- plc: 'PLC-003',
- status: 'Resolved',
- tag: 'B1-VIBRATION',
- },
-];
+// Transform API response to component format
+const transformNotificationData = (apiData) => {
+ return apiData.map((item, index) => ({
+ id: `notification-${item.notification_error_id}-${index}`, // Unique key prefix with array index
+ 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).toLocaleString('id-ID', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }) + ' WIB',
+ location: item.device_location || 'Location not specified',
+ details: item.message_error_issue || 'No details available',
+ link: '#', // Will be updated when API provides link
+ subsection: item.solution_name || 'N/A',
+ isRead: item.is_read,
+ status: item.is_read ? 'Resolved' : item.is_delivered ? 'Delivered' : 'Pending',
+ tag: item.error_code,
+ errorCode: item.error_code,
+ solutionName: item.solution_name,
+ typeSolution: item.type_solution,
+ pathSolution: item.path_solution,
+ }));
+};
// Dummy data untuk user history
const userHistoryData = [
- { id: 1, name: 'John Doe', phone: '081234567890', status: 'Delivered', timestamp: '04-11-2025 11:40 WIB' },
- { id: 2, name: 'Jane Smith', phone: '087654321098', status: 'Delivered', timestamp: '04-11-2025 11:41 WIB' },
- { id: 3, name: 'Peter Jones', phone: '082345678901', status: 'Delivered', timestamp: '04-11-2025 11:42 WIB' },
+ {
+ id: '1',
+ name: 'John Doe',
+ phone: '081234567890',
+ status: 'Delivered',
+ timestamp: '04-11-2025 11:40 WIB',
+ },
+ {
+ id: '2',
+ name: 'Jane Smith',
+ phone: '087654321098',
+ status: 'Delivered',
+ timestamp: '04-11-2025 11:41 WIB',
+ },
+ {
+ id: '3',
+ name: 'Peter Jones',
+ phone: '082345678901',
+ status: 'Delivered',
+ timestamp: '04-11-2025 11:42 WIB',
+ },
];
// Dummy data untuk log history
@@ -97,16 +101,16 @@ const logHistoryData = [
timestamp: '04-11-2025 11:55 WIB',
addedBy: {
name: 'Budi Santoso',
- phone: '081122334455'
+ phone: '081122334455',
},
- description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.'
+ description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
},
{
id: 2,
timestamp: '04-11-2025 11:45 WIB',
addedBy: {
name: 'John Doe',
- phone: '081234567890'
+ phone: '081234567890',
},
description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
},
@@ -115,15 +119,14 @@ const logHistoryData = [
timestamp: '04-11-2025 11:40 WIB',
addedBy: {
name: 'Jane Smith',
- phone: '087654321098'
+ phone: '087654321098',
},
description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
- }
+ },
];
-
const ListNotification = memo(function ListNotification(props) {
- const [notifications, setNotifications] = useState(initialDeviceNotifications);
+ const [notifications, setNotifications] = useState([]);
const [activeTab, setActiveTab] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null
@@ -131,19 +134,41 @@ const ListNotification = memo(function ListNotification(props) {
const [selectedNotification, setSelectedNotification] = useState(null);
const navigate = useNavigate();
+ // Fetch notifications from API
+ const fetchNotifications = async () => {
+ try {
+ const response = await getAllNotification();
+ if (response && response.data) {
+ const transformedData = transformNotificationData(response.data);
+ setNotifications(transformedData);
+ }
+ } catch (error) {
+ console.error('Error fetching notifications:', error);
+ setNotifications([]);
+ }
+ };
+
useEffect(() => {
const token = localStorage.getItem('token');
if (!token) {
navigate('/signin');
+ return;
}
- }, [navigate]);
+
+ // Fetch notifications on component mount
+ fetchNotifications();
+ }, []);
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' };
+ 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' };
}
};
@@ -156,25 +181,37 @@ const ListNotification = memo(function ListNotification(props) {
cancelText: 'Cancel',
onOk() {
console.log('Resending notification:', notification.id);
- // Di sini Anda bisa menambahkan logika pemanggilan API
- message.success(`Notification for "${notification.title}" has been resent successfully.`);
+
+ message.success(
+ `Notification for "${notification.title}" has been resent successfully.`
+ );
},
onCancel() {
console.log('Resend cancelled');
},
});
};
+
const handleMarkAsRead = (id) => {
setNotifications((prev) =>
- prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
+ prev.map((n) =>
+ n.id === id ? { ...n, isRead: true, type: 'resolved', status: 'Resolved' } : n
+ )
);
};
const filteredNotifications = notifications
- .filter(n => (activeTab === 'all' || (activeTab === 'unread' && !n.isRead) || (activeTab === 'read' && n.isRead)))
- .filter(n => {
+ .filter((n) => {
+ const matchesTab =
+ activeTab === 'all' ||
+ (activeTab === 'unread' && !n.isRead) ||
+ (activeTab === 'read' && n.isRead);
+ return matchesTab;
+ })
+ .filter((n) => {
if (!searchTerm) return true;
- const searchableText = `${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase();
+ const searchableText =
+ `${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase();
return searchableText.includes(searchTerm.toLowerCase());
});
@@ -196,7 +233,9 @@ const ListNotification = memo(function ListNotification(props) {
const renderDeviceNotifications = () => (
{filteredNotifications.length === 0 ? (
- Tidak ada notifikasi
+
+ Tidak ada notifikasi
+
) : (
filteredNotifications.map((notification) => {
const { IconComponent, color, bgColor } = getIconAndColor(notification.type);
@@ -211,47 +250,169 @@ const ListNotification = memo(function ListNotification(props) {
onClick={() => handleMarkAsRead(notification.id)}
>
-
+
-
+
{notification.title}
-
{notification.issue}
+
+
+ {notification.issue}
+
+
- {!notification.isRead &&
}
+ {!notification.isRead && (
+
+ )}
-
-
-
- {notification.description}
+
+
+
{notification.details}
-
- {notification.timestamp}
- {notification.location}
+
- {notification.link}
+
+
+ {notification.timestamp}
+
+
+
+
+
+ {notification.location}
+
+
+
+
+
+ {notification.link}
+
} style={{ paddingLeft: '8px' }}
- onClick={(e) => { e.stopPropagation(); handleResend(notification); }}
+ type="link"
+ icon={}
+ style={{ paddingLeft: '8px' }}
+ onClick={(e) => {
+ e.stopPropagation();
+ handleResend(notification);
+ }}
>
Resend
-
-
- } title="User History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('user'); }} />
- } title="Details" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('details'); setSelectedNotification(notification); }} />
- } title="Log History" style={{ border: '1px solid #1890ff', borderRadius: '4px' }} onClick={(e) => { e.stopPropagation(); setModalContent('log'); }} />
+
+
+
+ }
+ title="User History"
+ style={{
+ border: '1px solid #1890ff',
+ borderRadius: '4px',
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ setModalContent('user');
+ }}
+ />
+
+ }
+ title="Details"
+ style={{
+ border: '1px solid #1890ff',
+ borderRadius: '4px',
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ setModalContent('details');
+ setSelectedNotification(notification);
+ }}
+ />
+
+ }
+ title="Log History"
+ style={{
+ border: '1px solid #1890ff',
+ borderRadius: '4px',
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ setModalContent('log');
+ }}
+ />
@@ -267,25 +428,31 @@ const ListNotification = memo(function ListNotification(props) {
const renderUserHistory = () => (
<>
- {userHistoryData.map(user => (
+ {userHistoryData.map((user) => (
{user.name}
|
- {user.phone}
+
+ {user.phone}
+
|
- Success Delivered at {user.timestamp}
+
+ Success Delivered at {user.timestamp}
+
- }>Resend
+ }>
+ Resend
+
@@ -298,19 +465,45 @@ const ListNotification = memo(function ListNotification(props) {
<>
{/* Garis vertikal yang menyambung */}
-
+
{logHistoryData.map((log, index) => (
-
+
{/* Kolom Kiri: Branch/Timeline */}
-
-
+
+
{/* Kolom Kanan: Card */}
@@ -321,18 +514,35 @@ const ListNotification = memo(function ListNotification(props) {
- Added at {log.timestamp}
+
+ Added at {log.timestamp}
+
Added by: {log.addedBy.name}
-
+
{log.addedBy.phone}
-
+
{log.description}
@@ -355,48 +565,79 @@ const ListNotification = memo(function ListNotification(props) {
{/* Kolom Kiri: Data Kompresor */}
-
+
-
+
+
+
{selectedNotification.title}
- {selectedNotification.issue}
+
+
+ {selectedNotification.issue}
+
+
Plant Subsection
{selectedNotification.subsection}
-
Time
+
+ Time
+
{selectedNotification.timestamp.split(' ')[1]} WIB
-
+
- Value
- {selectedNotification.value.includes('°') ? selectedNotification.value : `${selectedNotification.value}° C`}
+
+ Value
+
+
+ N/A
+
- Treshold
- {selectedNotification.threshold.includes('°') ? selectedNotification.threshold : `${selectedNotification.threshold}° C`}
+
+ Treshold
+
+
+ N/A
+
@@ -414,11 +655,23 @@ const ListNotification = memo(function ListNotification(props) {
Status
-
{selectedNotification.status}
+
+ {selectedNotification.status}
+
Tag
-
{selectedNotification.tag}
+
+ {selectedNotification.tag}
+
@@ -427,26 +680,59 @@ const ListNotification = memo(function ListNotification(props) {
-
+
- Handling Guideline
+
+ Handling Guideline
+
-
+
- Spare Part
+
+ Spare Part
+
- setModalContent('log')}>
+ setModalContent('log')}
+ >
-
- Log Activity
+
+
+ Log Activity
+
@@ -456,56 +742,125 @@ const ListNotification = memo(function ListNotification(props) {
PDF}
+ size="small"
+ bodyStyle={{ padding: '8px 12px' }}
+ hoverable
+ extra={
+
+ PDF
+
+ }
>
-
+
-
- Error 303.pdf
+
+ {' '}
+ Error 303.pdf
-
+
lihat disini
PDF}
+ size="small"
+ bodyStyle={{ padding: '8px 12px' }}
+ hoverable
+ extra={
+
+ PDF
+
+ }
>
-
+
-
- Error 303.pdf
+
+ {' '}
+ Error 303.pdf
-
+
lihat disini
PDF}
+ size="small"
+ bodyStyle={{ padding: '8px 12px' }}
+ hoverable
+ extra={
+
+ PDF
+
+ }
>
-
+
-
- Error 303.pdf
+
+ {' '}
+ Error 303.pdf
-
+
lihat disini
Text}
+ size="small"
+ bodyStyle={{ padding: '8px 12px' }}
+ extra={
+
+ Text
+
+ }
>
- Error 303: Sensor suhu tidak merespon. Lakukan reset manual pada panel kontrol.
+ Error 303: Sensor suhu tidak
+ merespon. Lakukan reset manual pada panel kontrol.
@@ -517,19 +872,64 @@ const ListNotification = memo(function ListNotification(props) {
-
-
+
+
-
Available
+
+ Available
+
-
+
Air Filter
-
- Filters incoming air to remove dust and impurities before compression.
+
+ Filters incoming air to remove dust and
+ impurities before compression.
-
- Part No: AF-2024-X2 | Condition: New | Compatible with Model XR-60
+
+ Part No: AF-2024-X2 | Condition: New |
+ Compatible with Model XR-60
@@ -538,19 +938,64 @@ const ListNotification = memo(function ListNotification(props) {
-
-
+
+
-
Not Available
+
+ Not Available
+
-
+
Compressor Oil
-
- Special synthetic oil to lubricate and cool down the compressor unit.
+
+ Special synthetic oil to lubricate and cool
+ down the compressor unit.
-
- Part No: OL-SYN-550 | Condition: New | Compatible with Model XR-60
+
+ Part No: OL-SYN-550 | Condition: New |
+ Compatible with Model XR-60
@@ -562,29 +1007,68 @@ const ListNotification = memo(function ListNotification(props) {
-
-
+
+
{isAddingLog && (
<>
- Add New Log / Update Progress
-
+
+ Add New Log / Update Progress
+
+
>
)}
- } onClick={() => setIsAddingLog(!isAddingLog)}>
- {isAddingLog ? "Submit Log" : "Add Log"}
+ }
+ onClick={() => setIsAddingLog(!isAddingLog)}
+ >
+ {isAddingLog ? 'Submit Log' : 'Add Log'}
- {logHistoryData.slice(0, 2).map(log => ( // Menampilkan 2 log terbaru sebagai pratinjau
-
-
- {log.addedBy.name}: {log.description}
-
- {log.timestamp}
-
- ))}
+ {logHistoryData.slice(0, 2).map(
+ (
+ log // Menampilkan 2 log terbaru sebagai pratinjau
+ ) => (
+
+
+ {log.addedBy.name}:{' '}
+ {log.description}
+
+
+ {log.timestamp}
+
+
+ )
+ )}
-
@@ -602,31 +1086,67 @@ const ListNotification = memo(function ListNotification(props) {
- Notification
+
+ Notification
+
Riwayat notifikasi yang dikirim ke engineer
-
+
setSearchTerm(e.target.value)}
- style={{ width: 300 }}
+ style={{ width: 300 }}
/>
-
-
- setActiveTab('all')} style={tabButtonStyle(activeTab === 'all')}>All
- setActiveTab('unread')} style={{ ...tabButtonStyle(activeTab === 'unread'), display: 'flex', alignItems: 'center', gap: '8px' }}>
- Not read yet
- {getUnreadCount() > 0 && }
-
- setActiveTab('read')} style={tabButtonStyle(activeTab === 'read')}>Already read
-
+
+
+ setActiveTab('all')}
+ style={tabButtonStyle(activeTab === 'all')}
+ >
+ All
+
+ setActiveTab('unread')}
+ style={{
+ ...tabButtonStyle(activeTab === 'unread'),
+ display: 'flex',
+ alignItems: 'center',
+ gap: '8px',
+ }}
+ >
+ Not read yet
+ {getUnreadCount() > 0 && (
+
+ )}
+
+ setActiveTab('read')}
+ style={tabButtonStyle(activeTab === 'read')}
+ >
+ Already read
+
+
{renderDeviceNotifications()}
@@ -637,7 +1157,15 @@ const ListNotification = memo(function ListNotification(props) {
title={
{modalContent === 'details' ? (
-
+
Error Notification Detail
@@ -663,14 +1191,20 @@ const ListNotification = memo(function ListNotification(props) {
width={modalContent === 'details' ? 900 : 700}
footer={
- {
- setModalContent(null);
- setSelectedNotification(null);
- // Reset state isAddingLog saat modal ditutup
- if (modalContent === 'details') {
- setIsAddingLog(false);
- }
- }}>Close
+ {
+ setModalContent(null);
+ setSelectedNotification(null);
+ // Reset state isAddingLog saat modal ditutup
+ if (modalContent === 'details') {
+ setIsAddingLog(false);
+ }
+ }}
+ >
+ Close
+
}
>
@@ -682,4 +1216,4 @@ const ListNotification = memo(function ListNotification(props) {
);
});
-export default ListNotification;
\ No newline at end of file
+export default ListNotification;