diff --git a/app/cars/[type]/page.tsx b/app/cars/[type]/page.tsx index 06b9638..45a2709 100644 --- a/app/cars/[type]/page.tsx +++ b/app/cars/[type]/page.tsx @@ -14,13 +14,11 @@ import { BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { UnreleasedFeature } from "@/components/UnreleasedFeature"; import { API_URL, EXCLUSION_LIST, SITE_URL } from "@/config"; import { capitaliseWords } from "@/utils/capitaliseWords"; import { fetchApi } from "@/utils/fetchApi"; -import { Car } from "@/types"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import Typography from "@/components/Typography"; -import { UnreleasedFeature } from "@/components/UnreleasedFeature"; +import { Car, LatestMonth, Month } from "@/types"; interface Props { params: { type: string }; @@ -32,7 +30,7 @@ export const generateMetadata = async ({ searchParams, }: Props): Promise => { const { type } = params; - const month = searchParams?.month as string; + const month = searchParams?.month; return { title: capitaliseWords(type), @@ -50,17 +48,19 @@ const tabItems: Record = { diesel: "/cars/diesel", }; -export const generateStaticParams = async () => +export const generateStaticParams = () => Object.keys(tabItems).map((key) => ({ type: key, })); const CarsByFuelTypePage = async ({ params, searchParams }: Props) => { const { type } = params; - const month = searchParams?.month as string; const cars = await fetchApi(`${API_URL}/cars/${type}`); - const months = [...new Set(cars.map(({ month }) => month))]; + const [months, latestMonth] = await Promise.all([ + fetchApi(`${API_URL}/months`), + fetchApi(`${API_URL}/months/latest`), + ]); const totals = new Map(); cars.forEach(({ make, number }) => { @@ -72,7 +72,7 @@ const CarsByFuelTypePage = async ({ params, searchParams }: Props) => { }); const filteredCars = cars.filter( - ({ make }) => !EXCLUSION_LIST.includes(make), + ({ make, number }) => !EXCLUSION_LIST.includes(make) && number > 0, ); const jsonLd: WithContext = { @@ -115,7 +115,7 @@ const CarsByFuelTypePage = async ({ params, searchParams }: Props) => {

Cars

- +
@@ -131,9 +131,7 @@ const CarsByFuelTypePage = async ({ params, searchParams }: Props) => { })} -
- -
+ diff --git a/app/components/CarTreeMap.tsx b/app/components/CarTreeMap.tsx index 7a42213..fe71f71 100644 --- a/app/components/CarTreeMap.tsx +++ b/app/components/CarTreeMap.tsx @@ -33,22 +33,26 @@ export const CarTreeMap = ({ data }: TreeMapProps) => { const formattedData = convertToTreeMapFormat(data); return ( - `${id} (${formattedValue})`} - labelSkipSize={12} - labelTextColor={{ - from: "color", - modifiers: [["darker", 1.2]], - }} - colors={{ scheme: "tableau10" }} - borderColor={{ - from: "color", - modifiers: [["darker", 0.1]], - }} - /> + data.length > 0 && ( +
+ `${id} (${formattedValue})`} + labelSkipSize={12} + labelTextColor={{ + from: "color", + modifiers: [["darker", 1.2]], + }} + colors={{ scheme: "tableau10" }} + borderColor={{ + from: "color", + modifiers: [["darker", 0.1]], + }} + /> +
+ ) ); }; diff --git a/app/components/DataTable.tsx b/app/components/DataTable.tsx index 1d58f5a..f3ccae1 100644 --- a/app/components/DataTable.tsx +++ b/app/components/DataTable.tsx @@ -13,10 +13,13 @@ import { TableRow, } from "@/components/ui/table"; import { MEDAL_MAPPING } from "@/config"; +import Typography from "@/components/Typography"; import { useGlobalState } from "@/context/GlobalStateContext"; import { capitaliseWords } from "@/utils/capitaliseWords"; import { formatDateToMonthYear } from "@/utils/formatDateToMonthYear"; +import { CARS } from "@/constants"; +// TODO: Fix type interface DataTableProps { data: any[]; fuelType: string; @@ -28,21 +31,10 @@ export const DataTable = ({ data, fuelType }: DataTableProps) => { data = data.filter(({ month }) => month === selectedMonth); - const excludeHeaders = [ - "_id", - "importer_type", - "vehicle_type", - "month", - "fuel_type", - ]; - const tableHeaders = Object.keys(data[0]) - .filter((item) => !excludeHeaders.includes(item)) - .map((header) => capitaliseWords(header)); - const total = data.reduce((accum, curr) => accum + curr.number, 0); - const getWeight = (number: number) => number / total; + const marketShare = (number: number) => number / total; - data.sort((a, b) => getWeight(b.number) - getWeight(a.number)); + data.sort((a, b) => marketShare(b.number) - marketShare(a.number)); return ( @@ -54,41 +46,49 @@ export const DataTable = ({ data, fuelType }: DataTableProps) => { )} - # - {tableHeaders.map((header) => ( + {Object.values(CARS.TABLE.HEADERS).map((header) => ( {header} ))} - Weightage - {data.map((item, index) => { - const serial = index + 1; - return ( + {data.length === 0 && ( + + + No data available + + + )} + {data.length > 0 && + data.map((item, index) => ( - {MEDAL_MAPPING[serial] || serial} + {MEDAL_MAPPING[index + 1] || index + 1} {item.make} {item.number} - + {new Intl.NumberFormat("en-SG", { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2, - }).format(getWeight(item.number))} + }).format(marketShare(item.number))} - ); - })} + ))} - - Total - {total} - + {data.length > 0 && ( + + Total + {total} + + )}
); diff --git a/app/components/MonthSelect.tsx b/app/components/MonthSelect.tsx index 441660d..2a1bd37 100644 --- a/app/components/MonthSelect.tsx +++ b/app/components/MonthSelect.tsx @@ -1,6 +1,7 @@ "use client"; import { useCallback, useEffect, useMemo } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Select, SelectContent, @@ -13,20 +14,26 @@ import { import { useGlobalState } from "@/context/GlobalStateContext"; import { formatDateToMonthYear } from "@/utils/formatDateToMonthYear"; import { groupByYear } from "@/utils/groupByYear"; +import { Month } from "@/types"; interface MonthSelectProps { - months: string[]; - selectedMonth: string; + months: Month[]; + defaultMonth: Month; } -export const MonthSelect = ({ months, selectedMonth }: MonthSelectProps) => { +export const MonthSelect = ({ months, defaultMonth }: MonthSelectProps) => { const { state, dispatch } = useGlobalState(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const month = searchParams.get("month"); useEffect(() => { - if (selectedMonth) { - dispatch({ type: "SET_SELECTED_MONTH", payload: selectedMonth }); + if (month) { + dispatch({ type: "SET_SELECTED_MONTH", payload: month }); } - }, [dispatch, selectedMonth]); + }, [dispatch, month]); const memoisedGroupByYear = useMemo(() => groupByYear, []); const sortedMonths = useMemo( @@ -36,13 +43,17 @@ export const MonthSelect = ({ months, selectedMonth }: MonthSelectProps) => { const handleValueChange = useCallback( (month: string) => { - dispatch({ type: "SET_SELECTED_MONTH", payload: month }); + const queryString = new URLSearchParams({ month }).toString(); + router.replace(`${pathname}?${queryString}`); }, [dispatch], ); return ( - diff --git a/app/make/[make]/page.tsx b/app/make/[make]/page.tsx index 0e87714..f642f50 100644 --- a/app/make/[make]/page.tsx +++ b/app/make/[make]/page.tsx @@ -15,6 +15,7 @@ import { capitaliseWords } from "@/utils/capitaliseWords"; import type { Car, LatestMonth } from "@/types"; import { StructuredData } from "@/components/StructuredData"; import Typography from "@/components/Typography"; +import { MAKE } from "@/constants"; interface Props { params: { make: string }; @@ -67,15 +68,6 @@ const CarMakePage = async ({ params, searchParams }: Props) => { const cars = await fetchApi(`${API_URL}/make/${make}?month=${month}`); const filteredCars = mergeCarData(cars); - let tableHeaders: string[] = []; - if (filteredCars.length > 0) { - // TODO: Interim solution - const excludeHeaders = ["_id", "make"]; - tableHeaders = Object.keys(filteredCars[0]) - .filter((item) => !excludeHeaders.includes(item)) - .map(capitaliseWords); - } - const jsonLd: WithContext = { "@context": "https://schema.org", "@type": "WebSite", @@ -93,45 +85,49 @@ const CarMakePage = async ({ params, searchParams }: Props) => {

Registrations

- {filteredCars.length === 0 && ( - No data available - )} - {filteredCars.length > 0 && ( - - - Historical trends for {decodeURIComponent(make)} - - +
+ + Historical trends for {decodeURIComponent(make)} + + + + {Object.values(MAKE.TABLE.HEADERS).map((header) => ( + {header} + ))} + + + + {filteredCars.length === 0 && ( - # - {tableHeaders.map((header) => ( - {header} - ))} - - - - {filteredCars.map((car, index) => ( - - {index + 1} - {car.month} - - - {car.fuel_type} - - - {car.vehicle_type} - {car.number} - - ))} - -
- )} + No data available + + + )} + {filteredCars.map((car, index) => ( + + {index + 1} + {car.month} + + + {car.fuel_type} + + + {car.vehicle_type} + {car.number} + + ))} + + ); diff --git a/constants/index.ts b/constants/index.ts new file mode 100644 index 0000000..905f918 --- /dev/null +++ b/constants/index.ts @@ -0,0 +1,22 @@ +export const CARS = { + TABLE: { + HEADERS: { + SERIAL: "#", + MAKE: "Make", + COUNT: "Count", + MARKET_SHARE: "Market Share", + }, + }, +}; + +export const MAKE = { + TABLE: { + HEADERS: { + SERIAL: "#", + MONTH: "Month", + FUEL_TYPE: "Fuel Type", + VEHICLE_TYPE: "Vehicle Type", + COUNT: "Count", + }, + }, +}; diff --git a/types/index.ts b/types/index.ts index 03c1615..6c807c3 100644 --- a/types/index.ts +++ b/types/index.ts @@ -43,6 +43,8 @@ export interface COEResult { premium: number; } +export type Month = string; + export interface LatestMonth { cars: string; coe: string;