From 9bdbd058e79f2c762f307d1d9d3af2137e75a70c Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Tue, 10 Mar 2026 15:28:53 +0530 Subject: [PATCH] feat: add pagination --- app/create-location/page.tsx | 27 ++++++++++++----- app/create-package/page.tsx | 23 ++++++++++---- app/create-project/page.tsx | 21 ++++++++++--- components/data-table.tsx | 58 ++++++++++++++++++++++++++++++++++-- lib/api.ts | 41 +++++++++++++++++-------- 5 files changed, 140 insertions(+), 30 deletions(-) diff --git a/app/create-location/page.tsx b/app/create-location/page.tsx index d00bfd7..3d077ed 100644 --- a/app/create-location/page.tsx +++ b/app/create-location/page.tsx @@ -39,6 +39,10 @@ export default function CreateLocationPage() { const [loadingProjects, setLoadingProjects] = useState(false) const [loadingPackages, setLoadingPackages] = useState(false) + // Pagination state + const [skip, setSkip] = useState(0) + const [limit, setLimit] = useState(10) + // Editing state const [isEditing, setIsEditing] = useState(false) const [currentLocation, setCurrentLocation] = useState(null) @@ -55,11 +59,11 @@ export default function CreateLocationPage() { const [endLng, setEndLng] = useState("") // Load locations and projects - const loadLocations = async () => { + const loadLocations = async (currentSkip = skip, currentLimit = limit) => { try { setIsLoading(true) setError(null) - const data = await fetchAllLocations() + const data = await fetchAllLocations({ skip: currentSkip, limit: currentLimit }) setLocations(data) } catch (err) { setError("Failed to load locations. Please check if the backend is running.") @@ -71,7 +75,7 @@ export default function CreateLocationPage() { const loadProjects = async () => { try { setLoadingProjects(true) - const data = await fetchProjects() + const data = await fetchProjects({ skip: 0, limit: 1000 }) setProjects(data) } catch (err) { setError("Failed to load projects.") @@ -82,7 +86,7 @@ export default function CreateLocationPage() { const loadAllPackages = async () => { try { - const data = await fetchAllPackages() + const data = await fetchAllPackages({ skip: 0, limit: 1000 }) setAllPackages(data) } catch (err) { console.error("Failed to load all packages") @@ -90,10 +94,10 @@ export default function CreateLocationPage() { } useEffect(() => { - loadLocations() + loadLocations(skip, limit) loadProjects() loadAllPackages() - }, []) + }, [skip, limit]) // Load packages when project changes useEffect(() => { @@ -107,7 +111,7 @@ export default function CreateLocationPage() { try { setLoadingPackages(true) if (!isEditing) setSelectedPackageId("") - const data = await fetchPackagesByProject(selectedProjectId) + const data = await fetchPackagesByProject(selectedProjectId, { skip: 0, limit: 1000 }) setPackages(data) } catch (err) { setError("Failed to load packages for the selected project.") @@ -325,6 +329,15 @@ export default function CreateLocationPage() { onDelete={handleDelete} addButtonText="Add New Location" isLoading={isLoading} + pagination={{ + skip, + limit, + onPageChange: setSkip, + onLimitChange: (newLimit) => { + setLimit(newLimit); + setSkip(0); // Reset skip when limit changes + } + }} /> diff --git a/app/create-package/page.tsx b/app/create-package/page.tsx index 928b31f..e9d51d4 100644 --- a/app/create-package/page.tsx +++ b/app/create-package/page.tsx @@ -33,6 +33,10 @@ export default function CreatePackagePage() { const [error, setError] = useState(null) const [loadingProjects, setLoadingProjects] = useState(false) + // Pagination state + const [skip, setSkip] = useState(0) + const [limit, setLimit] = useState(10) + // Editing state const [isEditing, setIsEditing] = useState(false) const [currentPackage, setCurrentPackage] = useState(null) @@ -43,11 +47,11 @@ export default function CreatePackagePage() { const [region, setRegion] = useState("") // Load packages and projects - const loadPackages = async () => { + const loadPackages = async (currentSkip = skip, currentLimit = limit) => { try { setIsLoading(true) setError(null) - const data = await fetchAllPackages() + const data = await fetchAllPackages({ skip: currentSkip, limit: currentLimit }) setPackages(data) } catch (err) { setError("Failed to load packages. Please check if the backend is running.") @@ -59,7 +63,7 @@ export default function CreatePackagePage() { const loadProjects = async () => { try { setLoadingProjects(true) - const data = await fetchProjects() + const data = await fetchProjects({ skip: 0, limit: 1000 }) // Load all projects for selector setProjects(data) } catch (err) { setError("Failed to load projects.") @@ -69,9 +73,9 @@ export default function CreatePackagePage() { } useEffect(() => { - loadPackages() + loadPackages(skip, limit) loadProjects() - }, []) + }, [skip, limit]) const resetForm = () => { setSelectedProjectId("") @@ -229,6 +233,15 @@ export default function CreatePackagePage() { onDelete={handleDelete} addButtonText="Add New Package" isLoading={isLoading} + pagination={{ + skip, + limit, + onPageChange: setSkip, + onLimitChange: (newLimit) => { + setLimit(newLimit); + setSkip(0); // Reset skip when limit changes + } + }} /> diff --git a/app/create-project/page.tsx b/app/create-project/page.tsx index 4ab5959..200ac1c 100644 --- a/app/create-project/page.tsx +++ b/app/create-project/page.tsx @@ -20,6 +20,10 @@ export default function CreateProjectPage() { const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState(null) + // Pagination state + const [skip, setSkip] = useState(0) + const [limit, setLimit] = useState(10) + // Editing state const [isEditing, setIsEditing] = useState(false) const [currentProject, setCurrentProject] = useState(null) @@ -34,11 +38,11 @@ export default function CreateProjectPage() { const [endLng, setEndLng] = useState("") // Load projects - const loadProjects = async () => { + const loadProjects = async (currentSkip = skip, currentLimit = limit) => { try { setIsLoading(true) setError(null) - const data = await fetchProjects() + const data = await fetchProjects({ skip: currentSkip, limit: currentLimit }) setProjects(data) } catch (err) { setError("Failed to load projects. Please check if the backend is running.") @@ -48,8 +52,8 @@ export default function CreateProjectPage() { } useEffect(() => { - loadProjects() - }, []) + loadProjects(skip, limit) + }, [skip, limit]) const resetForm = () => { setName("") @@ -210,6 +214,15 @@ export default function CreateProjectPage() { onDelete={handleDelete} addButtonText="Add New Project" isLoading={isLoading} + pagination={{ + skip, + limit, + onPageChange: setSkip, + onLimitChange: (newLimit) => { + setLimit(newLimit); + setSkip(0); // Reset skip when limit changes + } + }} /> diff --git a/components/data-table.tsx b/components/data-table.tsx index 4a48344..216ff80 100644 --- a/components/data-table.tsx +++ b/components/data-table.tsx @@ -1,6 +1,6 @@ "use client" -import { Plus, Edit2, Trash2 } from "lucide-react" +import { Plus, Edit2, Trash2, ChevronLeft, ChevronRight } from "lucide-react" import { Button } from "@/components/ui/button" import { Tooltip, @@ -25,6 +25,13 @@ interface DataTableProps { isLoading?: boolean onEdit?: (item: T) => void onDelete?: (item: T) => void + pagination?: { + skip: number + limit: number + totalItems?: number + onPageChange: (newSkip: number) => void + onLimitChange: (newLimit: number) => void + } } export function DataTable>({ @@ -35,7 +42,8 @@ export function DataTable>({ addButtonText, isLoading = false, onEdit, - onDelete + onDelete, + pagination }: DataTableProps) { const showActions = !!onEdit || !!onDelete; const totalCols = columns.length + (showActions ? 1 : 0); @@ -171,6 +179,52 @@ export function DataTable>({ + + {/* Pagination Controls */} + {pagination && ( +
+
+
+ Rows per page: + +
+ + Showing {pagination.skip + 1} - {pagination.skip + data.length} + {pagination.totalItems !== undefined && ` of ${pagination.totalItems}`} + +
+
+ + +
+
+ )} ) } diff --git a/lib/api.ts b/lib/api.ts index bedd80e..2726c93 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -43,6 +43,11 @@ export interface Location { updated_at: string } +export interface PaginationParams { + skip?: number; + limit?: number; +} + // Helper function for GET API requests async function apiRequest(endpoint: string): Promise { const response = await fetch(`${API_URL}${endpoint}`, { @@ -241,36 +246,46 @@ export async function deleteLocation(locationId: string): Promise<{ message: str /** * Fetch all projects */ -export async function fetchProjects(): Promise { - return apiRequest("/projects/") +export async function fetchProjects(params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 + return apiRequest(`/projects/?skip=${skip}&limit=${limit}`) } /** * Fetch packages filtered by project ID */ -export async function fetchPackagesByProject(projectId: string): Promise { - return apiRequest(`/packages/?project_id=${projectId}`) +export async function fetchPackagesByProject(projectId: string, params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 + return apiRequest(`/packages/?project_id=${projectId}&skip=${skip}&limit=${limit}`) } /** * Fetch locations filtered by package ID */ -export async function fetchLocationsByPackage(packageId: string): Promise { - return apiRequest(`/locations/?package_id=${packageId}`) +export async function fetchLocationsByPackage(packageId: string, params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 + return apiRequest(`/locations/?package_id=${packageId}&skip=${skip}&limit=${limit}`) } /** * Fetch all packages (for dashboard) */ -export async function fetchAllPackages(): Promise { - return apiRequest("/packages/") +export async function fetchAllPackages(params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 + return apiRequest(`/packages/?skip=${skip}&limit=${limit}`) } /** * Fetch all locations (for dashboard) */ -export async function fetchAllLocations(): Promise { - return apiRequest("/locations/") +export async function fetchAllLocations(params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 + return apiRequest(`/locations/?skip=${skip}&limit=${limit}`) } // Video type for dashboard @@ -293,7 +308,9 @@ export interface Video { /** * Fetch all videos (for dashboard) */ -export async function fetchVideos(): Promise { +export async function fetchVideos(params?: PaginationParams): Promise { + const skip = params?.skip ?? 0 + const limit = params?.limit ?? 100 const response = await apiRequest<{ videos: Array<{ video_id: string; @@ -309,7 +326,7 @@ export async function fetchVideos(): Promise { total_detections?: number; } }> - }>("/videos") + }>(`/videos?skip=${skip}&limit=${limit}`) // Transform the response to match our Video interface return response.videos.map(v => ({