rework notification-table
This commit is contained in:
@@ -5131,7 +5131,6 @@
|
|||||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||||
color: v.$white;
|
color: v.$white;
|
||||||
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -5142,6 +5141,35 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-all-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: white;
|
||||||
|
color: black;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 5px 20px;
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 15px 20px -3px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-action {
|
&-action {
|
||||||
@@ -5162,30 +5190,76 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
background: v.$white;
|
background: v.$white;
|
||||||
|
|
||||||
.notification {
|
.notifications-table {
|
||||||
&-item {
|
width: 100%;
|
||||||
border-bottom: 1px solid v.$b-color-2;
|
}
|
||||||
padding: 20px;
|
|
||||||
|
.table-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
border-bottom: 1px solid v.$b-color-2;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.n-header {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid v.$b-color-2;
|
||||||
|
|
||||||
|
.table-cell {
|
||||||
|
padding: 15px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: v.$text-m;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.loading {
|
&.loading {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.READIED {
|
||||||
|
|
||||||
|
.notification-title,
|
||||||
|
.notification-text {
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.n-header) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-cell {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
/* width: 60px;
|
||||||
|
min-width: 60px;
|
||||||
|
max-width: 60px; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
&-body {
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
margin-bottom: 8px;
|
||||||
|
|
||||||
&-body {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@@ -5196,9 +5270,10 @@
|
|||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
color: v.$text-s;
|
color: v.$text-s;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 12px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-status {
|
&-status {
|
||||||
@@ -5213,21 +5288,39 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
.btn-remove {
|
.btn-remove {
|
||||||
color: v.$white;
|
color: v.$white;
|
||||||
background: v.$status-new;
|
background: v.$status-new;
|
||||||
padding: 4px 18px;
|
padding: 6px 18px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #ff9100;
|
background: #ff9100;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-date {
|
&-date {
|
||||||
color: v.$text-m;
|
color: v.$text-m;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5238,16 +5331,29 @@
|
|||||||
button {
|
button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid v.$border-color-1;
|
border: 2px solid v.$border-color-1;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
transition: all .3s ease-in;
|
transition: all 0.2s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background: white;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 2px solid v.$border-color-1-hover;
|
border-color: v.$border-color-1-hover;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
color: v.$status-new;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5258,19 +5364,162 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-item.READIED {
|
.no-notifications {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: v.$text-m;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
.notification-title,
|
&::before {
|
||||||
.notification-text {
|
content: "🔔";
|
||||||
font-weight: 500;
|
display: block;
|
||||||
opacity: 0.8;
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-footer {
|
&-footer {
|
||||||
padding: 10px;
|
padding: 15px;
|
||||||
border-top: 1px solid v.$b-color-2;
|
border-top: 1px solid v.$b-color-2;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid v.$b-color-2;
|
||||||
|
background: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
color: v.$text-s;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 36px;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: v.$b-color-2;
|
||||||
|
border-color: v.$border-color-1-hover;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||||
|
color: white;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.other {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls-pages {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive adjustments
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.notifications-list {
|
||||||
|
&-wrapper {
|
||||||
|
max-width: calc(100vw - var(--side-bar-width) - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-cell {
|
||||||
|
&:first-child {
|
||||||
|
width: 80px !important;
|
||||||
|
min-width: 80px !important;
|
||||||
|
max-width: 80px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
gap: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-header {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,34 +4,205 @@ import { useTranslations } from 'next-intl';
|
|||||||
import { IconCheck, IconArrowLeft, IconArrowRight, IconDoubleArrowLeft, IconDoubleArrowRight } from '@/app/ui/icons/icons';
|
import { IconCheck, IconArrowLeft, IconArrowRight, IconDoubleArrowLeft, IconDoubleArrowRight } from '@/app/ui/icons/icons';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useAllNotifications } from '@/app/hooks/react-query/useAllNotificationsList';
|
import { useAllNotifications } from '@/app/hooks/react-query/useAllNotificationsList';
|
||||||
import { notificationsMarkAsRead, notificationsDelete } from '@/app/actions/notificationActions';
|
import { notificationsMarkAsRead, notificationsDelete, NotificationBody } from '@/app/actions/notificationActions';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useReactTable,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
flexRender,
|
||||||
|
createColumnHelper,
|
||||||
|
RowSelectionState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
|
||||||
|
|
||||||
export function NotificationsList() {
|
export function NotificationsList() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const norificationTranslate = useTranslations('Notifications');
|
const norificationTranslate = useTranslations('Notifications');
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
const [loadingIds, setLoadingIds] = useState<Set<number>>(new Set());
|
const [loadingIds, setLoadingIds] = useState<Set<number>>(new Set());
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [pagination, setPagination] = useState({
|
||||||
const [pageSize] = useState(10);
|
pageIndex: 0,
|
||||||
const { data: allNotifications, isLoading } = useAllNotifications(currentPage, pageSize);
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: allNotifications, isLoading } = useAllNotifications(
|
||||||
|
pagination.pageIndex,
|
||||||
|
pagination.pageSize
|
||||||
|
);
|
||||||
|
|
||||||
const totalPages = allNotifications?.totalPages || 0;
|
const totalPages = allNotifications?.totalPages || 0;
|
||||||
const currentPageNum = allNotifications?.currentPage || 0;
|
const totalRecords = allNotifications?.totalElements || 0;
|
||||||
|
|
||||||
const goToFirstPage = () => setCurrentPage(0);
|
const columnHelper = createColumnHelper<NotificationBody>();
|
||||||
const goToPrevPage = () => setCurrentPage(prev => Math.max(0, prev - 1));
|
|
||||||
const goToNextPage = () => setCurrentPage(prev => Math.min(totalPages - 1, prev + 1));
|
const columns = [
|
||||||
const goToLastPage = () => setCurrentPage(totalPages - 1);
|
columnHelper.display({
|
||||||
const goToPage = (page: number) => setCurrentPage(page);
|
id: 'select',
|
||||||
|
header: ({ table }) => {
|
||||||
|
const currentPageRows = table.getCoreRowModel().rows;
|
||||||
|
const unreadRowsOnPage = currentPageRows.filter(row => row.original.status !== 'READIED');
|
||||||
|
const allUnreadSelectedOnPage = unreadRowsOnPage.every(row => rowSelection[row.original.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="select-all-header">
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (allUnreadSelectedOnPage) {
|
||||||
|
|
||||||
|
const newSelection = { ...rowSelection };
|
||||||
|
unreadRowsOnPage.forEach(row => {
|
||||||
|
delete newSelection[row.original.id];
|
||||||
|
});
|
||||||
|
setRowSelection(newSelection);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const newSelection = { ...rowSelection };
|
||||||
|
unreadRowsOnPage.forEach(row => {
|
||||||
|
newSelection[row.original.id] = true;
|
||||||
|
});
|
||||||
|
setRowSelection(newSelection);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{allUnreadSelectedOnPage ? t('deselect') : t('select-all')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{Object.keys(rowSelection).length > 0 && (
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
const selectedIds = Object.keys(rowSelection).map(Number);
|
||||||
|
notificationActionHandler(selectedIds, 'mark-as-read');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('mark-as-read')} ({Object.keys(rowSelection).length})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isReadied = row.original.status === 'READIED';
|
||||||
|
const isSelected = !!rowSelection[row.original.id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="notification-check">
|
||||||
|
{!isReadied ? (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setRowSelection((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[row.original.id]: !prev[row.original.id],
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSelected && <IconCheck />}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className="empty-block"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('message', {
|
||||||
|
id: 'message',
|
||||||
|
header: () => null, // No header text, using custom header above
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const item = row.original;
|
||||||
|
return (
|
||||||
|
<div className="notification-body">
|
||||||
|
<div className="notification-header">
|
||||||
|
{/* Optional header content */}
|
||||||
|
</div>
|
||||||
|
<div className="notification-text">
|
||||||
|
{norificationTranslate.has(item.message)
|
||||||
|
? norificationTranslate(item.message)
|
||||||
|
: item.message} ({item.id})
|
||||||
|
</div>
|
||||||
|
<div className="notification-footer">
|
||||||
|
<div className="notification-date">
|
||||||
|
{item.createdAt
|
||||||
|
? `${formatDate(item.createdAt)}: ${formatDateTime(item.createdAt)}`
|
||||||
|
: '---'}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn btn-remove"
|
||||||
|
onClick={() => {
|
||||||
|
notificationActionHandler([item.id], 'remove');
|
||||||
|
}}
|
||||||
|
disabled={loadingIds.has(item.id)}
|
||||||
|
>
|
||||||
|
{t('remove')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: allNotifications?.notifications || [],
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
manualPagination: true,
|
||||||
|
pageCount: totalPages,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function notificationActionHandler(ids: number[], action: 'remove' | 'mark-as-read') {
|
||||||
|
setLoadingIds(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
ids.forEach(id => newSet.add(id));
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
if (action === 'mark-as-read') {
|
||||||
|
response = await notificationsMarkAsRead(ids);
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
response = await notificationsDelete(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response?.success) {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['activeNotifications'] });
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['allNotifications'] });
|
||||||
|
|
||||||
|
// Clear selection after successful action
|
||||||
|
setRowSelection({});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = action === 'mark-as-read' ? 'Failed to mark as read' : 'Failed to remove';
|
||||||
|
setError(errorMessage);
|
||||||
|
} finally {
|
||||||
|
setLoadingIds(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
ids.forEach(id => newSet.delete(id));
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getPageNumbers = (): (number | string)[] => {
|
const getPageNumbers = (): (number | string)[] => {
|
||||||
const delta: number = 2;
|
const delta: number = 2;
|
||||||
|
const currentPageNum = pagination.pageIndex;
|
||||||
const range: number[] = [];
|
const range: number[] = [];
|
||||||
const rangeWithDots: (number | string)[] = [];
|
const rangeWithDots: (number | string)[] = [];
|
||||||
let l: number | undefined;
|
let l: number | undefined;
|
||||||
@@ -57,85 +228,25 @@ export function NotificationsList() {
|
|||||||
return rangeWithDots;
|
return rangeWithDots;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(allNotifications);
|
|
||||||
}, [allNotifications])
|
|
||||||
|
|
||||||
function selectHandler(fileId: number) {
|
|
||||||
setSelectedFiles((prev) => {
|
|
||||||
const newSet = new Set(prev);
|
|
||||||
|
|
||||||
if (newSet.has(fileId)) {
|
|
||||||
newSet.delete(fileId)
|
|
||||||
} else {
|
|
||||||
newSet.add(fileId)
|
|
||||||
}
|
|
||||||
return newSet;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearHandler() {
|
|
||||||
setSelectedFiles(new Set());
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectAllHandler() {
|
|
||||||
const idsToSelect = allNotifications?.notifications
|
|
||||||
.filter(item => item.status !== 'READIED')
|
|
||||||
.map(item => item.id);
|
|
||||||
|
|
||||||
setSelectedFiles(new Set(idsToSelect));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationActionHandler(ids: number[], action: 'remove' | 'mark-as-read') {
|
|
||||||
setLoadingIds(prev => {
|
|
||||||
const newSet = new Set(prev);
|
|
||||||
ids.forEach(id => newSet.add(id));
|
|
||||||
return newSet;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
let response;
|
|
||||||
|
|
||||||
if (action === 'mark-as-read') {
|
|
||||||
response = await notificationsMarkAsRead(ids);
|
|
||||||
} else if (action === 'remove') {
|
|
||||||
response = await notificationsDelete(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.success) {
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ['activeNotifications'] });
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ['allNotifications'] });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = action === 'mark-as-read' ? 'Failed to mark as read' : 'Failed to remove';
|
|
||||||
setError(errorMessage);
|
|
||||||
} 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="notifications-list-wrapper">
|
||||||
className="notifications-list-wrapper"
|
<div className="notifications-list-header">
|
||||||
>
|
<h4>{t('all-notifications')}</h4>
|
||||||
<div
|
<div className="notifications-table-header">
|
||||||
className="notifications-list-header"
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
>
|
<div key={headerGroup.id} className="table-row n-header">
|
||||||
<h4>
|
{headerGroup.headers.map((header) => (
|
||||||
{t('all-notifications')}
|
<div key={header.id} className="table-cell">
|
||||||
</h4>
|
{flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
<div
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* <div
|
||||||
className="notifications-list-action"
|
className="notifications-list-action"
|
||||||
>
|
>
|
||||||
{(selectedFiles.size !== 0) && (
|
{(selectedFiles.size !== 0) && (
|
||||||
@@ -168,100 +279,49 @@ export function NotificationsList() {
|
|||||||
>
|
>
|
||||||
{t('select-all')}
|
{t('select-all')}
|
||||||
</button>
|
</button>
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="notifications-list-body">
|
||||||
|
{allNotifications?.notifications.length !== 0 ? (
|
||||||
|
<div className="notifications-table">
|
||||||
|
<div className="notifications-table-body">
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
<div
|
<div
|
||||||
className="notifications-list-body"
|
key={row.id}
|
||||||
|
className={`table-row ${loadingIds.has(row.original.id) ? 'loading' : ''} ${row.original.status}`}
|
||||||
>
|
>
|
||||||
{(allNotifications?.notifications.length !== 0) && (
|
{row.getVisibleCells().map((cell) => (
|
||||||
allNotifications?.notifications.map(item => {
|
<div key={cell.id} className="table-cell">
|
||||||
return (
|
{flexRender(
|
||||||
<div
|
cell.column.columnDef.cell,
|
||||||
className={`notification-item ${loadingIds.has(item.id) ? 'loading' : ''} ${item.status}`}
|
cell.getContext()
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="notification-check"
|
|
||||||
>
|
|
||||||
{item.status !== 'READIED' ? (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
selectHandler(item.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedFiles.has(item.id) && (
|
|
||||||
<IconCheck />
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="empty-block"></div>
|
<div className="no-notifications">{t('no-notifications')}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="notifications-list-footer">
|
||||||
className="notification-body"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="notification-header"
|
|
||||||
>
|
|
||||||
{/* <div
|
|
||||||
className="notification-title"
|
|
||||||
>
|
|
||||||
{item.type} {item.id}
|
|
||||||
</div> */}
|
|
||||||
{/* <div
|
|
||||||
className="notification-status"
|
|
||||||
>
|
|
||||||
{item.status}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="notification-text"
|
|
||||||
>
|
|
||||||
{norificationTranslate.has(item.message) ? norificationTranslate(item.message) : item.message}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="notification-footer"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="notification-date"
|
|
||||||
>
|
|
||||||
{item.createdAt ? `${formatDate(item.createdAt)}: ${formatDateTime(item.createdAt)}` : '---'}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="btn btn-remove"
|
|
||||||
onClick={() => {
|
|
||||||
notificationActionHandler([item.id], 'remove');
|
|
||||||
}}
|
|
||||||
disabled={loadingIds.has(item.id)}
|
|
||||||
>
|
|
||||||
{t('remove')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="notifications-list-footer"
|
|
||||||
>
|
|
||||||
{totalPages > 0 && (
|
{totalPages > 0 && (
|
||||||
<div className="pagination-controls">
|
<div className="pagination-controls">
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={goToFirstPage}
|
onClick={() => table.firstPage()}
|
||||||
disabled={currentPageNum === 0}
|
disabled={!table.getCanPreviousPage()}
|
||||||
>
|
>
|
||||||
<IconDoubleArrowLeft />
|
<IconDoubleArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={goToPrevPage}
|
onClick={() => table.previousPage()}
|
||||||
className="arrow"
|
className="arrow"
|
||||||
disabled={currentPageNum === 0}
|
disabled={!table.getCanPreviousPage()}
|
||||||
>
|
>
|
||||||
<IconArrowLeft />
|
<IconArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
@@ -270,8 +330,8 @@ export function NotificationsList() {
|
|||||||
{getPageNumbers().map((pageNum, index) => (
|
{getPageNumbers().map((pageNum, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => typeof pageNum === 'number' && goToPage(pageNum)}
|
onClick={() => typeof pageNum === 'number' && table.setPageIndex(pageNum)}
|
||||||
className={currentPageNum === pageNum ? 'current' : 'other'}
|
className={pagination.pageIndex === pageNum ? 'current' : 'other'}
|
||||||
disabled={pageNum === '...'}
|
disabled={pageNum === '...'}
|
||||||
>
|
>
|
||||||
{pageNum === '...' ? '...' : (pageNum as number) + 1}
|
{pageNum === '...' ? '...' : (pageNum as number) + 1}
|
||||||
@@ -280,22 +340,24 @@ export function NotificationsList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={goToNextPage}
|
onClick={() => table.nextPage()}
|
||||||
className="arrow"
|
className="arrow"
|
||||||
disabled={currentPageNum === totalPages - 1}
|
disabled={!table.getCanNextPage()}
|
||||||
>
|
>
|
||||||
<IconArrowRight />
|
<IconArrowRight />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={goToLastPage}
|
onClick={() => table.lastPage()}
|
||||||
disabled={currentPageNum === totalPages - 1}
|
disabled={!table.getCanNextPage()}
|
||||||
>
|
>
|
||||||
<IconDoubleArrowRight />
|
<IconDoubleArrowRight />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && <div className="error-message">{error}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user