diff --git a/src/api/notification.jsx b/src/api/notification.jsx new file mode 100644 index 0000000..9aab0c5 --- /dev/null +++ b/src/api/notification.jsx @@ -0,0 +1,9 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +export const getAllNotification = async () => { + const response = await SendRequest({ + method: 'get', + prefix: 'notification', + }); + return response.data; +}; diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index 8366cef..67fda5e 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -1,6 +1,18 @@ import { useEffect, useState } from 'react'; import { useNavigate, useParams, useLocation } from 'react-router-dom'; -import { Divider, Typography, Button, Steps, Form, Row, Col, Card, Spin, Modal, ConfigProvider } from 'antd'; +import { + Divider, + Typography, + Button, + Steps, + Form, + Row, + Col, + Card, + Spin, + Modal, + ConfigProvider, +} from 'antd'; import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { getBrandById, updateBrand } from '../../../api/master-brand'; @@ -48,12 +60,10 @@ const EditBrandDevice = () => { const [solutionForm] = Form.useForm(); const [sparepartForm] = Form.useForm(); - const { - errorCodeFields, - addErrorCode, - removeErrorCode, - editErrorCode, - } = useErrorCodeLogic(errorCodeForm, fileList); + const { errorCodeFields, addErrorCode, removeErrorCode, editErrorCode } = useErrorCodeLogic( + errorCodeForm, + fileList + ); const { solutionFields, @@ -83,14 +93,14 @@ const EditBrandDevice = () => { // Handlers for sparepart image upload const handleSparepartImageUpload = (fieldKey, imageData) => { - setSparepartImages(prev => ({ + setSparepartImages((prev) => ({ ...prev, - [fieldKey]: imageData + [fieldKey]: imageData, })); }; const handleSparepartImageRemove = (fieldKey) => { - setSparepartImages(prev => { + setSparepartImages((prev) => { const newImages = { ...prev }; delete newImages[fieldKey]; return newImages; @@ -159,17 +169,19 @@ const EditBrandDevice = () => { path_icon: ec.path_icon || '', status: ec.is_active, solution: ec.solution || [], - errorCodeIcon: ec.path_icon ? { - name: 'icon', - uploadPath: ec.path_icon, - url: (() => { - const pathParts = ec.path_icon.split('/'); - const folder = pathParts[0]; - const filename = pathParts.slice(1).join('/'); - return getFileUrl(folder, filename); - })(), - type_solution: 'image' - } : null, + errorCodeIcon: ec.path_icon + ? { + name: 'icon', + uploadPath: ec.path_icon, + url: (() => { + const pathParts = ec.path_icon.split('/'); + const folder = pathParts[0]; + const filename = pathParts.slice(1).join('/'); + return getFileUrl(folder, filename); + })(), + type_solution: 'image', + } + : null, })) : []; @@ -332,7 +344,7 @@ const EditBrandDevice = () => { // Load sparepart images const newSparepartImages = {}; - record.sparepart.forEach(sparepart => { + record.sparepart.forEach((sparepart) => { if (sparepart.sparepart_image) { newSparepartImages[sparepart.id || sparepart.key] = sparepart.sparepart_image; } @@ -367,7 +379,7 @@ const EditBrandDevice = () => { // Load sparepart images const newSparepartImages = {}; - record.sparepart.forEach(sparepart => { + record.sparepart.forEach((sparepart) => { if (sparepart.sparepart_image) { newSparepartImages[sparepart.id || sparepart.key] = sparepart.sparepart_image; } @@ -381,34 +393,81 @@ const EditBrandDevice = () => { } }; - const handleAddErrorCode = (newErrorCode) => { - // Include the current icon in the error code - const errorCodeWithIcon = { - ...newErrorCode, - errorCodeIcon: errorCodeIcon - }; + const handleAddErrorCode = async () => { + try { + // Validate error code form + const errorCodeValues = await errorCodeForm.validateFields(); - let updatedErrorCodes; - if (editingErrorCodeKey) { - updatedErrorCodes = errorCodes.map((item) => - item.key === editingErrorCodeKey ? errorCodeWithIcon : item - ); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil diupdate!', - }); - } else { - updatedErrorCodes = [...errorCodes, errorCodeWithIcon]; - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: 'Error code berhasil ditambahkan!', + // Get solution data from solution form + const solutionData = getSolutionData(); + + // Get sparepart data from sparepart form + const sparepartData = getSparepartData(); + + if (solutionData.length === 0) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Setiap error code harus memiliki minimal 1 solution!', + }); + return; + } + + // Create complete error code object + const newErrorCode = { + error_code: errorCodeValues.error_code, + error_code_name: errorCodeValues.error_code_name, + error_code_description: errorCodeValues.error_code_description, + error_code_color: errorCodeValues.error_code_color || '#000000', + path_icon: errorCodeIcon?.uploadPath || '', + status: errorCodeValues.status === undefined ? true : errorCodeValues.status, + solution: solutionData, + sparepart: sparepartData, + errorCodeIcon: errorCodeIcon, + key: editingErrorCodeKey || `temp-${Date.now()}`, + }; + + let updatedErrorCodes; + if (editingErrorCodeKey) { + // Update existing error code + updatedErrorCodes = errorCodes.map((item) => { + if (item.key === editingErrorCodeKey) { + return { + ...item, + ...newErrorCode, + error_code_id: item.error_code_id || newErrorCode.error_code_id, + }; + } + return item; + }); + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Error code berhasil diupdate!', + }); + } else { + // Add new error code + updatedErrorCodes = [...errorCodes, newErrorCode]; + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Error code berhasil ditambahkan!', + }); + } + + setErrorCodes(updatedErrorCodes); + + // Delay form reset to prevent data loss + setTimeout(() => { + resetErrorCodeForm(); + }, 100); + } catch (error) { + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Harap isi semua kolom wajib (error code + minimal 1 solution)!', }); } - - setErrorCodes(updatedErrorCodes); - resetErrorCodeForm(); }; const resetErrorCodeForm = () => { @@ -552,7 +611,11 @@ const EditBrandDevice = () => { Solutions} + title={ + + Solutions + + } size="small" >
{ Spareparts} + title={ + + Spareparts + + } size="small" > { diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx index b21df9e..4128dfb 100644 --- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -157,12 +157,12 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { icon: 'question', title: 'Konfirmasi', message: 'Apakah anda yakin hapus data "' + param.brand_name + '" ?', - onConfirm: () => handleDelete(param.brand_id), + onConfirm: () => handleDelete(param.brand_id, param.brand_name), onCancel: () => {}, }); }; - const handleDelete = async (brand_id) => { + const handleDelete = async (brand_id, brand_name) => { try { const response = await deleteBrand(brand_id); @@ -170,7 +170,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { NotifOk({ icon: 'success', title: 'Berhasil', - message: response.message || 'Data Brand Device berhasil dihapus.', + message: `Brand ${brand_name} deleted successfully.`, }); doFilter(); // Refresh data } else { @@ -278,4 +278,4 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { ); }); -export default ListBrandDevice; \ No newline at end of file +export default ListBrandDevice; diff --git a/src/pages/master/brandDevice/component/SparepartForm.jsx b/src/pages/master/brandDevice/component/SparepartForm.jsx index a6c70a1..696dbc8 100644 --- a/src/pages/master/brandDevice/component/SparepartForm.jsx +++ b/src/pages/master/brandDevice/component/SparepartForm.jsx @@ -1,5 +1,16 @@ import React, { useState, useEffect } from 'react'; -import { Form, Input, Button, Divider, Typography, Switch, Space, Card, Upload, message } from 'antd'; +import { + Form, + Input, + Button, + Divider, + Typography, + Switch, + Space, + Card, + Upload, + message, +} from 'antd'; import { PlusOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons'; import { uploadFile } from '../../../../api/file-uploads'; @@ -15,7 +26,7 @@ const SparepartForm = ({ onSparepartImageUpload, onSparepartImageRemove, sparepartImages = {}, - isReadOnly = false + isReadOnly = false, }) => { const [fieldStatuses, setFieldStatuses] = useState({}); @@ -32,7 +43,7 @@ const SparepartForm = ({ useEffect(() => { // Update field statuses when form changes const newStatuses = {}; - sparepartFields.forEach(field => { + sparepartFields.forEach((field) => { newStatuses[field.key] = getFieldValue(field.key); }); setFieldStatuses(newStatuses); @@ -55,21 +66,25 @@ const SparepartForm = ({ try { const fileExtension = file.name.split('.').pop().toLowerCase(); - const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExtension); + const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes( + fileExtension + ); const fileType = isImageFile ? 'image' : 'pdf'; const folder = 'images'; const uploadResponse = await uploadFile(file, folder); - const imagePath = uploadResponse.data?.path_icon || uploadResponse.data?.path_solution || ''; + const imagePath = + uploadResponse.data?.path_icon || uploadResponse.data?.path_solution || ''; if (imagePath) { - onSparepartImageUpload && onSparepartImageUpload(fieldKey, { - name: file.name, - uploadPath: imagePath, - fileExtension, - isImage: isImageFile, - size: file.size, - }); + onSparepartImageUpload && + onSparepartImageUpload(fieldKey, { + name: file.name, + uploadPath: imagePath, + fileExtension, + isImage: isImageFile, + size: file.size, + }); message.success(`${file.name} uploaded successfully!`); } else { message.error(`Failed to upload ${file.name}`); @@ -106,48 +121,40 @@ const SparepartForm = ({ size="small" style={{ marginBottom: 16 }} title={ -
+
Sparepart {index + 1}
- - { - onSparepartStatusChange && onSparepartStatusChange(field.key, checked); - setFieldStatuses(prev => ({ ...prev, [field.key]: checked })); - }} - style={{ - backgroundColor: fieldStatuses[field.key] ? '#23A55A' : '#bfbfbf' - }} - /> - - - {fieldStatuses[field.key] ? 'Active' : 'Inactive'} - + {!isReadOnly && sparepartFields.length > 1 && ( +
+
+ )}
} > - + {/* Sparepart Name */} - + {/* Description */} - + ) : ( -
+
No upload allowed
)} {sparepartImages[field.key] && (
-
+
Sparepart Image
- {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 && ( -
- -
- )} ))} @@ -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} + - - - + @@ -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 + + )} - - {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 }} /> -
-
- - - -
+
+
+ + +
+
{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={
- +
} > @@ -682,4 +1216,4 @@ const ListNotification = memo(function ListNotification(props) { ); }); -export default ListNotification; \ No newline at end of file +export default ListNotification;