mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
feat: Add pagination to people overview (#830)
* feat: paginante people page
* undo: delete env example
* fix: rename vars and return empty arr over err
* fix: invalid page logic
* fix: handle bigger cases
* increase PEOPLE_PER_PAGE to 50
* fix: show no people when added invalid page no
* feat: add caching in people fetch services
* Revert "feat: add caching in people fetch services"
This reverts commit ab1e98c05e.
* fix build errors
---------
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
48921663be
commit
83bc8a8c11
@@ -2,17 +2,35 @@ export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller";
|
||||
import { truncateMiddle } from "@/lib/utils";
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { getPeople } from "@formbricks/lib/services/person";
|
||||
import { PEOPLE_PER_PAGE, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { getPeople, getPeopleCount } from "@formbricks/lib/services/person";
|
||||
import { TPerson } from "@formbricks/types/v1/people";
|
||||
import { PersonAvatar } from "@formbricks/ui";
|
||||
import { Pagination, PersonAvatar } from "@formbricks/ui";
|
||||
import Link from "next/link";
|
||||
|
||||
const getAttributeValue = (person: TPerson, attributeName: string) =>
|
||||
person.attributes[attributeName]?.toString();
|
||||
|
||||
export default async function PeoplePage({ params }) {
|
||||
const people = await getPeople(params.environmentId);
|
||||
export default async function PeoplePage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { environmentId: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const pageNumber = searchParams.page ? parseInt(searchParams.page as string) : 1;
|
||||
const totalPeople = await getPeopleCount(params.environmentId);
|
||||
const maxPageNumber = Math.ceil(totalPeople / PEOPLE_PER_PAGE);
|
||||
let hidePagination = false;
|
||||
|
||||
let people: TPerson[] = [];
|
||||
|
||||
if (pageNumber < 1 || pageNumber > maxPageNumber) {
|
||||
people = [];
|
||||
hidePagination = true;
|
||||
} else {
|
||||
people = await getPeople(params.environmentId, pageNumber);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -64,6 +82,14 @@ export default async function PeoplePage({ params }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{hidePagination ? null : (
|
||||
<Pagination
|
||||
baseUrl={`/environments/${params.environmentId}/people`}
|
||||
currentPage={pageNumber}
|
||||
totalItems={totalPeople}
|
||||
itemsPerPage={PEOPLE_PER_PAGE}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
function Pagination({ environmentId, currentPage, totalItems, itemsPerPage }) {
|
||||
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
||||
|
||||
const previousPageLink =
|
||||
currentPage === 1 ? "#" : `/environments/${environmentId}/people?page=${currentPage - 1}`;
|
||||
const nextPageLink =
|
||||
currentPage === totalPages ? "#" : `/environments/${environmentId}/people?page=${currentPage + 1}`;
|
||||
|
||||
return (
|
||||
<nav aria-label="Page navigation" className="flex justify-center">
|
||||
<ul className="mt-4 inline-flex -space-x-px text-sm">
|
||||
<li>
|
||||
<a
|
||||
href={previousPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-l-lg border border-gray-300 bg-white px-3 text-gray-500 ${
|
||||
currentPage === 1
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{Array.from({ length: totalPages }).map((_, idx) => {
|
||||
const pageNum = idx + 1;
|
||||
const pageLink = `/environments/${environmentId}/people?page=${pageNum}`;
|
||||
|
||||
return (
|
||||
<li key={pageNum} className="hidden sm:block">
|
||||
<a
|
||||
href={pageNum === currentPage ? "#" : pageLink}
|
||||
className={`flex h-8 items-center justify-center px-3 ${
|
||||
pageNum === currentPage ? "bg-blue-50 text-green-500" : "bg-white text-gray-500"
|
||||
} border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white`}>
|
||||
{pageNum}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
<li>
|
||||
<a
|
||||
href={nextPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-r-lg border border-gray-300 bg-white px-3 text-gray-500 ${
|
||||
currentPage === totalPages
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
@@ -25,3 +25,5 @@ export const SURVEY_BASE_URL = process.env.NEXT_PUBLIC_SURVEY_BASE_URL
|
||||
export const INTERNAL_SECRET = process.env.INTERNAL_SECRET || "";
|
||||
export const CRON_SECRET = process.env.CRON_SECRET;
|
||||
export const DEFAULT_BRAND_COLOR = "#64748b";
|
||||
|
||||
export const PEOPLE_PER_PAGE = 50;
|
||||
|
||||
@@ -2,11 +2,12 @@ import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { DatabaseError } from "@formbricks/types/v1/errors";
|
||||
import { TPerson } from "@formbricks/types/v1/people";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { cache } from "react";
|
||||
import { PEOPLE_PER_PAGE } from "../constants";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { getAttributeClassByName } from "./attributeClass";
|
||||
|
||||
@@ -100,20 +101,24 @@ export const getPersonCached = async (personId: string) =>
|
||||
}
|
||||
)();
|
||||
|
||||
export const getPeople = cache(async (environmentId: string): Promise<TPerson[]> => {
|
||||
export const getPeople = cache(async (environmentId: string, page: number = 1): Promise<TPerson[]> => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
try {
|
||||
const personsPrisma = await prisma.person.findMany({
|
||||
const itemsPerPage = PEOPLE_PER_PAGE;
|
||||
const people = await prisma.person.findMany({
|
||||
where: {
|
||||
environmentId: environmentId,
|
||||
},
|
||||
select: selectPerson,
|
||||
take: itemsPerPage,
|
||||
skip: itemsPerPage * (page - 1),
|
||||
});
|
||||
if (!personsPrisma) {
|
||||
throw new ResourceNotFoundError("Persons", "All Persons");
|
||||
|
||||
if (!people || people.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const transformedPeople: TPerson[] = personsPrisma
|
||||
const transformedPeople: TPerson[] = people
|
||||
.map(transformPrismaPerson)
|
||||
.filter((person: TPerson | null): person is TPerson => person !== null);
|
||||
|
||||
@@ -127,6 +132,24 @@ export const getPeople = cache(async (environmentId: string): Promise<TPerson[]>
|
||||
}
|
||||
});
|
||||
|
||||
export const getPeopleCount = cache(async (environmentId: string): Promise<number> => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
try {
|
||||
const totalCount = await prisma.person.count({
|
||||
where: {
|
||||
environmentId: environmentId,
|
||||
},
|
||||
});
|
||||
return totalCount;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
export const createPerson = async (environmentId: string): Promise<TPerson> => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
try {
|
||||
|
||||
72
packages/ui/components/Pagination.tsx
Normal file
72
packages/ui/components/Pagination.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
function Pagination({
|
||||
baseUrl,
|
||||
currentPage,
|
||||
totalItems,
|
||||
itemsPerPage,
|
||||
}: {
|
||||
baseUrl: string;
|
||||
currentPage: number;
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
}) {
|
||||
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
||||
|
||||
const previousPageLink = currentPage === 1 ? "#" : `${baseUrl}?page=${currentPage - 1}`;
|
||||
const nextPageLink = currentPage === totalPages ? "#" : `${baseUrl}?page=${currentPage + 1}`;
|
||||
|
||||
const getDisplayedPages = () => {
|
||||
if (totalPages <= 20) {
|
||||
return Array.from({ length: totalPages }, (_, idx) => idx + 1);
|
||||
} else {
|
||||
let range = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2];
|
||||
return [1, ...range.filter((n) => n > 1 && n < totalPages), totalPages];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav aria-label="Page navigation" className="flex justify-center">
|
||||
<ul className="mt-4 inline-flex -space-x-px text-sm">
|
||||
<li>
|
||||
<a
|
||||
href={previousPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-l-lg border border-gray-300 bg-white px-3 text-gray-500 ${
|
||||
currentPage === 1
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{getDisplayedPages().map((pageNum) => {
|
||||
const pageLink = `${baseUrl}?page=${pageNum}`;
|
||||
return (
|
||||
<li key={pageNum} className="hidden sm:block">
|
||||
<a
|
||||
href={pageNum === currentPage ? "#" : pageLink}
|
||||
className={`flex h-8 items-center justify-center px-3 ${
|
||||
pageNum === currentPage ? "bg-blue-50 text-green-500" : "bg-white text-gray-500"
|
||||
} border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white`}>
|
||||
{pageNum}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
<li>
|
||||
<a
|
||||
href={nextPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-r-lg border border-gray-300 bg-white px-3 text-gray-500 ${
|
||||
currentPage === totalPages
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export { Pagination };
|
||||
@@ -50,6 +50,7 @@ export {
|
||||
export { ErrorComponent } from "./components/ErrorComponent";
|
||||
export { Input } from "./components/Input";
|
||||
export { Label } from "./components/Label";
|
||||
export { Pagination } from "./components/Pagination";
|
||||
export { PasswordInput } from "./components/PasswordInput";
|
||||
export { Popover, PopoverContent, PopoverTrigger } from "./components/Popover";
|
||||
export { HalfCircle, ProgressBar } from "./components/ProgressBar";
|
||||
|
||||
Reference in New Issue
Block a user