diff --git a/app/listings/actions.ts b/app/listings/actions.ts new file mode 100644 index 00000000..0dbe3530 --- /dev/null +++ b/app/listings/actions.ts @@ -0,0 +1,45 @@ +"use server"; + +import prisma from "@/lib/db"; +import { LISTINGS_PER_PAGE } from "./constants"; +import { countListingsToSkip } from "./function"; +import { Listing } from "./types"; + +export async function fetchListingsByPage(currentPage: number) { + const dbListings = await prisma.listing.findMany({ + skip: countListingsToSkip(currentPage, LISTINGS_PER_PAGE), + take: LISTINGS_PER_PAGE, + include: { + address: true, + prices: true, + }, + }); + const processedListings = dbListings.map((dbListing) => { + const dbListingPrice = dbListing.prices + .filter((dbListingPrice) => dbListingPrice.isCurrent) + .at(0)! + .value.toNumber(); + const dbListingAddress = dbListing.address!; + const listing: Listing = { + id: dbListing.id, + imageUrls: dbListing.imageUrls, + beds: dbListing.beds, + baths: dbListing.baths, + area: dbListing.area.toNumber(), + createdDate: dbListing.createdDate, + address: { + addressLine: dbListingAddress.addressLine, + city: dbListingAddress.city, + state: dbListingAddress.state, + longitude: dbListingAddress.longitude.toNumber(), + latitude: dbListingAddress.latitude.toNumber(), + }, + price: { + value: dbListingPrice, + }, + }; + return listing; + }); + + return processedListings; +} diff --git a/app/listings/constants.ts b/app/listings/constants.ts index 54c1093f..11eb6262 100644 --- a/app/listings/constants.ts +++ b/app/listings/constants.ts @@ -1,2 +1,2 @@ -export const LISTINGS_PER_PAGE = 32; +export const LISTINGS_PER_PAGE = 5; export const STARTING_PAGE = 1; diff --git a/app/listings/content.tsx b/app/listings/content.tsx index 3baac70a..f155d249 100644 --- a/app/listings/content.tsx +++ b/app/listings/content.tsx @@ -1,21 +1,37 @@ +"use client" + +import { useContext, useEffect, useState } from "react" +import { fetchListingsByPage } from "./actions" import ListingsList from "./list" import { ListingsListTop } from "./list-top" import { ListingsMap } from "./map" import ListingsPagination from "./pagination" +import { ListingsContext } from "./provider" import { Listing } from "./types" export interface ListingsContentProps { - listings: Listing[] pages: number } export default function ListingsContent(props: ListingsContentProps) { + const [listings, setListings] = useState([]); + const context = useContext(ListingsContext); + const currentPage = context.pagination.currentPage.value; + + useEffect(() => { + async function updateListings() { + const newListings = await fetchListingsByPage(currentPage); + setListings(newListings); + } + updateListings(); + }, [currentPage]) + return (
- +
diff --git a/app/listings/filter-beds-baths.tsx b/app/listings/filter-beds-baths.tsx index fd8fcc66..1fa0b653 100644 --- a/app/listings/filter-beds-baths.tsx +++ b/app/listings/filter-beds-baths.tsx @@ -7,9 +7,11 @@ export default function ListingsFiltersBedsBaths() { return (
+ values={bedsBathsOptions} + activeIndex={0} /> + values={bedsBathsOptions} + activeIndex={0} />
); diff --git a/app/listings/list.tsx b/app/listings/list.tsx index 26fffb5c..90c67e35 100644 --- a/app/listings/list.tsx +++ b/app/listings/list.tsx @@ -2,20 +2,17 @@ import CardListing from "@/components/card-listing"; import { CURRENCY_FORMATTER } from "@/lib/formatter/currency"; -import { useState } from "react"; import { Listing } from "./types"; -export interface ListingsListInterface { +export interface ListingsListProps { listings: Listing[] } -export default function ListingsList(props: ListingsListInterface) { - const [listings, setListings] = useState(props.listings); - +export default function ListingsList(props: ListingsListProps) { return (
- {listings.map((listing) => { + {props.listings.map((listing) => { const priceFormatted = CURRENCY_FORMATTER.format(listing.price.value) return ( { - const dbListingPrice = dbListing.prices.filter((dbListingPrice) => dbListingPrice.isCurrent).at(0)!.value.toNumber(); - const dbListingAddress = dbListing.address!; - const listing: Listing = { - id: dbListing.id, - imageUrls: dbListing.imageUrls, - beds: dbListing.beds, - baths: dbListing.baths, - area: dbListing.area.toNumber(), - createdDate: dbListing.createdDate, - address: { - addressLine: dbListingAddress.addressLine, - city: dbListingAddress.city, - state: dbListingAddress.state, - longitude: dbListingAddress.longitude.toNumber(), - latitude: dbListingAddress.latitude.toNumber(), - }, - price: { - value: dbListingPrice - } - } - return listing - }) - - + // TODO: Move down and the count must be equal to the number of matched listings based on filter values const listingsCount = await prisma.listing.count() const pages = Math.ceil(listingsCount / LISTINGS_PER_PAGE); @@ -52,7 +13,7 @@ export default async function Listings() {
- +
); diff --git a/app/listings/pagination.tsx b/app/listings/pagination.tsx index 91b32acb..1173fc53 100644 --- a/app/listings/pagination.tsx +++ b/app/listings/pagination.tsx @@ -13,7 +13,13 @@ export default function ListingsPagination(props: ListingsPaginationProps) { return (
- +
); } diff --git a/app/listings/provider.tsx b/app/listings/provider.tsx index 13eb79ce..ded3dcfc 100644 --- a/app/listings/provider.tsx +++ b/app/listings/provider.tsx @@ -1,25 +1,52 @@ "use client" -import { createContext } from "react" +import { createContext, useState } from "react" import { STARTING_PAGE } from "./constants" -export interface ListingsContextInterface { +interface ListingsContextInterface { pagination: { - currentPage: number + changeToPreviousPage: () => void, + changeToNextPage: () => void, + currentPage: { + value: number, + change: (page: number) => void + } } } -const INITIAL_STATE: ListingsContextInterface = { +export const ListingsContext = createContext({ pagination: { - currentPage: STARTING_PAGE + changeToPreviousPage: () => { }, + changeToNextPage: () => { }, + currentPage: { + value: STARTING_PAGE, + change: (page) => { } + }, } -} - -export const ListingsContext = createContext(INITIAL_STATE); +}); export default function ListingsProvider({ children }: { children: React.ReactNode }) { + const [currentPage, setCurrentPage] = useState(STARTING_PAGE); + + const stateValue: ListingsContextInterface = { + pagination: { + changeToPreviousPage: () => { + setCurrentPage(currentPage - 1) + }, + changeToNextPage: () => { + setCurrentPage(currentPage + 1) + }, + currentPage: { + value: currentPage, + change: (page: number) => { + setCurrentPage(page) + }, + } + } + } + return ( - + {children} ) diff --git a/components/buttons-segmented/labelled.tsx b/components/buttons-segmented/labelled.tsx index 2059d44a..e48e431c 100644 --- a/components/buttons-segmented/labelled.tsx +++ b/components/buttons-segmented/labelled.tsx @@ -13,4 +13,4 @@ export default function ButtonsSegmentedLabelled(props: ButtonsSegmentedLabelled
); -} \ No newline at end of file +} diff --git a/components/pagination/function.ts b/components/pagination/function.ts index a48aaf7d..c3dda17f 100644 --- a/components/pagination/function.ts +++ b/components/pagination/function.ts @@ -1,21 +1,21 @@ /** * - * Show the previous button only if the current page is not the first page + * Enable the previous button only if the current page is not the first page * * @param currentPage Current page - * @returns `true` if the previous button should be shown, otherwise false. + * @returns `true` if the previous button should be enabled, otherwise false. */ -export function checkShowPreviousButton(currentPage: number) { +export function checkPreviousButton(currentPage: number) { return currentPage > 1; } /** - * Show the next button only if the current page is not the last page + * Enable the next button only if the current page is not the last page * * @param pages Total number of pages * @param currentPage Current page - * @returns `true` if the next button should be shown, otherwise false. + * @returns `true` if the next button should be enabled, otherwise false. */ -export function checkShowNextButton(pages: number, currentPage: number) { +export function checkNextButton(pages: number, currentPage: number) { return currentPage < pages; } diff --git a/components/pagination/index.tsx b/components/pagination/index.tsx index 67cf5297..ece42897 100644 --- a/components/pagination/index.tsx +++ b/components/pagination/index.tsx @@ -1,43 +1,64 @@ -import { checkShowNextButton, checkShowPreviousButton } from "./function" +import { checkNextButton, checkPreviousButton } from "./function" import PaginationItemNav from "./item-nav" import PaginationItemNumber from "./item-number" export interface PaginationProps { pages: number currentPage: number + changeToPreviousPage?: () => void + changeCurrentPage?: (page: number) => void + changeToNextPage?: () => void } export default function Pagination(props: PaginationProps) { const pageNumbers = Array.from({ length: props.pages }, (elem, i) => i + 1) - const showPreviousButton = checkShowPreviousButton(props.currentPage); - const showNextButton = checkShowNextButton(props.pages, props.currentPage); + const previousButtonEnabled = checkPreviousButton(props.currentPage); + const nextButtonEnabled = checkNextButton(props.pages, props.currentPage); return (
{/* Previous (hidden if the current page is the first page) */} - {showPreviousButton && +
{ + if (props.changeToPreviousPage && previousButtonEnabled) { + props.changeToPreviousPage() + } + }}> - } +
{pageNumbers.map((pageNumber) => { const isCurrentPage = props.currentPage === pageNumber - return + return ( +
{ + if (props.changeCurrentPage) { + props.changeCurrentPage(pageNumber); + } + }}> + +
+ ) })} {/* Next (hidden if current page is the last page) */} - {showNextButton && +
{ + if (props.changeToNextPage && nextButtonEnabled) { + props.changeToNextPage() + } + }}> - } +
) } diff --git a/tests/components/pagination.test.ts b/tests/components/pagination.test.ts index 008f12c1..b936e5c3 100644 --- a/tests/components/pagination.test.ts +++ b/tests/components/pagination.test.ts @@ -1,6 +1,6 @@ import { - checkShowNextButton, - checkShowPreviousButton, + checkNextButton, + checkPreviousButton, } from "../../components/pagination/function"; describe("Pagination", () => { @@ -11,7 +11,7 @@ describe("Pagination", () => { { currentPage: 99, expected: true }, { currentPage: NaN, expected: false }, ])("checkShowPreviousButton($currentPage)", ({ currentPage, expected }) => { - const output = checkShowPreviousButton(currentPage); + const output = checkPreviousButton(currentPage); expect(output).toBe(expected); }); @@ -27,7 +27,7 @@ describe("Pagination", () => { ])( "checkShowNextButton($pages, $currentPage)", ({ pages, currentPage, expected }) => { - const output = checkShowNextButton(pages, currentPage); + const output = checkNextButton(pages, currentPage); expect(output).toBe(expected); } );