continue: add pagination for notification page and some featur
This commit is contained in:
@@ -47,7 +47,7 @@ export async function fetchActiveNotifications() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllNotifications() {
|
||||
export async function fetchAllNotifications(page: number = 0, size: number = 10) {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
@@ -59,11 +59,11 @@ export async function fetchAllNotifications() {
|
||||
message_body: {
|
||||
action: 'list',
|
||||
authToken: token,
|
||||
page: 0,
|
||||
size: 10,
|
||||
page: page,
|
||||
size: size,
|
||||
sortBy: "createdAt",
|
||||
sortDirection: "DESC",
|
||||
types: [], //"SEARCH_RESULT", "MONITORING_RESULT"
|
||||
types: [], //"SEARCH_RESULT", "MONITORING_RESULT"
|
||||
statuses: [] //"NEW"
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -3,20 +3,24 @@ import { fetchAllNotifications, NotificationBody } from '@/app/actions/notificat
|
||||
|
||||
export interface UseAllNotifications {
|
||||
notifications: NotificationBody[],
|
||||
unreadCount: number
|
||||
currentPage: number,
|
||||
totalElements: number,
|
||||
totalPages: number,
|
||||
}
|
||||
|
||||
export const useAllNotifications = () => {
|
||||
export const useAllNotifications = (page: number, size: number = 10) => {
|
||||
return useQuery({
|
||||
queryKey: ['allNotifications'],
|
||||
queryKey: ['allNotifications', page, size],
|
||||
queryFn: () => {
|
||||
return fetchAllNotifications()
|
||||
return fetchAllNotifications(page, size)
|
||||
},
|
||||
select: (data: UseAllNotifications | null) => {
|
||||
if (!data) {
|
||||
return {
|
||||
notifications: [],
|
||||
unreadCount: 0
|
||||
currentPage: 0,
|
||||
totalElements: 0,
|
||||
totalPages: 0,
|
||||
}
|
||||
}
|
||||
return data;
|
||||
|
||||
@@ -4735,65 +4735,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.arrow {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border: 2px solid v.$border-color-1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px #0000000d;
|
||||
color: v.$p-color;
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: v.$bg-light;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-pages {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
button {
|
||||
border: 2px solid v.$border-color-1;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
color: v.$p-color;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background-color: v.$p-color;
|
||||
color: v.$white;
|
||||
}
|
||||
|
||||
&.other {
|
||||
&:hover {
|
||||
background-color: v.$bg-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.violation-page-note {
|
||||
margin-top: auto;
|
||||
margin-bottom: 0;
|
||||
@@ -5117,6 +5058,7 @@
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
max-width: calc(100vw - var(--side-bar-width) - 60px);
|
||||
background-color: v.$white;
|
||||
|
||||
@media (max-width: 440px) {
|
||||
max-width: calc(100vw - var(--side-bar-width) - 30px);
|
||||
@@ -5247,6 +5189,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
padding: 10px;
|
||||
border-top: 1px solid v.$b-color-2;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-user-cards {
|
||||
@@ -5374,4 +5321,63 @@
|
||||
color: #9ca3af;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.arrow {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border: 2px solid v.$border-color-1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px #0000000d;
|
||||
color: v.$p-color;
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: v.$bg-light;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-pages {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
button {
|
||||
border: 2px solid v.$border-color-1;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
color: v.$p-color;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background-color: v.$p-color;
|
||||
color: v.$white;
|
||||
}
|
||||
|
||||
&.other {
|
||||
&:hover {
|
||||
background-color: v.$bg-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,6 @@ export default function NotificationsButton() {
|
||||
const { data: notificationList } = useActiveNotifications();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(notificationList);
|
||||
}, [notificationList])
|
||||
|
||||
useClickOutside(
|
||||
menuRef,
|
||||
() => {
|
||||
|
||||
@@ -1,14 +1,59 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { IconCheck } from '@/app/ui/icons/icons';
|
||||
import { IconCheck, IconArrowLeft, IconArrowRight, IconDoubleArrowLeft, IconDoubleArrowRight } from '@/app/ui/icons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAllNotifications } from '@/app/hooks/react-query/useAllNotificationsList';
|
||||
import { notificationsMarkAsRead } from '@/app/actions/notificationActions';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export function NotificationsList() {
|
||||
const t = useTranslations('Global');
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
|
||||
const { data: allNotifications } = useAllNotifications();
|
||||
const [loadingIds, setLoadingIds] = useState<Set<number>>(new Set());
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [pageSize] = useState(10);
|
||||
const { data: allNotifications, isLoading } = useAllNotifications(currentPage, pageSize);
|
||||
|
||||
|
||||
const totalPages = allNotifications?.totalPages || 0;
|
||||
const currentPageNum = allNotifications?.currentPage || 0;
|
||||
|
||||
const goToFirstPage = () => setCurrentPage(0);
|
||||
const goToPrevPage = () => setCurrentPage(prev => Math.max(0, prev - 1));
|
||||
const goToNextPage = () => setCurrentPage(prev => Math.min(totalPages - 1, prev + 1));
|
||||
const goToLastPage = () => setCurrentPage(totalPages - 1);
|
||||
const goToPage = (page: number) => setCurrentPage(page);
|
||||
|
||||
const getPageNumbers = (): (number | string)[] => {
|
||||
const delta: number = 2;
|
||||
const range: number[] = [];
|
||||
const rangeWithDots: (number | string)[] = [];
|
||||
let l: number | undefined;
|
||||
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
if (i === 0 || i === totalPages - 1 || (i >= currentPageNum - delta && i <= currentPageNum + delta)) {
|
||||
range.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
range.forEach((i: number) => {
|
||||
if (l !== undefined) {
|
||||
if (i - l === 2) {
|
||||
rangeWithDots.push(l + 1);
|
||||
} else if (i - l !== 1) {
|
||||
rangeWithDots.push('...');
|
||||
}
|
||||
}
|
||||
rangeWithDots.push(i);
|
||||
l = i;
|
||||
});
|
||||
|
||||
return rangeWithDots;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(allNotifications);
|
||||
@@ -35,6 +80,46 @@ export function NotificationsList() {
|
||||
setSelectedFiles(new Set(allNotifications?.notifications.map(item => item.id)));
|
||||
}
|
||||
|
||||
async function markAsReadHandler(ids: number[]) {
|
||||
setLoadingIds(prev => {
|
||||
const newSet = new Set(prev);
|
||||
ids.forEach(id => newSet.add(id));
|
||||
return newSet;
|
||||
});
|
||||
|
||||
try {
|
||||
/* const response = await notificationsMarkAsRead(ids);
|
||||
|
||||
if (response.success) {
|
||||
await queryClient.invalidateQueries({ queryKey: ['activeNotifications'] });
|
||||
await queryClient.invalidateQueries({ queryKey: ['allNotifications'] });
|
||||
} */
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to mark as read');
|
||||
} finally {
|
||||
setLoadingIds(prev => {
|
||||
const newSet = new Set(prev);
|
||||
ids.forEach(id => newSet.delete(id));
|
||||
return newSet;
|
||||
});
|
||||
|
||||
setSelectedFiles(prev => {
|
||||
const newSet = new Set(prev);
|
||||
ids.forEach(id => newSet.delete(id));
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeHandler(id: number) {
|
||||
console.log(id);
|
||||
|
||||
setSelectedFiles(prev => {
|
||||
prev.delete(id)
|
||||
return new Set(prev);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="notifications-list-wrapper"
|
||||
@@ -63,7 +148,8 @@ export function NotificationsList() {
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => {
|
||||
clearHandler();
|
||||
const array = Array.from(selectedFiles);
|
||||
markAsReadHandler(array);
|
||||
}}
|
||||
>
|
||||
{t('mark-all-as-read')}
|
||||
@@ -137,6 +223,10 @@ export function NotificationsList() {
|
||||
|
||||
<button
|
||||
className="btn btn-remove"
|
||||
onClick={() => {
|
||||
removeHandler(item.id);
|
||||
}}
|
||||
disabled={false}
|
||||
>
|
||||
{t('remove')}
|
||||
</button>
|
||||
@@ -147,6 +237,57 @@ export function NotificationsList() {
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="notifications-list-footer"
|
||||
>
|
||||
{totalPages > 0 && (
|
||||
<div className="pagination-controls">
|
||||
<button
|
||||
className="arrow"
|
||||
onClick={goToFirstPage}
|
||||
disabled={currentPageNum === 0}
|
||||
>
|
||||
<IconDoubleArrowLeft />
|
||||
</button>
|
||||
<button
|
||||
onClick={goToPrevPage}
|
||||
className="arrow"
|
||||
disabled={currentPageNum === 0}
|
||||
>
|
||||
<IconArrowLeft />
|
||||
</button>
|
||||
|
||||
<div className="pagination-controls-pages">
|
||||
{getPageNumbers().map((pageNum, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => typeof pageNum === 'number' && goToPage(pageNum)}
|
||||
className={currentPageNum === pageNum ? 'current' : 'other'}
|
||||
disabled={pageNum === '...'}
|
||||
>
|
||||
{pageNum === '...' ? '...' : (pageNum as number) + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={goToNextPage}
|
||||
className="arrow"
|
||||
disabled={currentPageNum === totalPages - 1}
|
||||
>
|
||||
<IconArrowRight />
|
||||
</button>
|
||||
<button
|
||||
className="arrow"
|
||||
onClick={goToLastPage}
|
||||
disabled={currentPageNum === totalPages - 1}
|
||||
>
|
||||
<IconDoubleArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user