lavoce #23
@@ -1,9 +1,21 @@
|
|||||||
import { SendRequest } from '../components/Global/ApiRequest';
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
export const getAllNotification = async () => {
|
const getAllNotification = async (queryParams) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
prefix: 'notification',
|
prefix: `notification?${queryParams.toString()}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNotificationById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `notification/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllNotification, getNotificationById };
|
||||||
|
|||||||
@@ -146,21 +146,43 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Fetch notifications from API
|
// Fetch notifications from API
|
||||||
const fetchNotifications = async () => {
|
const fetchNotifications = async (page = 1, limit = 10, isRead = null) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await getAllNotification();
|
const queryParams = new URLSearchParams({
|
||||||
|
page: page.toString(),
|
||||||
|
limit: limit.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isRead !== null) {
|
||||||
|
queryParams.append('is_read', isRead.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await getAllNotification(queryParams);
|
||||||
if (response && response.data) {
|
if (response && response.data) {
|
||||||
const transformedData = transformNotificationData(response.data);
|
const transformedData = transformNotificationData(response.data);
|
||||||
setNotifications(transformedData);
|
setNotifications(transformedData);
|
||||||
|
|
||||||
// Update pagination with mock data (since API doesn't provide pagination info)
|
// Update pagination with API response or calculate from data
|
||||||
const totalItems = transformedData.length;
|
if (response.paging) {
|
||||||
setPagination((prev) => ({
|
setPagination({
|
||||||
...prev,
|
current_page: response.paging.current_page || page,
|
||||||
total_limit: totalItems,
|
current_limit: response.paging.current_limit || limit,
|
||||||
total_page: Math.ceil(totalItems / prev.current_limit),
|
total_limit: response.paging.total_limit || transformedData.length,
|
||||||
}));
|
total_page:
|
||||||
|
response.paging.total_page || Math.ceil(transformedData.length / limit),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback: calculate pagination from data
|
||||||
|
const totalItems = transformedData.length;
|
||||||
|
setPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
current_page: page,
|
||||||
|
current_limit: limit,
|
||||||
|
total_limit: totalItems,
|
||||||
|
total_page: Math.ceil(totalItems / limit),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching notifications:', error);
|
console.error('Error fetching notifications:', error);
|
||||||
@@ -178,13 +200,10 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
current_page: page,
|
current_page: page,
|
||||||
current_limit: pageSize,
|
current_limit: pageSize,
|
||||||
}));
|
}));
|
||||||
};
|
|
||||||
|
|
||||||
// Get paginated notifications
|
// Fetch notifications with new pagination
|
||||||
const getPaginatedNotifications = () => {
|
const isReadFilter = activeTab === 'read' ? true : activeTab === 'unread' ? false : null;
|
||||||
const startIndex = (pagination.current_page - 1) * pagination.current_limit;
|
fetchNotifications(page, pageSize, isReadFilter);
|
||||||
const endIndex = startIndex + pagination.current_limit;
|
|
||||||
return filteredNotifications.slice(startIndex, endIndex);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -194,9 +213,10 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch notifications on component mount
|
// Fetch notifications on component mount and when tab changes
|
||||||
fetchNotifications();
|
const isReadFilter = activeTab === 'read' ? true : activeTab === 'unread' ? false : null;
|
||||||
}, []);
|
fetchNotifications(pagination.current_page, pagination.current_limit, isReadFilter);
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
const getIconAndColor = (type) => {
|
const getIconAndColor = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -248,22 +268,17 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredNotifications = notifications
|
const getUnreadCount = () => notifications.filter((n) => !n.isRead).length;
|
||||||
.filter((n) => {
|
|
||||||
const matchesTab =
|
// Filter notifications based on search term
|
||||||
activeTab === 'all' ||
|
const getFilteredNotifications = () => {
|
||||||
(activeTab === 'unread' && !n.isRead) ||
|
if (!searchTerm) return notifications;
|
||||||
(activeTab === 'read' && n.isRead);
|
// Search by title and error code name
|
||||||
return matchesTab;
|
return notifications.filter((n) => {
|
||||||
})
|
|
||||||
.filter((n) => {
|
|
||||||
if (!searchTerm) return true;
|
|
||||||
// Search by title and error code name
|
|
||||||
const searchableText = `${n.title} ${n.issue}`.toLowerCase();
|
const searchableText = `${n.title} ${n.issue}`.toLowerCase();
|
||||||
return searchableText.includes(searchTerm.toLowerCase());
|
return searchableText.includes(searchTerm.toLowerCase());
|
||||||
});
|
});
|
||||||
|
};
|
||||||
const getUnreadCount = () => notifications.filter((n) => !n.isRead).length;
|
|
||||||
|
|
||||||
const tabButtonStyle = (isActive) => ({
|
const tabButtonStyle = (isActive) => ({
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
@@ -279,8 +294,7 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const renderDeviceNotifications = () => {
|
const renderDeviceNotifications = () => {
|
||||||
const paginatedNotifications = getPaginatedNotifications();
|
const filteredNotifications = getFilteredNotifications();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
||||||
{filteredNotifications.length === 0 ? (
|
{filteredNotifications.length === 0 ? (
|
||||||
@@ -288,200 +302,215 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
Tidak ada notifikasi
|
Tidak ada notifikasi
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
paginatedNotifications.map((notification) => {
|
filteredNotifications.map((notification) => {
|
||||||
const { IconComponent, color, bgColor } = getIconAndColor(notification.type);
|
const { IconComponent, color, bgColor } = getIconAndColor(
|
||||||
return (
|
notification.type
|
||||||
<Card
|
);
|
||||||
key={notification.id}
|
return (
|
||||||
style={{
|
<Card
|
||||||
backgroundColor: notification.isRead ? '#ffffff' : '#f6f9ff',
|
key={notification.id}
|
||||||
borderColor: notification.isRead ? '#f0f0f0' : '#d6e4ff',
|
style={{
|
||||||
cursor: 'pointer',
|
backgroundColor: notification.isRead ? '#ffffff' : '#f6f9ff',
|
||||||
}}
|
borderColor: notification.isRead ? '#f0f0f0' : '#d6e4ff',
|
||||||
onClick={() => handleMarkAsRead(notification.id)}
|
cursor: 'pointer',
|
||||||
>
|
}}
|
||||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>
|
onClick={() => handleMarkAsRead(notification.id)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: '40px',
|
|
||||||
height: '40px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: bgColor,
|
|
||||||
color: color,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
gap: '16px',
|
||||||
justifyContent: 'center',
|
alignItems: 'flex-start',
|
||||||
fontSize: '22px',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconComponent style={{ fontSize: '22px' }} />
|
<div
|
||||||
</div>
|
style={{
|
||||||
<div style={{ flex: 1 }}>
|
width: '40px',
|
||||||
<Row align="top">
|
height: '40px',
|
||||||
<Col flex="220px">
|
borderRadius: '50%',
|
||||||
<div
|
backgroundColor: bgColor,
|
||||||
style={{
|
color: color,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
alignItems: 'center',
|
||||||
alignItems: 'flex-start',
|
justifyContent: 'center',
|
||||||
}}
|
fontSize: '22px',
|
||||||
>
|
flexShrink: 0,
|
||||||
<div>
|
}}
|
||||||
<Text strong>{notification.title}</Text>
|
>
|
||||||
<div style={{ marginTop: '4px' }}>
|
<IconComponent style={{ fontSize: '22px' }} />
|
||||||
<Text style={{ color }}>
|
</div>
|
||||||
{notification.issue}
|
<div style={{ flex: 1 }}>
|
||||||
</Text>
|
<Row align="top">
|
||||||
|
<Col flex="220px">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Text strong>{notification.title}</Text>
|
||||||
|
<div style={{ marginTop: '4px' }}>
|
||||||
|
<Text style={{ color }}>
|
||||||
|
{notification.issue}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!notification.isRead && (
|
||||||
|
<Badge
|
||||||
|
color="red"
|
||||||
|
status="processing"
|
||||||
|
style={{
|
||||||
|
marginLeft: '8px',
|
||||||
|
marginTop: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!notification.isRead && (
|
</Col>
|
||||||
<Badge
|
<Col flex="auto">
|
||||||
color="red"
|
<div
|
||||||
status="processing"
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MailOutlined
|
||||||
style={{
|
style={{
|
||||||
marginLeft: '8px',
|
|
||||||
marginTop: '4px',
|
marginTop: '4px',
|
||||||
|
color: '#1890ff',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
<Paragraph
|
||||||
</div>
|
style={{
|
||||||
</Col>
|
color: '#595959',
|
||||||
<Col flex="auto">
|
margin: 0,
|
||||||
<div
|
flex: 1,
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: '8px',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginBottom: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MailOutlined
|
|
||||||
style={{ marginTop: '4px', color: '#1890ff' }}
|
|
||||||
/>
|
|
||||||
<Paragraph
|
|
||||||
style={{ color: '#595959', margin: 0, flex: 1 }}
|
|
||||||
>
|
|
||||||
{notification.details}
|
|
||||||
</Paragraph>
|
|
||||||
</div>
|
|
||||||
<Space
|
|
||||||
direction="vertical"
|
|
||||||
size={4}
|
|
||||||
style={{ fontSize: '13px', color: '#8c8c8c' }}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
<ClockCircleOutlined />
|
|
||||||
<Text type="secondary">
|
|
||||||
{notification.timestamp}
|
|
||||||
</Text>
|
|
||||||
</Space>
|
|
||||||
<Space>
|
|
||||||
<EnvironmentOutlined />
|
|
||||||
<Text type="secondary">
|
|
||||||
{notification.location}
|
|
||||||
</Text>
|
|
||||||
</Space>
|
|
||||||
<Space>
|
|
||||||
<LinkOutlined />
|
|
||||||
<AntdLink
|
|
||||||
href={notification.link}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{notification.link}
|
|
||||||
</AntdLink>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon={<SendOutlined />}
|
|
||||||
style={{ paddingLeft: '8px' }}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleResend(notification);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Resend
|
{notification.details}
|
||||||
</Button>
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size={4}
|
||||||
|
style={{ fontSize: '13px', color: '#8c8c8c' }}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<ClockCircleOutlined />
|
||||||
|
<Text type="secondary">
|
||||||
|
{notification.timestamp}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<EnvironmentOutlined />
|
||||||
|
<Text type="secondary">
|
||||||
|
{notification.location}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<LinkOutlined />
|
||||||
|
<AntdLink
|
||||||
|
href={notification.link}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{notification.link}
|
||||||
|
</AntdLink>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<SendOutlined />}
|
||||||
|
style={{ paddingLeft: '8px' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleResend(notification);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Resend
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Col>
|
||||||
</Col>
|
<Col
|
||||||
<Col
|
flex="120px"
|
||||||
flex="120px"
|
style={{ textAlign: 'center' }}
|
||||||
style={{ textAlign: 'center' }}
|
align="bottom"
|
||||||
align="bottom"
|
|
||||||
>
|
|
||||||
<Space
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Space
|
||||||
type="text"
|
|
||||||
icon={
|
|
||||||
<UserOutlined
|
|
||||||
style={{ color: '#1890ff' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title="User History"
|
|
||||||
style={{
|
style={{
|
||||||
border: '1px solid #1890ff',
|
display: 'flex',
|
||||||
borderRadius: '4px',
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setModalContent('user');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RouterLink
|
|
||||||
to={`/detail-notification/${
|
|
||||||
notification.id.split('-')[1]
|
|
||||||
}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={
|
icon={
|
||||||
<EyeOutlined
|
<UserOutlined
|
||||||
style={{ color: '#1890ff' }}
|
style={{ color: '#1890ff' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title="Details"
|
title="User History"
|
||||||
style={{
|
style={{
|
||||||
border: '1px solid #1890ff',
|
border: '1px solid #1890ff',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
}}
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setModalContent('user');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</RouterLink>
|
<RouterLink
|
||||||
<Button
|
to={`/detail-notification/${
|
||||||
type="text"
|
notification.id.split('-')[1]
|
||||||
icon={
|
}`}
|
||||||
<HistoryOutlined
|
target="_blank"
|
||||||
style={{ color: '#1890ff' }}
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={
|
||||||
|
<EyeOutlined
|
||||||
|
style={{ color: '#1890ff' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title="Details"
|
||||||
|
style={{
|
||||||
|
border: '1px solid #1890ff',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
</RouterLink>
|
||||||
title="Log History"
|
<Button
|
||||||
style={{
|
type="text"
|
||||||
border: '1px solid #1890ff',
|
icon={
|
||||||
borderRadius: '4px',
|
<HistoryOutlined
|
||||||
}}
|
style={{ color: '#1890ff' }}
|
||||||
onClick={(e) => {
|
/>
|
||||||
e.stopPropagation();
|
}
|
||||||
setModalContent('log');
|
title="Log History"
|
||||||
}}
|
style={{
|
||||||
/>
|
border: '1px solid #1890ff',
|
||||||
</Space>
|
borderRadius: '4px',
|
||||||
</Col>
|
}}
|
||||||
</Row>
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setModalContent('log');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Card>
|
);
|
||||||
);
|
})
|
||||||
})
|
)}
|
||||||
)}
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1212,16 +1241,15 @@ const ListNotification = memo(function ListNotification(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>{renderDeviceNotifications()}</Spin>
|
||||||
{renderDeviceNotifications()}
|
|
||||||
</Spin>
|
|
||||||
|
|
||||||
{/* PAGINATION */}
|
{/* PAGINATION */}
|
||||||
<Row justify="space-between" align="middle" style={{ marginTop: '16px' }}>
|
<Row justify="space-between" align="middle" style={{ marginTop: '16px' }}>
|
||||||
<Col>
|
<Col>
|
||||||
<div>
|
<div>
|
||||||
Menampilkan {pagination.current_limit} data halaman{' '}
|
Menampilkan {pagination.current_limit} data halaman{' '}
|
||||||
{pagination.current_page} dari total {pagination.total_limit} data
|
{pagination.current_page} dari total {pagination.total_limit}{' '}
|
||||||
|
data
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
|
|||||||
Reference in New Issue
Block a user