Skip to content

Commit

Permalink
Fix table crashing when no data is fetched
Browse files Browse the repository at this point in the history
  • Loading branch information
Ru Chern Chong committed Jul 20, 2024
1 parent 4a2a966 commit 0e0d5a4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 112 deletions.
24 changes: 11 additions & 13 deletions app/cars/[type]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -32,7 +30,7 @@ export const generateMetadata = async ({
searchParams,
}: Props): Promise<Metadata> => {
const { type } = params;
const month = searchParams?.month as string;
const month = searchParams?.month;

return {
title: capitaliseWords(type),
Expand All @@ -50,17 +48,19 @@ const tabItems: Record<string, string> = {
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<Car[]>(`${API_URL}/cars/${type}`);
const months = [...new Set(cars.map(({ month }) => month))];
const [months, latestMonth] = await Promise.all([
fetchApi<Month[]>(`${API_URL}/months`),
fetchApi<LatestMonth>(`${API_URL}/months/latest`),
]);

const totals = new Map();
cars.forEach(({ make, number }) => {
Expand All @@ -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<WebSite> = {
Expand Down Expand Up @@ -115,7 +115,7 @@ const CarsByFuelTypePage = async ({ params, searchParams }: Props) => {
<p className="text-xl text-muted-foreground">Cars</p>
</div>
<div>
<MonthSelect months={months} selectedMonth={month} />
<MonthSelect months={months} defaultMonth={latestMonth.cars} />
</div>
</div>
<Tabs defaultValue={type}>
Expand All @@ -131,9 +131,7 @@ const CarsByFuelTypePage = async ({ params, searchParams }: Props) => {
})}
</TabsList>
</Tabs>
<div className="h-[600px]">
<CarTreeMap data={filteredCars} />
</div>
<CarTreeMap data={filteredCars} />
<DataTable data={filteredCars} fuelType={type} />
</div>
</section>
Expand Down
38 changes: 21 additions & 17 deletions app/components/CarTreeMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,26 @@ export const CarTreeMap = ({ data }: TreeMapProps) => {
const formattedData = convertToTreeMapFormat(data);

return (
<ResponsiveTreeMap
data={formattedData}
identity="make"
value="number"
leavesOnly={true}
label={({ id, formattedValue }) => `${id} (${formattedValue})`}
labelSkipSize={12}
labelTextColor={{
from: "color",
modifiers: [["darker", 1.2]],
}}
colors={{ scheme: "tableau10" }}
borderColor={{
from: "color",
modifiers: [["darker", 0.1]],
}}
/>
data.length > 0 && (
<div className="h-[600px]">
<ResponsiveTreeMap
data={formattedData}
identity="make"
value="number"
leavesOnly={true}
label={({ id, formattedValue }) => `${id} (${formattedValue})`}
labelSkipSize={12}
labelTextColor={{
from: "color",
modifiers: [["darker", 1.2]],
}}
colors={{ scheme: "tableau10" }}
borderColor={{
from: "color",
modifiers: [["darker", 0.1]],
}}
/>
</div>
)
);
};
56 changes: 28 additions & 28 deletions app/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 (
<Table>
Expand All @@ -54,41 +46,49 @@ export const DataTable = ({ data, fuelType }: DataTableProps) => {
)}
<TableHeader>
<TableRow>
<TableHead>#</TableHead>
{tableHeaders.map((header) => (
{Object.values(CARS.TABLE.HEADERS).map((header) => (
<TableHead key={header}>{header}</TableHead>
))}
<TableHead>Weightage</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => {
const serial = index + 1;
return (
{data.length === 0 && (
<TableRow>
<TableCell
colSpan={Object.entries(CARS.TABLE.HEADERS).length}
className="text-center"
>
<Typography.H4>No data available</Typography.H4>
</TableCell>
</TableRow>
)}
{data.length > 0 &&
data.map((item, index) => (
<TableRow key={item._id} className="even:bg-muted">
<TableCell>{MEDAL_MAPPING[serial] || serial}</TableCell>
<TableCell>{MEDAL_MAPPING[index + 1] || index + 1}</TableCell>
<TableCell>
<Link href={`/make/${item.make}`}>{item.make}</Link>
</TableCell>
<TableCell>{item.number}</TableCell>
<TableCell>
<Progress value={getWeight(item.number)}>
<Progress value={marketShare(item.number)}>
{new Intl.NumberFormat("en-SG", {
style: "percent",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(getWeight(item.number))}
}).format(marketShare(item.number))}
</Progress>
</TableCell>
</TableRow>
);
})}
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell colSpan={2}>{total}</TableCell>
</TableRow>
{data.length > 0 && (
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell colSpan={2}>{total}</TableCell>
</TableRow>
)}
</TableFooter>
</Table>
);
Expand Down
27 changes: 19 additions & 8 deletions app/components/MonthSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useCallback, useEffect, useMemo } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
Select,
SelectContent,
Expand All @@ -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(
Expand All @@ -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 (
<Select value={state.selectedMonth} onValueChange={handleValueChange}>
<Select
defaultValue={month || defaultMonth}
onValueChange={handleValueChange}
>
<SelectTrigger>
<SelectValue placeholder="Select a month" />
</SelectTrigger>
Expand Down
88 changes: 42 additions & 46 deletions app/make/[make]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -67,15 +68,6 @@ const CarMakePage = async ({ params, searchParams }: Props) => {
const cars = await fetchApi<Car[]>(`${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<WebSite> = {
"@context": "https://schema.org",
"@type": "WebSite",
Expand All @@ -93,45 +85,49 @@ const CarMakePage = async ({ params, searchParams }: Props) => {
</h1>
<p className="text-xl text-muted-foreground">Registrations</p>
</div>
{filteredCars.length === 0 && (
<Typography.Lead>No data available</Typography.Lead>
)}
{filteredCars.length > 0 && (
<Table>
<TableCaption>
Historical trends for {decodeURIComponent(make)}
</TableCaption>
<TableHeader>
<Table>
<TableCaption>
Historical trends for {decodeURIComponent(make)}
</TableCaption>
<TableHeader>
<TableRow>
{Object.values(MAKE.TABLE.HEADERS).map((header) => (
<TableHead key={header}>{header}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{filteredCars.length === 0 && (
<TableRow>
<TableHead>#</TableHead>
{tableHeaders.map((header) => (
<TableHead key={header}>{header}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{filteredCars.map((car, index) => (
<TableRow
key={`${car.fuel_type}-${car.vehicle_type}`}
className="even:bg-muted"
<TableCell
colSpan={Object.entries(MAKE.TABLE.HEADERS).length}
className="text-center"
>
<TableCell>{index + 1}</TableCell>
<TableCell>{car.month}</TableCell>
<TableCell>
<Link
href={`/cars/${car.fuel_type.toLowerCase()}?month=${car.month}`}
className="hover:underline"
>
{car.fuel_type}
</Link>
</TableCell>
<TableCell>{car.vehicle_type}</TableCell>
<TableCell>{car.number}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<Typography.H4>No data available</Typography.H4>
</TableCell>
</TableRow>
)}
{filteredCars.map((car, index) => (
<TableRow
key={`${car.fuel_type}-${car.vehicle_type}`}
className="even:bg-muted"
>
<TableCell>{index + 1}</TableCell>
<TableCell>{car.month}</TableCell>
<TableCell>
<Link
href={`/cars/${car.fuel_type.toLowerCase()}?month=${car.month}`}
className="hover:underline"
>
{car.fuel_type}
</Link>
</TableCell>
<TableCell>{car.vehicle_type}</TableCell>
<TableCell>{car.number}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</section>
);
Expand Down
Loading

0 comments on commit 0e0d5a4

Please sign in to comment.