diff --git a/app/listings/filters-area.tsx b/app/listings/filters-area.tsx index 9101df5d..25bcf848 100644 --- a/app/listings/filters-area.tsx +++ b/app/listings/filters-area.tsx @@ -1,13 +1,13 @@ import ButtonSmallFilled from "@/components/buttons/small/filled"; import ButtonSmallText from "@/components/buttons/small/text"; import FormInput from "@/components/form-input"; -import { AREA_FORMATTER } from "@/lib/formatter/area"; +import { formatAppend } from "@/lib/formatter/number"; export default function ListingsFilterArea() { const DEFAULT_MAX = 20; const minAreaPlaceholder = "None"; - const maxAreaPlaceholder = AREA_FORMATTER.format(DEFAULT_MAX); + const maxAreaPlaceholder = formatAppend(DEFAULT_MAX, "sqm."); return (
diff --git a/app/listings/function.ts b/app/listings/function.ts new file mode 100644 index 00000000..3cf3b2f9 --- /dev/null +++ b/app/listings/function.ts @@ -0,0 +1,3 @@ +export function countListingsToSkip(currentPage: number, countPerPage: number) { + return (currentPage - 1) * countPerPage; +} diff --git a/app/listings/list-top.tsx b/app/listings/list-top.tsx index ea74bda2..2760d78c 100644 --- a/app/listings/list-top.tsx +++ b/app/listings/list-top.tsx @@ -1,14 +1,17 @@ +import { formatAppend } from "@/lib/formatter/number"; + export function ListingsListTop() { + const listingsCount = 7_238; + const listingsCountText = formatAppend(listingsCount, "listings"); + return (
{/* TODO: Count and sort options */}
{/* Count */} {/* TODO: Use actual numbers */} - {/* TODO: Ensure number formatting is correct (use commas to separate) - (refactor (similar to AREA_FORMATTER now) and use NUMBER_FORMATTER) */} - 7,238 listings + {listingsCountText} {/* TODO: Sort options */}
diff --git a/app/listings/page.tsx b/app/listings/page.tsx index d3927fc9..651933d7 100644 --- a/app/listings/page.tsx +++ b/app/listings/page.tsx @@ -2,21 +2,16 @@ import CardListing from "@/components/card-listing"; import Pagination from "@/components/pagination"; import prisma from "@/lib/db"; import { CURRENCY_FORMATTER } from "@/lib/formatter/currency"; +import { countListingsToSkip } from "./function"; import { ListingsListTop } from "./list-top"; import { ListingsMap } from "./map"; import { ListingsSearchFilters } from "./search-filters"; const LISTINGS_PER_PAGE = 11; -// TODO: Move to new file (functions.ts) -// TODO: Unit test -function countListingsToSkip(currentPage: number) { - return (currentPage - 1) * LISTINGS_PER_PAGE; -} - export default async function Listings() { const currentPage = 1; - const listingsToSkip = countListingsToSkip(currentPage); + const listingsToSkip = countListingsToSkip(currentPage, LISTINGS_PER_PAGE); // Count the number of listings using the givne current pagination option values const listingsCount = await prisma.listing.count() // Filter listings using the given pagination and filter options diff --git a/components/buttons-segmented/index.tsx b/components/buttons-segmented/index.tsx index 0a2f06b7..9e5e9b99 100644 --- a/components/buttons-segmented/index.tsx +++ b/components/buttons-segmented/index.tsx @@ -1,19 +1,30 @@ export interface ButtonsSegmentedProps { values: string[] + /** + * Valid value is between 0-`n` + * Where `n` is the number of entries in `values` minus one (1). + */ + activeIndex: number } -// TODO: Correct hover style on currently active segment export default function ButtonsSegmented(props: ButtonsSegmentedProps) { return (
- {props.values.map((value, index, arrayValue) => -
{ }}> - {value} -
)} + {props.values.map((value, index, arrayValue) => { + const indexIsActive = props.activeIndex === index; + return ( +
{ }}> + {value} +
+ ) + })}
); } diff --git a/components/buttons-segmented/labelled.tsx b/components/buttons-segmented/labelled.tsx index 278c3316..2059d44a 100644 --- a/components/buttons-segmented/labelled.tsx +++ b/components/buttons-segmented/labelled.tsx @@ -1,17 +1,15 @@ -import ButtonsSegmented from "."; +import ButtonsSegmented, { ButtonsSegmentedProps } from "."; -export interface ButtonsSegmentedLabelledProps { - values: string[] +export interface ButtonsSegmentedLabelledProps extends ButtonsSegmentedProps { label: string } -// TODO: Story export default function ButtonsSegmentedLabelled(props: ButtonsSegmentedLabelledProps) { return (
- +
); diff --git a/components/buttons/small/text.tsx b/components/buttons/small/text.tsx index 2868578d..eb826c04 100644 --- a/components/buttons/small/text.tsx +++ b/components/buttons/small/text.tsx @@ -1,8 +1,6 @@ import { buttonText, buttonType } from "../functions"; import { ButtonProps } from "../types"; -// TODO: Story -// TODO: Component export default function ButtonSmallText(props: ButtonProps) { const text = buttonText(props.loading) ?? props.text; const type = buttonType(props.type); diff --git a/components/pagination/function.ts b/components/pagination/function.ts new file mode 100644 index 00000000..3eaf6c76 --- /dev/null +++ b/components/pagination/function.ts @@ -0,0 +1,27 @@ +/** + * + * Show 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. + */ +export function checkShowPreviousButton(currentPage?: number) { + return currentPage !== undefined && currentPage > 1; +} + +/** + * Show 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. + */ +export function checkShowNextButton(pages: number, currentPage?: number) { + if (currentPage === undefined) { + if (pages !== 1) { + return true; + } + } else { + return currentPage < pages; + } +} diff --git a/components/pagination/index.tsx b/components/pagination/index.tsx index 5f77a84e..03de2e3a 100644 --- a/components/pagination/index.tsx +++ b/components/pagination/index.tsx @@ -1,3 +1,4 @@ +import { checkShowNextButton, checkShowPreviousButton } from "./function" import PaginationItemNav from "./item-nav" import PaginationItemNumber from "./item-number" @@ -12,19 +13,8 @@ export interface PaginationProps { // TODO: Style when many pages (need to break it somewhere, must work for all screen screens) export default function Pagination(props: PaginationProps) { const pageNumbers = Array.from({ length: props.pages }, (elem, i) => i + 1) - // Show the previous button only if the current page is not the first page - // TODO: Move to function then unit test - const showPreviousButton = props.currentPage && props.currentPage > 1 - // Show the next button only if the current page is not the last page - // TODO: Move to function then unit test - let showNextButton = false; - if (props.currentPage === undefined) { - if (props.pages !== 1) { - showNextButton = true; - } - } else { - showNextButton = props.currentPage < props.pages; - } + const showPreviousButton = checkShowPreviousButton(props.currentPage); + const showNextButton = checkShowNextButton(props.pages, props.currentPage); return (
diff --git a/lib/formatter/area.ts b/lib/formatter/area.ts deleted file mode 100644 index 1632ae22..00000000 --- a/lib/formatter/area.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NUMBER_FORMATTER } from "./number"; - -// FUTURE: Localize and put in env -// TODO: Unit test -export class AREA_FORMATTER { - static format(value: number) { - const formatted = NUMBER_FORMATTER.format(value); - // FUTURE: This is temporary until the actual `area-square-meter` is supported. - // Read more at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#unit_2 - return `${formatted} sqm.`; - } -} diff --git a/lib/formatter/currency.ts b/lib/formatter/currency.ts index 391a1cb6..1d052b96 100644 --- a/lib/formatter/currency.ts +++ b/lib/formatter/currency.ts @@ -1,5 +1,4 @@ // FUTURE: Localize and put in env -// TODO: Unit testing export const CURRENCY_FORMATTER = new Intl.NumberFormat("en-PH", { style: "currency", currency: "PHP", diff --git a/lib/formatter/number.ts b/lib/formatter/number.ts index 85677421..c840fa34 100644 --- a/lib/formatter/number.ts +++ b/lib/formatter/number.ts @@ -2,3 +2,8 @@ export const NUMBER_FORMATTER = new Intl.NumberFormat("en-PH", { style: "decimal", }); + +export function formatAppend(value: number, append: string) { + const formatted = NUMBER_FORMATTER.format(value); + return `${formatted} ${append}`; +} diff --git a/stories/buttons-segmented/ButtonsSegmented.stories.tsx b/stories/buttons-segmented/ButtonsSegmented.stories.tsx index 6a7f2a2a..8d842bab 100644 --- a/stories/buttons-segmented/ButtonsSegmented.stories.tsx +++ b/stories/buttons-segmented/ButtonsSegmented.stories.tsx @@ -12,6 +12,7 @@ type Story = StoryObj; export const Example: Story = { args: { - values: ["Any", "1", "2", "3", "4", "5+"] + values: ["Any", "1", "2", "3", "4", "5+"], + activeIndex: 0 } }; diff --git a/stories/buttons-segmented/ButtonsSegmentedLabelled.stories.tsx b/stories/buttons-segmented/ButtonsSegmentedLabelled.stories.tsx index 112ac40d..6d1b680d 100644 --- a/stories/buttons-segmented/ButtonsSegmentedLabelled.stories.tsx +++ b/stories/buttons-segmented/ButtonsSegmentedLabelled.stories.tsx @@ -13,6 +13,7 @@ type Story = StoryObj; export const Example: Story = { args: { values: ["Any", "1", "2", "3", "4", "5+"], - label: "Options" + label: "Options", + activeIndex: 3, } }; diff --git a/stories/pagination/Pagination.stories.tsx b/stories/pagination/Pagination.stories.tsx index a02b8fba..aa7425ca 100644 --- a/stories/pagination/Pagination.stories.tsx +++ b/stories/pagination/Pagination.stories.tsx @@ -46,6 +46,7 @@ export const SinglePage: Story = { export const ManyPages: Story = { args: { pages: 101, + currentPage: 55, } }; diff --git a/tests/app/listings.test.ts b/tests/app/listings.test.ts new file mode 100644 index 00000000..88695303 --- /dev/null +++ b/tests/app/listings.test.ts @@ -0,0 +1,21 @@ +import { countListingsToSkip } from "../../app/listings/function"; + +describe("Listings", () => { + test.each([ + { currentPage: 1, countPerPage: 20, expected: 0 }, + { currentPage: 2, countPerPage: 30, expected: 30 }, + { currentPage: 3, countPerPage: 40, expected: 80 }, + { currentPage: 4, countPerPage: 50, expected: 150 }, + { currentPage: 5, countPerPage: 60, expected: 240 }, + { currentPage: 12, countPerPage: 55, expected: 605 }, + { currentPage: 22, countPerPage: 25, expected: 525 }, + { currentPage: 29, countPerPage: 32, expected: 896 }, + { currentPage: 99, countPerPage: 44, expected: 4312 }, + ])( + "countListingsToSkip($currentPage, $countPerPage)", + ({ currentPage, countPerPage, expected }) => { + const output = countListingsToSkip(currentPage, countPerPage); + expect(output).toBe(expected); + } + ); +}); diff --git a/tests/components/pagination.test.ts b/tests/components/pagination.test.ts new file mode 100644 index 00000000..51ca9a0e --- /dev/null +++ b/tests/components/pagination.test.ts @@ -0,0 +1,36 @@ +import { + checkShowNextButton, + checkShowPreviousButton, +} from "../../components/pagination/function"; + +describe("Pagination", () => { + test.each([ + { currentPage: -1, expected: false }, + { currentPage: 0, expected: false }, + { currentPage: 1, expected: false }, + { currentPage: 99, expected: true }, + { currentPage: NaN, expected: false }, + { currentPage: undefined, expected: false }, + ])("checkShowPreviousButton($currentPage)", ({ currentPage, expected }) => { + const output = checkShowPreviousButton(currentPage); + expect(output).toBe(expected); + }); + + test.each([ + { pages: 100, currentPage: -1, expected: true }, + { pages: 100, currentPage: 0, expected: true }, + { pages: 100, currentPage: 1, expected: true }, + { pages: 100, currentPage: 99, expected: true }, + { pages: 100, currentPage: 100, expected: false }, + { pages: 100, currentPage: 101, expected: false }, + { pages: 100, currentPage: 201, expected: false }, + { pages: 100, currentPage: NaN, expected: false }, + { pages: 100, currentPage: undefined, expected: true }, + ])( + "checkShowNextButton($pages, $currentPage)", + ({ pages, currentPage, expected }) => { + const output = checkShowNextButton(pages, currentPage); + expect(output).toBe(expected); + } + ); +}); diff --git a/tests/lib/formatter/currency.test.ts b/tests/lib/formatter/currency.test.ts new file mode 100644 index 00000000..7af93518 --- /dev/null +++ b/tests/lib/formatter/currency.test.ts @@ -0,0 +1,12 @@ +import { CURRENCY_FORMATTER } from "../../../lib/formatter/currency"; + +describe("Currency formatter", () => { + test.each([ + { value: 12.39, expected: "₱12" }, + { value: 2_592.912, expected: "₱2,593" }, + { value: 42_622_884.2837, expected: "₱42,622,884" }, + ])("format($value)", ({ value, expected }) => { + const output = CURRENCY_FORMATTER.format(value); + expect(output).toBe(expected); + }); +}); diff --git a/tests/lib/formatter/number.test.ts b/tests/lib/formatter/number.test.ts new file mode 100644 index 00000000..d4f06995 --- /dev/null +++ b/tests/lib/formatter/number.test.ts @@ -0,0 +1,12 @@ +import { formatAppend } from "../../../lib/formatter/number"; + +describe("Number formatter", () => { + test.each([ + { value: 15, append: "apples", expected: "15 apples" }, + { value: 1_302, append: "MB", expected: "1,302 MB" }, + { value: 1_250_390, append: "units", expected: "1,250,390 units" }, + ])("formatAppend($value, $append)", ({ value, append, expected }) => { + const output = formatAppend(value, append); + expect(output).toBe(expected); + }); +});