diff --git a/doc/prompts/13. Pagination b/doc/prompts/13. Pagination new file mode 100644 index 0000000..04cd32f --- /dev/null +++ b/doc/prompts/13. Pagination @@ -0,0 +1,2 @@ +Please implement pagination to all list pages in admin +create new branch and please work in the branch \ No newline at end of file diff --git a/src/app/(admin)/admin/contact-records/page.tsx b/src/app/(admin)/admin/contact-records/page.tsx index 0d9a83d..479f961 100644 --- a/src/app/(admin)/admin/contact-records/page.tsx +++ b/src/app/(admin)/admin/contact-records/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, FormEvent } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { CONTACT_TYPES } from '@/lib/constants'; +import Pagination from '@/lib/components/Pagination'; interface Customer { id: string; @@ -19,6 +20,18 @@ interface ContactRecord { customer: Customer; } +interface PaginationInfo { + page: number; + pageSize: number; + totalCount: number; + totalPages: number; +} + +interface ContactRecordsResponse { + data: ContactRecord[]; + pagination: PaginationInfo; +} + export default function ContactRecordsList() { const router = useRouter(); const searchParams = useSearchParams(); @@ -28,16 +41,26 @@ export default function ContactRecordsList() { const initialContactType = searchParams.get('contactType') || ''; const initialDateFrom = searchParams.get('dateFrom') || ''; const initialDateTo = searchParams.get('dateTo') || ''; + const initialPage = parseInt(searchParams.get('page') || '1'); + const initialPageSize = parseInt(searchParams.get('pageSize') || '10'); // State for filters const [customerId, setCustomerId] = useState(initialCustomerId); const [contactType, setContactType] = useState(initialContactType); const [dateFrom, setDateFrom] = useState(initialDateFrom); const [dateTo, setDateTo] = useState(initialDateTo); + const [page, setPage] = useState(initialPage); + const [pageSize, setPageSize] = useState(initialPageSize); // State for data const [contactRecords, setContactRecords] = useState([]); const [customers, setCustomers] = useState([]); + const [pagination, setPagination] = useState({ + page: initialPage, + pageSize: initialPageSize, + totalCount: 0, + totalPages: 0 + }); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -60,19 +83,21 @@ export default function ContactRecordsList() { fetchCustomers(); }, []); - // Fetch contact records with filters + // Fetch contact records with filters and pagination useEffect(() => { const fetchContactRecords = async () => { setIsLoading(true); setError(null); try { - // Build query string with filters + // Build query string with filters and pagination const params = new URLSearchParams(); if (customerId) params.append('customerId', customerId); if (contactType) params.append('contactType', contactType); if (dateFrom) params.append('dateFrom', dateFrom); if (dateTo) params.append('dateTo', dateTo); + params.append('page', initialPage.toString()); + params.append('pageSize', initialPageSize.toString()); const response = await fetch(`/api/contact-records?${params.toString()}`); @@ -80,8 +105,9 @@ export default function ContactRecordsList() { throw new Error('Failed to fetch contact records'); } - const data = await response.json(); - setContactRecords(data); + const responseData: ContactRecordsResponse = await response.json(); + setContactRecords(responseData.data); + setPagination(responseData.pagination); } catch (err) { console.error('Error fetching contact records:', err); setError(err instanceof Error ? err.message : 'An error occurred'); @@ -90,22 +116,21 @@ export default function ContactRecordsList() { } }; - // Only fetch if we have URL parameters or if this is the initial load - if (initialCustomerId || initialContactType || initialDateFrom || initialDateTo || isLoading) { - fetchContactRecords(); - } - }, [initialCustomerId, initialContactType, initialDateFrom, initialDateTo]); + fetchContactRecords(); + }, [initialCustomerId, initialContactType, initialDateFrom, initialDateTo, initialPage, initialPageSize]); // Handle filter form submission const handleFilterSubmit = (e: FormEvent) => { e.preventDefault(); - // Build query string with filters + // Reset to page 1 when applying new filters const params = new URLSearchParams(); if (customerId) params.append('customerId', customerId); if (contactType) params.append('contactType', contactType); if (dateFrom) params.append('dateFrom', dateFrom); if (dateTo) params.append('dateTo', dateTo); + params.append('page', '1'); // Reset to page 1 + params.append('pageSize', pageSize.toString()); // Update URL with filters router.push(`/admin/contact-records?${params.toString()}`); @@ -117,7 +142,19 @@ export default function ContactRecordsList() { setContactType(''); setDateFrom(''); setDateTo(''); - router.push('/admin/contact-records'); + router.push(`/admin/contact-records?page=1&pageSize=${pageSize}`); + }; + + // Handle page change + const handlePageChange = (newPage: number) => { + setPage(newPage); + + // Build query string with current filters and new page + const params = new URLSearchParams(searchParams.toString()); + params.set('page', newPage.toString()); + + // Update URL with new page + router.push(`/admin/contact-records?${params.toString()}`); }; return ( @@ -308,6 +345,14 @@ export default function ContactRecordsList() { + {/* Pagination */} + )} diff --git a/src/app/(admin)/admin/customers/page.tsx b/src/app/(admin)/admin/customers/page.tsx index dbec913..7abbad1 100644 --- a/src/app/(admin)/admin/customers/page.tsx +++ b/src/app/(admin)/admin/customers/page.tsx @@ -1,20 +1,103 @@ +'use client'; + +import { useState, useEffect } from 'react'; import Link from 'next/link'; -import { getDataSource, Customer } from '@/lib/database'; +import { useRouter, useSearchParams } from 'next/navigation'; import DeleteButton from './DeleteButton'; +import Pagination from '@/lib/components/Pagination'; -export default async function AdminCustomers() { - // Fetch customers from the database - const dataSource = await getDataSource(); - const customerRepository = dataSource.getRepository(Customer); +interface Customer { + id: string; + name: string; + url: string; + email: string; + createdAt: string; + modifiedAt: string; +} - const customers = await customerRepository.find({ - order: { createdAt: 'DESC' } +interface PaginationInfo { + page: number; + pageSize: number; + totalCount: number; + totalPages: number; +} + +interface CustomersResponse { + data: Customer[]; + pagination: PaginationInfo; +} + +export default function AdminCustomers() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get pagination values from URL params + const initialPage = parseInt(searchParams.get('page') || '1'); + const initialPageSize = parseInt(searchParams.get('pageSize') || '10'); + + // State for pagination + const [page, setPage] = useState(initialPage); + const [pageSize, setPageSize] = useState(initialPageSize); + + // State for data + const [customers, setCustomers] = useState([]); + const [pagination, setPagination] = useState({ + page: initialPage, + pageSize: initialPageSize, + totalCount: 0, + totalPages: 0 }); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch customers with pagination + useEffect(() => { + const fetchCustomers = async () => { + setIsLoading(true); + setError(null); + + try { + // Build query string with pagination + const params = new URLSearchParams(); + params.append('page', initialPage.toString()); + params.append('pageSize', initialPageSize.toString()); + + const response = await fetch(`/api/customers?${params.toString()}`); + + if (!response.ok) { + throw new Error('Failed to fetch customers'); + } + + const responseData: CustomersResponse = await response.json(); + setCustomers(responseData.data); + setPagination(responseData.pagination); + } catch (err) { + console.error('Error fetching customers:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setIsLoading(false); + } + }; + + fetchCustomers(); + }, [initialPage, initialPageSize]); + + // Handle page change + const handlePageChange = (newPage: number) => { + setPage(newPage); + + // Build query string with new page + const params = new URLSearchParams(searchParams.toString()); + params.set('page', newPage.toString()); + + // Update URL with new page + router.push(`/admin/customers?${params.toString()}`); + }; return (
-

Customers

+

Customers

-
-
-
-
- - - - - - - - - - - - - - {customers.length > 0 ? ( - customers.map((customer) => ( - - - - - - - - - - )) - ) : ( - - - - )} - -
- ID - - Name - - URL - - Email - - Created - - Modified - - Actions -
- - {customer.id.substring(0, 8)}... - - - - {customer.name} - - - {customer.url ? ( - - {customer.url} - - ) : ( - - - )} - - - {customer.email} - - - {new Date(customer.createdAt).toLocaleDateString()} - - {new Date(customer.modifiedAt).toLocaleDateString()} - - - Edit - - -
- No customers found. Create your first customer! -
+ {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

{error}

-
+ )} + + {/* Loading State */} + {isLoading ? ( +
+

Loading customers...

+
+ ) : ( +
+
+
+
+ + + + + + + + + + + + + + {customers.length > 0 ? ( + customers.map((customer) => ( + + + + + + + + + + )) + ) : ( + + + + )} + +
+ ID + + Name + + URL + + Email + + Created + + Modified + + Actions +
+ + {customer.id.substring(0, 8)}... + + + + {customer.name} + + + {customer.url ? ( + + {customer.url} + + ) : ( + - + )} + + + {customer.email} + + + {new Date(customer.createdAt).toLocaleDateString()} + + {new Date(customer.modifiedAt).toLocaleDateString()} + + + Edit + + +
+ No customers found. Create your first customer! +
+ + {/* Pagination */} + +
+
+
+
+ )}
); } diff --git a/src/app/(admin)/admin/email-templates/page.tsx b/src/app/(admin)/admin/email-templates/page.tsx index 3a288cb..9621a8a 100644 --- a/src/app/(admin)/admin/email-templates/page.tsx +++ b/src/app/(admin)/admin/email-templates/page.tsx @@ -1,20 +1,102 @@ +'use client'; + +import { useState, useEffect } from 'react'; import Link from 'next/link'; -import { getDataSource, EmailTemplate } from '@/lib/database'; +import { useRouter, useSearchParams } from 'next/navigation'; import DeleteButton from './DeleteButton'; +import Pagination from '@/lib/components/Pagination'; -export default async function AdminEmailTemplates() { - // Fetch email templates from the database - const dataSource = await getDataSource(); - const emailTemplateRepository = dataSource.getRepository(EmailTemplate); +interface EmailTemplate { + id: string; + title: string; + content: string; + createdAt: string; + modifiedAt: string; +} - const emailTemplates = await emailTemplateRepository.find({ - order: { createdAt: 'DESC' } +interface PaginationInfo { + page: number; + pageSize: number; + totalCount: number; + totalPages: number; +} + +interface EmailTemplatesResponse { + data: EmailTemplate[]; + pagination: PaginationInfo; +} + +export default function AdminEmailTemplates() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get pagination values from URL params + const initialPage = parseInt(searchParams.get('page') || '1'); + const initialPageSize = parseInt(searchParams.get('pageSize') || '10'); + + // State for pagination + const [page, setPage] = useState(initialPage); + const [pageSize, setPageSize] = useState(initialPageSize); + + // State for data + const [emailTemplates, setEmailTemplates] = useState([]); + const [pagination, setPagination] = useState({ + page: initialPage, + pageSize: initialPageSize, + totalCount: 0, + totalPages: 0 }); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch email templates with pagination + useEffect(() => { + const fetchEmailTemplates = async () => { + setIsLoading(true); + setError(null); + + try { + // Build query string with pagination + const params = new URLSearchParams(); + params.append('page', initialPage.toString()); + params.append('pageSize', initialPageSize.toString()); + + const response = await fetch(`/api/email-templates?${params.toString()}`); + + if (!response.ok) { + throw new Error('Failed to fetch email templates'); + } + + const responseData: EmailTemplatesResponse = await response.json(); + setEmailTemplates(responseData.data); + setPagination(responseData.pagination); + } catch (err) { + console.error('Error fetching email templates:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setIsLoading(false); + } + }; + + fetchEmailTemplates(); + }, [initialPage, initialPageSize]); + + // Handle page change + const handlePageChange = (newPage: number) => { + setPage(newPage); + + // Build query string with new page + const params = new URLSearchParams(searchParams.toString()); + params.set('page', newPage.toString()); + + // Update URL with new page + router.push(`/admin/email-templates?${params.toString()}`); + }; return (
-

Email Templates

+

Email Templates

-
-
-
-
- - - - - - - - - - - {emailTemplates.length > 0 ? ( - emailTemplates.map((template) => ( - - - - - - - - )) - ) : ( - - - - )} - -
- Title - - Created - - Modified - - Actions -
- - {template.title} - - - {new Date(template.createdAt).toLocaleDateString()} - {new Date(template.createdAt).toLocaleTimeString()} - - {new Date(template.modifiedAt).toLocaleDateString()} - {new Date(template.modifiedAt).toLocaleTimeString()} - - - Edit - - -
- No email templates found. Create your first template! -
+ {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

{error}

-
+ )} + + {/* Loading State */} + {isLoading ? ( +
+

Loading email templates...

+
+ ) : ( +
+
+
+
+ + + + + + + + + + + {emailTemplates.length > 0 ? ( + emailTemplates.map((template) => ( + + + + + + + )) + ) : ( + + + + )} + +
+ Title + + Created + + Modified + + Actions +
+ + {template.title} + + + {new Date(template.createdAt).toLocaleDateString()} + {' '} + {new Date(template.createdAt).toLocaleTimeString()} + + {new Date(template.modifiedAt).toLocaleDateString()} + {' '} + {new Date(template.modifiedAt).toLocaleTimeString()} + + + Edit + + +
+ No email templates found. Create your first template! +
+ + {/* Pagination */} + +
+
+
+
+ )}
); } diff --git a/src/app/(admin)/admin/posts/page.tsx b/src/app/(admin)/admin/posts/page.tsx index b34e14f..64ff182 100644 --- a/src/app/(admin)/admin/posts/page.tsx +++ b/src/app/(admin)/admin/posts/page.tsx @@ -1,20 +1,108 @@ -import Link from 'next/link'; -import { Post, getDataSource } from '@/lib/database'; -import DeleteButton from './DeleteButton'; +'use client'; -export default async function AdminPosts() { - // Fetch posts from the database - const dataSource = await getDataSource(); - const postRepository = dataSource.getRepository(Post); - const posts = await postRepository.find({ - relations: ['user'], - order: { createdAt: 'DESC' } +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import DeleteButton from './DeleteButton'; +import Pagination from '@/lib/components/Pagination'; + +interface User { + id: string; + username: string; +} + +interface Post { + id: string; + title: string; + content: string; + createdAt: string; + modifiedAt: string; + user: User; +} + +interface PaginationInfo { + page: number; + pageSize: number; + totalCount: number; + totalPages: number; +} + +interface PostsResponse { + data: Post[]; + pagination: PaginationInfo; +} + +export default function AdminPosts() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get pagination values from URL params + const initialPage = parseInt(searchParams.get('page') || '1'); + const initialPageSize = parseInt(searchParams.get('pageSize') || '10'); + + // State for pagination + const [page, setPage] = useState(initialPage); + const [pageSize, setPageSize] = useState(initialPageSize); + + // State for data + const [posts, setPosts] = useState([]); + const [pagination, setPagination] = useState({ + page: initialPage, + pageSize: initialPageSize, + totalCount: 0, + totalPages: 0 }); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch posts with pagination + useEffect(() => { + const fetchPosts = async () => { + setIsLoading(true); + setError(null); + + try { + // Build query string with pagination + const params = new URLSearchParams(); + params.append('page', initialPage.toString()); + params.append('pageSize', initialPageSize.toString()); + + const response = await fetch(`/api/posts?${params.toString()}`); + + if (!response.ok) { + throw new Error('Failed to fetch posts'); + } + + const responseData: PostsResponse = await response.json(); + setPosts(responseData.data); + setPagination(responseData.pagination); + } catch (err) { + console.error('Error fetching posts:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setIsLoading(false); + } + }; + + fetchPosts(); + }, [initialPage, initialPageSize]); + + // Handle page change + const handlePageChange = (newPage: number) => { + setPage(newPage); + + // Build query string with new page + const params = new URLSearchParams(searchParams.toString()); + params.set('page', newPage.toString()); + + // Update URL with new page + router.push(`/admin/posts?${params.toString()}`); + }; return (
-

Posts

+

Posts

-
-
-
-
- - - - - - - - - - - - {posts.length > 0 ? ( - posts.map((post: Post) => ( - - - - - - - - )) - ) : ( - - - - )} - -
- Title - - Author - - Created - - Modified - - Actions -
- {post.title} - - {post.user?.username || 'Unknown'} - - {new Date(post.createdAt).toLocaleDateString()} - - {new Date(post.modifiedAt).toLocaleDateString()} - - - Edit - - -
- No posts found. Create your first post! -
+ {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

{error}

-
+ )} + + {/* Loading State */} + {isLoading ? ( +
+

Loading posts...

+
+ ) : ( +
+
+
+
+ + + + + + + + + + + + {posts.length > 0 ? ( + posts.map((post) => ( + + + + + + + + )) + ) : ( + + + + )} + +
+ Title + + Author + + Created + + Modified + + Actions +
+ {post.title} + + {post.user?.username || 'Unknown'} + + {new Date(post.createdAt).toLocaleDateString()} + + {new Date(post.modifiedAt).toLocaleDateString()} + + + Edit + + +
+ No posts found. Create your first post! +
+ + {/* Pagination */} + +
+
+
+
+ )}
); } diff --git a/src/app/(admin)/admin/users/page.tsx b/src/app/(admin)/admin/users/page.tsx index 7f01a9c..41486dd 100644 --- a/src/app/(admin)/admin/users/page.tsx +++ b/src/app/(admin)/admin/users/page.tsx @@ -1,22 +1,103 @@ +'use client'; + +import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; -import { getDataSource, User } from '@/lib/database'; +import { useRouter, useSearchParams } from 'next/navigation'; import DeleteButton from './DeleteButton'; +import Pagination from '@/lib/components/Pagination'; -export default async function AdminUsers() { - // Fetch users from the database - const dataSource = await getDataSource(); - const userRepository = dataSource.getRepository(User); +interface User { + id: string; + username: string; + avatar: string | null; + createdAt: string; + modifiedAt: string; +} - const users = await userRepository.find({ - select: ['id', 'username', 'avatar', 'createdAt', 'modifiedAt'], - order: { createdAt: 'DESC' } +interface PaginationInfo { + page: number; + pageSize: number; + totalCount: number; + totalPages: number; +} + +interface UsersResponse { + data: User[]; + pagination: PaginationInfo; +} + +export default function AdminUsers() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get pagination values from URL params + const initialPage = parseInt(searchParams.get('page') || '1'); + const initialPageSize = parseInt(searchParams.get('pageSize') || '10'); + + // State for pagination + const [page, setPage] = useState(initialPage); + const [pageSize, setPageSize] = useState(initialPageSize); + + // State for data + const [users, setUsers] = useState([]); + const [pagination, setPagination] = useState({ + page: initialPage, + pageSize: initialPageSize, + totalCount: 0, + totalPages: 0 }); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch users with pagination + useEffect(() => { + const fetchUsers = async () => { + setIsLoading(true); + setError(null); + + try { + // Build query string with pagination + const params = new URLSearchParams(); + params.append('page', initialPage.toString()); + params.append('pageSize', initialPageSize.toString()); + + const response = await fetch(`/api/users?${params.toString()}`); + + if (!response.ok) { + throw new Error('Failed to fetch users'); + } + + const responseData: UsersResponse = await response.json(); + setUsers(responseData.data); + setPagination(responseData.pagination); + } catch (err) { + console.error('Error fetching users:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setIsLoading(false); + } + }; + + fetchUsers(); + }, [initialPage, initialPageSize]); + + // Handle page change + const handlePageChange = (newPage: number) => { + setPage(newPage); + + // Build query string with new page + const params = new URLSearchParams(searchParams.toString()); + params.set('page', newPage.toString()); + + // Update URL with new page + router.push(`/admin/users?${params.toString()}`); + }; return (
-

Users

+

Users

-
-
-
-
- - - - - - - - - - - - {users.length > 0 ? ( - users.map((user) => ( - - - - - - - - )) - ) : ( - - - - )} - -
- Username - - Avatar - - Created - - Modified - - Actions -
- {user.username} - - {user.avatar ? ( - {user.username} - ) : ( -
- {user.username.charAt(0).toUpperCase()} -
- )} -
- {new Date(user.createdAt).toLocaleDateString()} - - {new Date(user.modifiedAt).toLocaleDateString()} - - - Edit - - -
- No users found. Create your first user! -
+ {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

{error}

-
+ )} + + {/* Loading State */} + {isLoading ? ( +
+

Loading users...

+
+ ) : ( +
+
+
+
+ + + + + + + + + + + + {users.length > 0 ? ( + users.map((user) => ( + + + + + + + + )) + ) : ( + + + + )} + +
+ Username + + Avatar + + Created + + Modified + + Actions +
+ {user.username} + + {user.avatar ? ( + {user.username} + ) : ( +
+ {user.username.charAt(0).toUpperCase()} +
+ )} +
+ {new Date(user.createdAt).toLocaleDateString()} + + {new Date(user.modifiedAt).toLocaleDateString()} + + + Edit + + +
+ No users found. Create your first user! +
+ + {/* Pagination */} + +
+
+
+
+ )}
); } diff --git a/src/app/api/contact-records/route.ts b/src/app/api/contact-records/route.ts index fa7abfe..f9842ef 100644 --- a/src/app/api/contact-records/route.ts +++ b/src/app/api/contact-records/route.ts @@ -14,49 +14,63 @@ export async function GET(request: NextRequest) { const dateFrom = url.searchParams.get('dateFrom'); const dateTo = url.searchParams.get('dateTo'); + // Pagination parameters + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const skip = (page - 1) * pageSize; + // Build query - const queryOptions: any = { - order: { createdAt: 'DESC' }, - relations: ['customer'] - }; + let queryBuilder = contactRecordRepository.createQueryBuilder('contactRecord') + .leftJoinAndSelect('contactRecord.customer', 'customer') + .orderBy('contactRecord.createdAt', 'DESC'); - // Build where clause - let whereClause: any = {}; - - // Filter by customer if customerId is provided + // Apply filters if (customerId) { - whereClause.customerId = customerId; + queryBuilder = queryBuilder.andWhere('contactRecord.customerId = :customerId', { customerId }); } - // Filter by contact type if provided if (contactType) { - whereClause.contactType = contactType; + queryBuilder = queryBuilder.andWhere('contactRecord.contactType = :contactType', { contactType }); } - // Filter by date range if provided - if (dateFrom || dateTo) { - whereClause.createdAt = {}; + if (dateFrom) { + queryBuilder = queryBuilder.andWhere('contactRecord.createdAt >= :dateFrom', { + dateFrom: new Date(dateFrom) + }); + } - if (dateFrom) { - whereClause.createdAt.gte = new Date(dateFrom); + if (dateTo) { + // Set the date to the end of the day for inclusive filtering + const endDate = new Date(dateTo); + endDate.setHours(23, 59, 59, 999); + queryBuilder = queryBuilder.andWhere('contactRecord.createdAt <= :dateTo', { + dateTo: endDate + }); + } + + // Get total count for pagination + const totalCount = await queryBuilder.getCount(); + + // Apply pagination + queryBuilder = queryBuilder + .skip(skip) + .take(pageSize); + + // Execute query + const contactRecords = await queryBuilder.getMany(); + + // Calculate total pages + const totalPages = Math.ceil(totalCount / pageSize); + + return NextResponse.json({ + data: contactRecords, + pagination: { + page, + pageSize, + totalCount, + totalPages } - - if (dateTo) { - // Set the date to the end of the day for inclusive filtering - const endDate = new Date(dateTo); - endDate.setHours(23, 59, 59, 999); - whereClause.createdAt.lte = endDate; - } - } - - // Add where clause to query options if not empty - if (Object.keys(whereClause).length > 0) { - queryOptions.where = whereClause; - } - - const contactRecords = await contactRecordRepository.find(queryOptions); - - return NextResponse.json(contactRecords); + }); } catch (error) { console.error('Error fetching contact records:', error); return NextResponse.json( diff --git a/src/app/api/customers/route.ts b/src/app/api/customers/route.ts index c489f4b..4af014b 100644 --- a/src/app/api/customers/route.ts +++ b/src/app/api/customers/route.ts @@ -7,11 +7,41 @@ export async function GET(request: NextRequest) { const dataSource = await getDataSource(); const customerRepository = dataSource.getRepository(Customer); - const customers = await customerRepository.find({ - order: { createdAt: 'DESC' } - }); + // Get query parameters + const url = new URL(request.url); - return NextResponse.json(customers); + // Pagination parameters + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const skip = (page - 1) * pageSize; + + // Build query + let queryBuilder = customerRepository.createQueryBuilder('customer') + .orderBy('customer.createdAt', 'DESC'); + + // Get total count for pagination + const totalCount = await queryBuilder.getCount(); + + // Apply pagination + queryBuilder = queryBuilder + .skip(skip) + .take(pageSize); + + // Execute query + const customers = await queryBuilder.getMany(); + + // Calculate total pages + const totalPages = Math.ceil(totalCount / pageSize); + + return NextResponse.json({ + data: customers, + pagination: { + page, + pageSize, + totalCount, + totalPages + } + }); } catch (error) { console.error('Error fetching customers:', error); return NextResponse.json( diff --git a/src/app/api/email-templates/route.ts b/src/app/api/email-templates/route.ts index 59b3875..120a50c 100644 --- a/src/app/api/email-templates/route.ts +++ b/src/app/api/email-templates/route.ts @@ -7,11 +7,41 @@ export async function GET(request: NextRequest) { const dataSource = await getDataSource(); const emailTemplateRepository = dataSource.getRepository(EmailTemplate); - const emailTemplates = await emailTemplateRepository.find({ - order: { createdAt: 'DESC' } - }); + // Get query parameters + const url = new URL(request.url); - return NextResponse.json(emailTemplates); + // Pagination parameters + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const skip = (page - 1) * pageSize; + + // Build query + let queryBuilder = emailTemplateRepository.createQueryBuilder('emailTemplate') + .orderBy('emailTemplate.createdAt', 'DESC'); + + // Get total count for pagination + const totalCount = await queryBuilder.getCount(); + + // Apply pagination + queryBuilder = queryBuilder + .skip(skip) + .take(pageSize); + + // Execute query + const emailTemplates = await queryBuilder.getMany(); + + // Calculate total pages + const totalPages = Math.ceil(totalCount / pageSize); + + return NextResponse.json({ + data: emailTemplates, + pagination: { + page, + pageSize, + totalCount, + totalPages + } + }); } catch (error) { console.error('Error fetching email templates:', error); return NextResponse.json( diff --git a/src/app/api/posts/route.ts b/src/app/api/posts/route.ts index 2167f06..a4c2210 100644 --- a/src/app/api/posts/route.ts +++ b/src/app/api/posts/route.ts @@ -11,8 +11,13 @@ export async function GET(request: NextRequest) { const url = new URL(request.url); const parentId = url.searchParams.get('parentId'); + // Pagination parameters + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const skip = (page - 1) * pageSize; + // Build query - let query = postRepository.createQueryBuilder('post') + let queryBuilder = postRepository.createQueryBuilder('post') .leftJoinAndSelect('post.user', 'user') .leftJoinAndSelect('post.parent', 'parent') .orderBy('post.createdAt', 'DESC'); @@ -21,16 +26,36 @@ export async function GET(request: NextRequest) { if (parentId) { if (parentId === 'null') { // Get root posts (no parent) - query = query.where('post.parentId IS NULL'); + queryBuilder = queryBuilder.where('post.parentId IS NULL'); } else { // Get children of specific parent - query = query.where('post.parentId = :parentId', { parentId }); + queryBuilder = queryBuilder.where('post.parentId = :parentId', { parentId }); } } - const posts = await query.getMany(); + // Get total count for pagination + const totalCount = await queryBuilder.getCount(); - return NextResponse.json(posts); + // Apply pagination + queryBuilder = queryBuilder + .skip(skip) + .take(pageSize); + + // Execute query + const posts = await queryBuilder.getMany(); + + // Calculate total pages + const totalPages = Math.ceil(totalCount / pageSize); + + return NextResponse.json({ + data: posts, + pagination: { + page, + pageSize, + totalCount, + totalPages + } + }); } catch (error) { console.error('Error fetching posts:', error); return NextResponse.json( diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 42e615c..5abebc0 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -9,12 +9,42 @@ export async function GET(request: NextRequest) { const dataSource = await getDataSource(); const userRepository = dataSource.getRepository(User); - const users = await userRepository.find({ - select: ['id', 'username', 'avatar', 'createdAt', 'modifiedAt'], - order: { createdAt: 'DESC' } - }); + // Get query parameters + const url = new URL(request.url); - return NextResponse.json(users); + // Pagination parameters + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const skip = (page - 1) * pageSize; + + // Build query + let queryBuilder = userRepository.createQueryBuilder('user') + .select(['user.id', 'user.username', 'user.avatar', 'user.createdAt', 'user.modifiedAt']) + .orderBy('user.createdAt', 'DESC'); + + // Get total count for pagination + const totalCount = await queryBuilder.getCount(); + + // Apply pagination + queryBuilder = queryBuilder + .skip(skip) + .take(pageSize); + + // Execute query + const users = await queryBuilder.getMany(); + + // Calculate total pages + const totalPages = Math.ceil(totalCount / pageSize); + + return NextResponse.json({ + data: users, + pagination: { + page, + pageSize, + totalCount, + totalPages + } + }); } catch (error) { console.error('Error fetching users:', error); return NextResponse.json( diff --git a/src/lib/components/Pagination/index.tsx b/src/lib/components/Pagination/index.tsx new file mode 100644 index 0000000..bfc478a --- /dev/null +++ b/src/lib/components/Pagination/index.tsx @@ -0,0 +1,143 @@ +'use client'; + +import { useRouter, useSearchParams } from 'next/navigation'; + +interface PaginationProps { + currentPage: number; + totalPages: number; + totalItems: number; + pageSize: number; + onPageChange?: (page: number) => void; +} + +export default function Pagination({ + currentPage, + totalPages, + totalItems, + pageSize, + onPageChange +}: PaginationProps) { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Calculate the range of items being displayed + const startItem = (currentPage - 1) * pageSize + 1; + const endItem = Math.min(currentPage * pageSize, totalItems); + + // Generate page numbers to display + const getPageNumbers = () => { + const pages = []; + const maxPagesToShow = 5; // Show at most 5 page numbers + + let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); + let endPage = startPage + maxPagesToShow - 1; + + if (endPage > totalPages) { + endPage = totalPages; + startPage = Math.max(1, endPage - maxPagesToShow + 1); + } + + for (let i = startPage; i <= endPage; i++) { + pages.push(i); + } + + return pages; + }; + + // Handle page change + const handlePageChange = (page: number) => { + if (page < 1 || page > totalPages) return; + + if (onPageChange) { + onPageChange(page); + } else { + // Update URL with new page parameter + const params = new URLSearchParams(searchParams.toString()); + params.set('page', page.toString()); + router.push(`?${params.toString()}`); + } + }; + + if (totalPages <= 1) return null; + + return ( +
+
+ + +
+
+
+

+ Showing {startItem} to{' '} + {endItem} of{' '} + {totalItems} results +

+
+
+ +
+
+
+ ); +}