diff --git a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/OfferPrice.tsx b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/OfferPrice.tsx new file mode 100644 index 000000000..960e104ab --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/OfferPrice.tsx @@ -0,0 +1,39 @@ +"use client" +import { useIntl } from "react-intl" + +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { formatDate, type HotelData } from "./util" + +import styles from "./rewardNights.module.css" + +export function OfferPrice(offer: HotelData["rewardNight"]["campaign"]) { + const intl = useIntl() + + return ( +
+ +

+ {intl.formatMessage({ + id: "rewardNights.offerPrice", + defaultMessage: "Offer price", + })} +

+
+ +

+ {intl.formatMessage({ + id: "rewardNights.stayBetween:", + defaultMessage: "Stay between:", + })} +

+
+ + + +
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/Table.tsx b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/Table.tsx new file mode 100644 index 000000000..197c7fb66 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/Table.tsx @@ -0,0 +1,166 @@ +"use client" + +import { + createColumnHelper, + flexRender, + getCoreRowModel, + getSortedRowModel, + type SortingState, + useReactTable, +} from "@tanstack/react-table" +import { cx } from "class-variance-authority" +import { useState } from "react" +import { useIntl } from "react-intl" + +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import Table from "@scandic-hotels/design-system/Table" +import { TextLink } from "@scandic-hotels/design-system/TextLink" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { OfferPrice } from "./OfferPrice" +import { + formatPoints, + hasActiveCampaign, + type HotelData, + nameToSort, +} from "./util" + +import styles from "./rewardNights.module.css" + +interface RewardNightsTableProps { + hotelData: HotelData[] +} +export function RewardNightsTable({ hotelData }: RewardNightsTableProps) { + const intl = useIntl() + + const [sorting, setSorting] = useState([ + { id: "destination", desc: false }, + ]) + + const columnHelper = createColumnHelper() + const columns = [ + columnHelper.accessor("name", { + header: intl.formatMessage({ + id: "rewardNights.table.hotel", + defaultMessage: "Hotel", + }), + sortingFn: (a, b) => + nameToSort(a.original.name).localeCompare(nameToSort(b.original.name)), + cell: ({ row }) => ( + {row.original.name} + ), + }), + + columnHelper.accessor((row) => `${row.city}, ${row.country}`, { + id: "destination", + header: intl.formatMessage({ + id: "rewardNights.table.destination", + defaultMessage: "Destination", + }), + sortingFn: (a, b) => a.original.city.localeCompare(b.original.city), + cell: ({ row }) => { + const hotel = row.original + const hasCampaign = hasActiveCampaign(hotel.rewardNight.campaign) + + return ( + <> + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + {hotel.city}, {hotel.country} + {hasCampaign ? ( + + ) : null} + + ) + }, + }), + + columnHelper.accessor((row) => row.rewardNight.points, { + id: "points", + header: intl.formatMessage({ + id: "common.points", + defaultMessage: "Points", + }), + sortingFn: (a, b) => + a.original.rewardNight.points - b.original.rewardNight.points, + cell: ({ row }) => { + const hotel = row.original + const hasCampaign = hasActiveCampaign(hotel.rewardNight.campaign) + + return ( +
+ {formatPoints(hotel.rewardNight.points)} + + {hasCampaign ? ( + + {formatPoints(hotel.rewardNight.campaign.points)} + + ) : null} +
+ ) + }, + }), + ] + + const table = useReactTable({ + data: hotelData, + columns, + state: { sorting }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + enableSortingRemoval: false, + }) + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + + ))} + + ))} + + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/index.tsx index 2a71cef4d..8facad665 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/index.tsx @@ -1,128 +1,19 @@ -import { cx } from "class-variance-authority" - -import Table from "@scandic-hotels/design-system/Table" -import { TextLink } from "@scandic-hotels/design-system/TextLink" -import { Typography } from "@scandic-hotels/design-system/Typography" - import { getAllHotelData } from "@/lib/trpc/memoizedRequests" -import { getIntl } from "@/i18n" +import { RewardNightsTable } from "./Table" -import styles from "./rewardNights.module.css" - -import type { RewardNight } from "@scandic-hotels/trpc/types/hotel" +import type { HotelData } from "./util" export async function RewardNights() { - const intl = await getIntl() const hotelData = await getAllHotelData() - return ( - - - - - {intl.formatMessage({ - id: "rewardNights.table.hotel", - defaultMessage: "Hotel", - })} - - - {intl.formatMessage({ - id: "rewardNights.table.destination", - defaultMessage: "Destination", - })} - - - {intl.formatMessage({ - id: "common.points", - defaultMessage: "Points", - })} - - - - - {hotelData.map((data) => { - const { hotel } = data - const hasCampaign = hasActiveCampaign(hotel.rewardNight.campaign) - return ( - - - {hotel.name} - - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - {`${hotel.address.city}, ${hotel.address.country}`} - {hasCampaign ? ( - - ) : null} - - -
- {formatPoints(hotel.rewardNight.points)} - {hasCampaign ? ( - - - {formatPoints(hotel.rewardNight.campaign.points)} - - - ) : null} -
-
-
- ) - })} -
-
- ) -} -interface OfferPriceProps { - points: number - start: string - end: string -} -async function OfferPrice(offer: OfferPriceProps) { - const intl = await getIntl() + const rewardNightsData: HotelData[] = hotelData.map(({ url, hotel }) => ({ + url: url ?? "", + name: hotel.name, + city: hotel.address.city, + country: hotel.address.country, + rewardNight: hotel.rewardNight, + })) - return ( -
- -

- {intl.formatMessage({ - id: "rewardNights.offerPrice", - defaultMessage: "Offer price", - })} -

-
- -

- {intl.formatMessage({ - id: "rewardNights.stayBetween:", - defaultMessage: "Stay between:", - })} -

-
- - - -
- ) -} - -function formatPoints(number: number) { - const format = new Intl.NumberFormat("fr-FR") - return format.format(number).replace(/\u202F/g, " ") -} - -function formatDate(date?: string) { - return new Date(date ?? Date.now()).toISOString().split("T")[0] -} - -function hasActiveCampaign(campaign: RewardNight["campaign"]) { - return campaign.points && formatDate(campaign.end) >= formatDate() + return } diff --git a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/rewardNights.module.css b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/rewardNights.module.css index 14273ea70..50047fed7 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/rewardNights.module.css +++ b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/rewardNights.module.css @@ -11,3 +11,12 @@ .grid { display: grid; } + +.icon { + transition: transform 0.3s; + padding-left: var(--Space-x05); + + &.isTransformed { + transform: rotate(180deg); + } +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/util.ts b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/util.ts new file mode 100644 index 000000000..9335223bf --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/RewardNights/util.ts @@ -0,0 +1,28 @@ +import type { RewardNight } from "@scandic-hotels/trpc/types/hotel" + +export interface HotelData { + url: string + name: string + city: string + country: string + rewardNight: RewardNight +} + +export function formatDate(date?: string) { + return new Date(date ?? new Date()).toISOString().split("T")[0] +} + +export function formatPoints(number: number) { + const format = new Intl.NumberFormat("fr-FR") + return format.format(number).replace(/\u202F/g, " ") +} + +export function hasActiveCampaign( + campaign: HotelData["rewardNight"]["campaign"] +) { + return campaign.points && formatDate(campaign.end) >= formatDate() +} + +export function nameToSort(name: string) { + return name.toLowerCase().replaceAll("scandic", "").trim() +}