mirror of
https://github.com/ahmadk953/tasko.git
synced 2025-02-07 11:42:55 +00:00
Added Pagination to Audit Log Page
This commit is contained in:
parent
10c54df886
commit
c9313ff5f0
4 changed files with 198 additions and 44 deletions
|
@ -19,7 +19,7 @@ Currently, there is no documentation or wiki available but there will be one add
|
||||||
This will be published on the site some time soon but for now, the roadmap will be listed here.
|
This will be published on the site some time soon but for now, the roadmap will be listed here.
|
||||||
|
|
||||||
- [ ] Finish adding started at date feature
|
- [ ] Finish adding started at date feature
|
||||||
- [ ] Pagination for Audit Logs page
|
- [x] Pagination for Audit Logs page
|
||||||
- [ ] Board sorting options (Boards Page)
|
- [ ] Board sorting options (Boards Page)
|
||||||
- [ ] Add real-time collaboration
|
- [ ] Add real-time collaboration
|
||||||
- [ ] Add end-to-end Database encryption (for customer data such as card titles and descriptions, and subscription information)
|
- [ ] Add end-to-end Database encryption (for customer data such as card titles and descriptions, and subscription information)
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
import { auth } from '@clerk/nextjs/server';
|
'use client';
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
import { db } from '@/lib/db';
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
} from '@/components/ui/pagination';
|
||||||
import { ActivityItem } from '@/components/activity-item';
|
import { ActivityItem } from '@/components/activity-item';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { AuditLog } from '@prisma/client';
|
||||||
|
|
||||||
export const ActivityList = async () => {
|
|
||||||
const { orgId } = await auth();
|
|
||||||
|
|
||||||
if (!orgId) redirect('/select-org');
|
|
||||||
|
|
||||||
const auditLogs = await db.auditLog.findMany({
|
|
||||||
where: {
|
|
||||||
orgId,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
cacheStrategy: { ttl: 30, swr: 60 },
|
|
||||||
});
|
|
||||||
|
|
||||||
|
export const ActivityList = ({
|
||||||
|
totalPages,
|
||||||
|
currentPage,
|
||||||
|
auditLogs,
|
||||||
|
}: {
|
||||||
|
totalPages: number;
|
||||||
|
currentPage: number;
|
||||||
|
auditLogs: AuditLog[];
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<ol className='mt-4 space-y-4'>
|
<ol className='mt-4 space-y-4'>
|
||||||
<p className='hidden text-center text-xs text-muted-foreground last:block'>
|
<p className='hidden text-center text-xs text-muted-foreground last:block'>
|
||||||
No activity found inside this organization
|
No activity found inside this organization
|
||||||
|
@ -29,17 +31,114 @@ export const ActivityList = async () => {
|
||||||
<ActivityItem key={log.id} data={log} />
|
<ActivityItem key={log.id} data={log} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
<ActivityListPagination
|
||||||
|
totalPages={totalPages}
|
||||||
|
currentPage={currentPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ActivityList.Skeleton = function ActivityListSkeleton() {
|
const ActivityListPagination = ({
|
||||||
|
totalPages,
|
||||||
|
currentPage,
|
||||||
|
}: {
|
||||||
|
totalPages: number;
|
||||||
|
currentPage: number;
|
||||||
|
}) => {
|
||||||
|
const renderPageNumbers = () => {
|
||||||
|
const pageNumbers = [];
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
|
||||||
|
if (totalPages <= maxVisiblePages) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
href={`activity?page=${i}`}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Always show first page
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key={1}>
|
||||||
|
<PaginationLink href='activity?page=1' isActive={currentPage === 1}>
|
||||||
|
1
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show ellipsis if current page is more than 3
|
||||||
|
if (currentPage > 3) {
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key='ellipsis-start'>
|
||||||
|
<PaginationEllipsis />
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show current page and surrounding pages
|
||||||
|
const startPage = Math.max(2, currentPage - 1);
|
||||||
|
const endPage = Math.min(totalPages - 1, currentPage + 1);
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
href={`activity?page=${i}`}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show ellipsis if current page is less than totalPages - 2
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key='ellipsis-end'>
|
||||||
|
<PaginationEllipsis />
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always show last page
|
||||||
|
pageNumbers.push(
|
||||||
|
<PaginationItem key={totalPages}>
|
||||||
|
<PaginationLink
|
||||||
|
href={`activity?page=${totalPages}`}
|
||||||
|
isActive={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
{totalPages}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageNumbers;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ol className='mt-4 space-y-4'>
|
<Pagination>
|
||||||
<Skeleton className='h-14 w-[80%]' />
|
<PaginationContent>
|
||||||
<Skeleton className='h-14 w-[50%]' />
|
{currentPage > 1 && (
|
||||||
<Skeleton className='h-14 w-[70%]' />
|
<PaginationItem>
|
||||||
<Skeleton className='h-14 w-[80%]' />
|
<PaginationPrevious href={`activity?page=${currentPage - 1}`} />
|
||||||
<Skeleton className='h-14 w-[75%]' />
|
</PaginationItem>
|
||||||
</ol>
|
)}
|
||||||
|
{renderPageNumbers()}
|
||||||
|
{currentPage < totalPages && (
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext href={`activity?page=${currentPage + 1}`} />
|
||||||
|
</PaginationItem>
|
||||||
|
)}
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +1,77 @@
|
||||||
|
import { auth } from '@clerk/nextjs/server';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
|
|
||||||
import { Info } from '../_components/info';
|
import { Info } from '../_components/info';
|
||||||
import { ActivityList } from './_components/activity-list';
|
import { ActivityList } from './_components/activity-list';
|
||||||
import { checkSubscription } from '@/lib/subscription';
|
|
||||||
|
|
||||||
const ActivityPage = async () => {
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { checkSubscription } from '@/lib/subscription';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
|
const ActivityPage = async ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: { [key: string]: string | undefined };
|
||||||
|
}) => {
|
||||||
const isPro = await checkSubscription();
|
const isPro = await checkSubscription();
|
||||||
|
const { orgId } = await auth();
|
||||||
|
const params = await searchParams;
|
||||||
|
const currentPage = Number(params.page);
|
||||||
|
const limit = 10;
|
||||||
|
|
||||||
|
if (!orgId) redirect('/select-org');
|
||||||
|
if (!currentPage || currentPage < 1) redirect('activity?page=1');
|
||||||
|
|
||||||
|
const skip = (currentPage - 1) * limit;
|
||||||
|
|
||||||
|
const auditLogs = await db.auditLog.findMany({
|
||||||
|
where: {
|
||||||
|
orgId,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
cacheStrategy: { ttl: 30, swr: 60 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalCount = await db.auditLog.count({
|
||||||
|
where: {
|
||||||
|
orgId,
|
||||||
|
},
|
||||||
|
cacheStrategy: { ttl: 30, swr: 60 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalCount / limit);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<Info isPro={isPro} />
|
<Info isPro={isPro} />
|
||||||
<Separator className='my-2' />
|
<Separator className='my-2' />
|
||||||
<Suspense fallback={<ActivityList.Skeleton />}>
|
<Suspense fallback={<ActivityListSkeleton />}>
|
||||||
<ActivityList />
|
<ActivityList
|
||||||
|
totalPages={totalPages}
|
||||||
|
currentPage={currentPage}
|
||||||
|
auditLogs={auditLogs}
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ActivityPage;
|
export default ActivityPage;
|
||||||
|
|
||||||
|
const ActivityListSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<ol className='mt-4 space-y-4'>
|
||||||
|
<Skeleton className='h-14 w-[80%]' />
|
||||||
|
<Skeleton className='h-14 w-[50%]' />
|
||||||
|
<Skeleton className='h-14 w-[70%]' />
|
||||||
|
<Skeleton className='h-14 w-[80%]' />
|
||||||
|
<Skeleton className='h-14 w-[75%]' />
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ButtonProps, buttonVariants } from '@/components/ui/button';
|
import { ButtonProps, buttonVariants } from '@/components/ui/button';
|
||||||
|
@ -37,7 +38,7 @@ PaginationItem.displayName = 'PaginationItem';
|
||||||
type PaginationLinkProps = {
|
type PaginationLinkProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
} & Pick<ButtonProps, 'size'> &
|
} & Pick<ButtonProps, 'size'> &
|
||||||
React.ComponentProps<'a'>;
|
React.ComponentProps<typeof Link>;
|
||||||
|
|
||||||
const PaginationLink = ({
|
const PaginationLink = ({
|
||||||
className,
|
className,
|
||||||
|
@ -45,7 +46,7 @@ const PaginationLink = ({
|
||||||
size = 'icon',
|
size = 'icon',
|
||||||
...props
|
...props
|
||||||
}: PaginationLinkProps) => (
|
}: PaginationLinkProps) => (
|
||||||
<a
|
<Link
|
||||||
aria-current={isActive ? 'page' : undefined}
|
aria-current={isActive ? 'page' : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
|
|
Loading…
Reference in a new issue