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:
Shubham Palriwala
2023-09-27 15:29:44 +05:30
committed by GitHub
parent 48921663be
commit 83bc8a8c11
6 changed files with 192 additions and 11 deletions

View File

@@ -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}
/>
)}
</>
);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View 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 };

View File

@@ -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";