feat: enhance notification fetching with pagination and filtering options

This commit is contained in:
2025-11-26 11:44:50 +07:00
parent 309d191bce
commit 14f8a5d472
2 changed files with 242 additions and 202 deletions

View File

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