Skip to content

Commit

Permalink
Adding Pagination to the Babies and Caregivers Tables (#136)
Browse files Browse the repository at this point in the history
* feat: pagination component

* feat: pagination backend and adding to component, creating pagination component

* fix: page size and svg arrow symbols

* Move consts to utils to fix build error
  • Loading branch information
uma-anand authored Sep 24, 2024
1 parent 125af42 commit 74dd60e
Show file tree
Hide file tree
Showing 22 changed files with 1,235 additions and 504 deletions.
96 changes: 96 additions & 0 deletions web/components/molecules/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { PAGINATION_PAGE_SIZE } from "db/consts";
import React from "react";

const pageSize = PAGINATION_PAGE_SIZE;

const getFirstRecordOnPage = (currPage: number) =>
(currPage - 1) * pageSize + 1;

const getLastRecordOnPage = (
firstRecordOnPage: number,
totalRecords: number
) => Math.min(firstRecordOnPage + pageSize - 1, totalRecords);

const isValidPage = (firstRecordOnPage: number, totalRecords: number) =>
firstRecordOnPage > 0 && firstRecordOnPage <= totalRecords;

const LeftArrow = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15.1599 7.41L10.5799 12L15.1599 16.59L13.7499 18L7.74991 12L13.7499 6L15.1599 7.41Z" fill="currentColor"/>
</svg>
);

const RightArrow = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M8.84009 7.41L13.4201 12L8.84009 16.59L10.2501 18L16.2501 12L10.2501 6L8.84009 7.41Z" fill="currentColor"/>
</svg>
);

const getArrows = (
firstRecordOnPage: number,
totalRecords: number,
currPage: number,
onNextPage: any,
onPrevPage: any
) => [
{ symbol: <LeftArrow/>, disabled: firstRecordOnPage <= pageSize, onClick: onPrevPage },
{ symbol: <RightArrow/>, disabled: totalRecords <= currPage * pageSize, onClick: onNextPage },
];

const ArrowButton = ({
symbol,
disabled,
onClick
}: {
symbol: JSX.Element;
disabled: boolean;
onClick: () => void;
}) => (
<div
className={`w-8 h-8 rounded border flex justify-center items-center font-bold text-xl ${
disabled ? "text-gray-300 cursor-not-allowed" : "text-black cursor-pointer"
}`}
onClick={!disabled ? onClick : undefined}
>
{symbol}
</div>
);

function Pagination({
totalRecords,
currPage,
onNextPage,
onPrevPage
}: {
totalRecords: number;
currPage: number;
onNextPage: any;
onPrevPage: any;
}) {
const firstRecordOnPage = getFirstRecordOnPage(currPage);
const lastRecordOnPage = getLastRecordOnPage(
firstRecordOnPage,
totalRecords
);

if (!isValidPage(firstRecordOnPage, totalRecords)) {
return null;
}

const arrows = getArrows(firstRecordOnPage, totalRecords, currPage, onNextPage, onPrevPage);

return (
<div className="flex items-center gap-3 justify-end">
<h1 className="text-sm font-bold">
{firstRecordOnPage} - {lastRecordOnPage} of {totalRecords}
</h1>
<div className="flex items-center gap-2">
{arrows.map(({ symbol, disabled, onClick }, index) => (
<ArrowButton key={index} symbol={symbol} disabled={disabled} onClick={onClick} />
))}
</div>
</div>
);
}

export default Pagination;
49 changes: 49 additions & 0 deletions web/components/molecules/Pagination/PaginationHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CollectionPath } from "@lib/types/db";
import { getTotalRecords } from "db/firebase/getSize";
import { useEffect, useState } from "react";

export function usePaginatedData<T>(fetchPage: (page: number, paginationReferences: Map<number, any>) => Promise<{ data: T[], paginationInfo: any }>, type: CollectionPath) {
const [paginationReferences, setPaginationReferences] = useState<Map<number, any>>(new Map());
const [currPage, setCurrPage] = useState(1);
const [data, setData] = useState<T[]>([]);
const [totalRecords, setTotalRecords] = useState<number>(0);

const fetchTotalRecords = async () => {
try {
const numRecords = await getTotalRecords(type);
if (numRecords) {
setTotalRecords(numRecords);
}
} catch (error) {
console.error('Error fetching records.');
}
};

const updatePaginationReferences = async (paginationInfo: any) => {
setPaginationReferences(prev => {
const newRefs = new Map(prev);
newRefs.set(currPage, paginationInfo);
return newRefs;
});
};

const fetchPageData = async () => {
const newPageData = await fetchPage(currPage, paginationReferences);
setData(newPageData.data);
updatePaginationReferences(newPageData.paginationInfo);
};

useEffect(() => {
fetchPageData();
fetchTotalRecords();
}, [currPage]);

return {
data,
totalRecords,
currPage,
setCurrPage,
onNextPage: () => setCurrPage(prev => prev + 1),
onPrevPage: () => setCurrPage(prev => prev - 1)
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import ChildModal from "@components/modals/addChildModal";
import Image from "next/image";
import React, { useState } from "react";
import { useTable } from "react-table";
import book from "../public/book.svg";
import dots from "../public/dots.png";
import Modal from "./modal";
import book from "../../public/book.svg";
import dots from "../../public/dots.png";
import Modal from "@components/modal";

function BabiesTable({ columns, data, onEdit, caretakers, onDelete }: any) {
function BabiesTable({props}: any) {
if (!props) {
return <></>;
}

const { columns, data, onEdit, caretakers, onDelete } = props;
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({
columns,
Expand Down Expand Up @@ -107,7 +112,7 @@ function BabiesTable({ columns, data, onEdit, caretakers, onDelete }: any) {
onClick={() => {
confirm(
"Are you sure you want to delete this baby?"
) && onDelete(row.original);
) && onDelete(row.original) && setSelectedOptionsPanel(-1);
}}
className="block px-4 py-2 text-red-500 hover:bg-gray-100 cursor-pointer"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import React from "react";
import { useTable } from "react-table";
import { HiOutlineTrash } from "react-icons/hi";
import { RiArrowDropDownLine } from "react-icons/ri";
import Tooltip from "./ToolTip";
import Tooltip from "../ToolTip";
import Link from "next/link";

function CaretakerTable({ columns, data, onDelete }: any) {
function CaretakerTable({props}: any) {
if (!props) {
return <></>;
}

const { columns, data, onDelete } = props;
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({
columns,
data,
});

const [open, setOpen] = React.useState(Array(data.length).fill(false));

const metadata = {
Address: "address",
"Address": "address",
"Pref. Communication": "prefferedCommunication",
"Child Name": "childName",
"Household Info": "houseHoldInfo",
Expand Down
23 changes: 23 additions & 0 deletions web/components/tables/PaginatedTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import Pagination from "@components/molecules/Pagination/Pagination";
import CaretakerTable from "@components/tables/CaretakerTable";
import BabiesTable from "@components/tables/BabiesTable";
import { BABIES_TAB, CAREGIVERS_TAB } from "@lib/utils/consts";

export default function PaginatedTable({type, tableProps, paginatedProps, onNextPage, onPrevPage}: any) {
return (
<div className="flex flex-col gap-4 mb-3 w-[65vw]">
<div className="overflow-auto h-[70vh]">
{(type == BABIES_TAB) ? <BabiesTable props={tableProps} /> :
(type == CAREGIVERS_TAB)? <CaretakerTable props={tableProps}/> : <></>}
</div>
<div>
<Pagination
totalRecords={paginatedProps.totalRecords}
currPage={paginatedProps.pageNumber}
onNextPage={onNextPage}
onPrevPage={onPrevPage}
/>
</div>
</div>)
}
53 changes: 18 additions & 35 deletions web/db/actions/admin/Baby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,30 @@ import {
serverTimestamp,
updateDoc,
query,
Timestamp,
} from "firebase/firestore";

import { Baby } from "@lib/types/baby";
import { encrypt } from "@lib/utils/encryption";

import { db } from "db/firebase";
import { PaginationInfoType, PaginationReferencesType } from "@lib/types/common";
import { BABIES_COLLECTION_PATH, CAREGIVERS_COLLECTION_PATH } from "db/consts";
import { getQueryConstraintsFromPagination } from "@lib/utils/pagination";
import getBabiesFromBabyDocs from "@lib/utils/baby";

export async function getBabies() {
const itemsRef = query(collection(db, "babies"));
const babyDocs = await getDocs(itemsRef);

const babies = await Promise.all(
babyDocs?.docs.map(async (babyDoc: any) => {
const data = babyDoc.data() as Baby;

const dobDate = new Timestamp(
data.dob.seconds,
data.dob.nanoseconds
).toDate();
const path = BABIES_COLLECTION_PATH;
const caregiverPath = CAREGIVERS_COLLECTION_PATH;

const { iv, content } = encrypt(babyDoc.id);

return {
id: babyDoc.id,
firstName: data.firstName,
lastName: data.lastName,
name: data?.firstName ?? "" + " " + data?.lastName ?? "",
motherName: data?.motherName || null,
birthday: dobDate?.toLocaleDateString("en-us") || null,
sex: data?.sex || null,
babyBook: `/admin/book/${content}?iv=${iv}`,
};
})
);

return babies;
export async function getBabyPage(pageNumber: number, paginationReferences: PaginationReferencesType) {
const constraints = getQueryConstraintsFromPagination(path, pageNumber, paginationReferences);
const itemsRef = query(collection(db, path), ...constraints);
const babyDocs = await getDocs(itemsRef);
const babies = await getBabiesFromBabyDocs(babyDocs);
const paginationInfo: PaginationInfoType = {pageNumber: pageNumber, startAfter: babyDocs?.docs[babies.length - 1]}
return {data: babies, paginationInfo: paginationInfo};
}

export async function getCaregiversInfo() {
const q = query(collection(db, "caregivers"));
const q = query(collection(db, caregiverPath));
const res = await getDocs(q);

const caregivers = res.docs.map((doc) => ({
Expand All @@ -61,10 +44,10 @@ export async function getCaregiversInfo() {
export const addNewChild = async (child: Baby) => {
let caretakerRef = null;
if (child.caretakerID) {
caretakerRef = doc(db, "caregivers", child.caretakerID);
caretakerRef = doc(db, caregiverPath, child.caretakerID);
}

const newBaby = await addDoc(collection(db, "babies"), {
const newBaby = await addDoc(collection(db, path), {
...child,
dob: child.dob,
createdAt: serverTimestamp(),
Expand All @@ -83,10 +66,10 @@ export const editBaby = async (baby: any) => {
// TODO find out why deleting baby.id and if needed
const babyID = baby.id;
delete baby.id;
await updateDoc(doc(db, "babies", babyID), baby);
await updateDoc(doc(db, path, babyID), baby);
};

export const deleteBaby = async (baby: any) => {
const babyID = baby.id;
await deleteDoc(doc(db, "babies", babyID));
await deleteDoc(doc(db, path, babyID));
};
51 changes: 19 additions & 32 deletions web/db/actions/admin/Caregiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,39 @@ import {
doc,
deleteDoc,
collection,
getDoc,
getDocs,
query,
} from "firebase/firestore";

import { formatPhoneNumber } from "@lib/utils/contactInfo";
import { db } from "db/firebase";

import { CaregiverDisplay } from "pages/admin/caregivers";
import { PaginationInfoType, PaginationReferencesType } from "@lib/types/common";
import { getQueryConstraintsFromPagination } from "@lib/utils/pagination";
import { CAREGIVERS_COLLECTION_PATH } from "db/consts";
import getCaregiversFromCaregiverDocs from "@lib/utils/caregiver";

const path = CAREGIVERS_COLLECTION_PATH;

export async function getCaregivers() {
const itemsRef = query(collection(db, "caregivers"));
const itemsRef = query(collection(db, path));
const caregiverDocs = await getDocs(itemsRef);

const caregivers: Partial<CaregiverDisplay>[] = [];

// TODO update for multiple children
caregiverDocs.forEach(async (doc) => {
const data = doc.data();
const child: any = data.baby ? (await getDoc(data.baby)).data() : null;
caregivers.push({
id: doc.id,
name: data.firstName + " " + data.lastName,
email: data.email || "N/A",
phone: (data.phoneNumber && formatPhoneNumber(data.phoneNumber)) || "N/A",
registeredDate: data.createdAt
? data.createdAt.toDate().toLocaleDateString()
: null,
assigned: child ? true : false,
address: `${data.address}, ${
data.apartment ? `${data.apartment}, ` : ""
}${data.city}, ${data.state}`,
prefferedCommunication: data.prefferedCommunication || "N/A",
childName: child ? child.firstName + " " + child.lastName : null,
houseHoldInfo: `${data.numAdults} adults, ${data.numChildren} children`,
// liabilityWaiver: data.signedWaivers?.at(-1).id || null,
liabilityWaiver: "",
});
});

// TODO catch errors
const caregivers = await getCaregiversFromCaregiverDocs(caregiverDocs);

return caregivers;
}


export async function getCaregiverPage(pageNumber: number, paginationReferences: PaginationReferencesType) {
const constraints = getQueryConstraintsFromPagination(path, pageNumber, paginationReferences)
const itemsRef = query(collection(db, path), ...constraints);
const caregiverDocs = await getDocs(itemsRef);
const caregivers = await getCaregiversFromCaregiverDocs(caregiverDocs);
const paginationInfo: PaginationInfoType = {pageNumber: pageNumber, startAfter: caregiverDocs?.docs[caregivers.length - 1]}
return {data: caregivers, paginationInfo: paginationInfo};
}

export const deleteCaretaker = async (caretakerID: string) => {
// TODO catch errors
await deleteDoc(doc(db, "caregivers", caretakerID));
await deleteDoc(doc(db, path, caretakerID));
};
Loading

0 comments on commit 74dd60e

Please sign in to comment.