Skip to content

Commit d91401c

Browse files
committed
Merge branch 'feat/history-graph-changes'
2 parents bc28a36 + a2e0cf3 commit d91401c

File tree

6 files changed

+244
-0
lines changed

6 files changed

+244
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { getServerRestClient } from "@/context/RestClientStore";
2+
import { Enums } from "@/lib/database.types";
3+
import { NextRequest, NextResponse } from "next/server";
4+
5+
export async function GET(request: NextRequest) {
6+
const { searchParams } = new URL(request.url);
7+
const resolution = searchParams.get(
8+
"resolution"
9+
) as Enums<"history_resolution">;
10+
const unitType = searchParams.get("unit_type") as
11+
UnitType;
12+
const series_codes = searchParams.get("series_codes")?.split(",");
13+
14+
const client = await getServerRestClient();
15+
const { data, error } = await client.rpc("statistical_history_highcharts", {
16+
p_resolution: resolution,
17+
p_unit_type: unitType,
18+
p_series_codes: series_codes,
19+
});
20+
21+
if (error) {
22+
return NextResponse.json({ message: error.message });
23+
}
24+
25+
return NextResponse.json(data);
26+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
import { useRef } from "react";
3+
import * as highcharts from "highcharts";
4+
import type { Chart } from "highcharts";
5+
import { chart } from "highcharts";
6+
import { useGuardedEffect } from "@/hooks/use-guarded-effect";
7+
8+
export const HistoryChangesChart = ({
9+
history,
10+
}: {
11+
readonly history: StatisticalHistoryHighcharts;
12+
}) => {
13+
const _ref = useRef<HTMLDivElement>(null);
14+
const _chart = useRef<Chart | null>(null);
15+
16+
17+
const colors = [
18+
"#1A3A70",
19+
"#4B8B3B",
20+
"#AA4643",
21+
"#80699B",
22+
"#D6995C",
23+
"#628DCB",
24+
"#C7B491",
25+
"#f45b5b",
26+
"#e4d354",
27+
"#89A54E",
28+
"#2b908f",
29+
"#CAC47F",
30+
].reverse();
31+
32+
useGuardedEffect(
33+
() => {
34+
const chartSeries = history.series;
35+
if (!_ref.current || !highcharts || !chartSeries) return;
36+
_chart.current?.destroy();
37+
38+
_chart.current = chart({
39+
lang: {
40+
thousandsSep: " ",
41+
},
42+
chart: {
43+
type: "column",
44+
renderTo: _ref.current,
45+
backgroundColor: "white",
46+
},
47+
title: {
48+
text: "",
49+
},
50+
xAxis: {
51+
type: "datetime",
52+
},
53+
yAxis: {
54+
title: {
55+
text: "Change count",
56+
},
57+
},
58+
tooltip: {
59+
xDateFormat: "%Y-%m-%d",
60+
shared: true,
61+
},
62+
colors: colors,
63+
plotOptions: {
64+
series: {
65+
connectNulls: false,
66+
marker: { radius: 3, enabled: true },
67+
states: {
68+
inactive: {
69+
opacity: 1,
70+
},
71+
},
72+
},
73+
column: {
74+
borderWidth: 0,
75+
},
76+
},
77+
78+
series: chartSeries
79+
.sort((a, b) => a.priority - b.priority)
80+
.map((s, i) => ({
81+
type: "column",
82+
name: s.name,
83+
data: s.data,
84+
visible: i < 5,
85+
color: colors[i % colors.length],
86+
})),
87+
credits: { enabled: false },
88+
legend: {
89+
enabled: true,
90+
},
91+
});
92+
},
93+
[history],
94+
"HistoryChangesChart:createChart"
95+
);
96+
97+
return <div ref={_ref} />;
98+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import React, { useState } from "react";
4+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
5+
import { useStatisticalHistoryChanges } from "./use-statistical-history-changes";
6+
import { HistoryChangesChart } from "./history-changes-chart";
7+
import { Skeleton } from "@/components/ui/skeleton";
8+
9+
const unitTypes: { value: UnitType; label: string }[] = [
10+
{ value: "enterprise", label: "Enterprises" },
11+
{ value: "legal_unit", label: "Legal Units" },
12+
{ value: "establishment", label: "Establishments" },
13+
];
14+
15+
export default function HistoryChangesPage() {
16+
const [activeUnitType, setActiveUnitType] = useState<UnitType>("enterprise");
17+
const { history, isLoading } = useStatisticalHistoryChanges(
18+
activeUnitType,
19+
"year",
20+
[
21+
"births",
22+
"deaths",
23+
"name_change_count",
24+
"primary_activity_category_change_count",
25+
"physical_region_change_count",
26+
]
27+
);
28+
29+
return (
30+
<main className="mx-auto flex w-full max-w-5xl flex-col px-2 py-8 md:py-12">
31+
<h1 className="mb-3 text-center text-2xl">Changes over time</h1>
32+
<p className="mb-12 text-center">
33+
Annual overview of births, deaths, name changes, and other changes
34+
</p>
35+
<div className="w-full space-y-8">
36+
<Tabs
37+
defaultValue="enterprise"
38+
onValueChange={(value) => setActiveUnitType(value as UnitType)}
39+
>
40+
<TabsList className="mx-auto">
41+
{unitTypes.map((unitType) => (
42+
<TabsTrigger key={unitType.value} value={unitType.value}>
43+
{unitType.label}
44+
</TabsTrigger>
45+
))}
46+
</TabsList>
47+
{unitTypes.map((unitType) => (
48+
<TabsContent
49+
key={unitType.value}
50+
value={unitType.value}
51+
className="space-y-8"
52+
>
53+
{!isLoading && history ? (
54+
<HistoryChangesChart history={history} />
55+
) : (
56+
<Skeleton className="w-full h-[400px]" />
57+
)}
58+
</TabsContent>
59+
))}
60+
</Tabs>
61+
</div>
62+
</main>
63+
);
64+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
declare interface StatisticalHistoryHighcharts {
2+
series: Array<{
3+
code: string;
4+
name: string;
5+
data: [number, number][];
6+
priority: number;
7+
}>;
8+
unit_type: UnitType;
9+
resolution: Enums<"history_resolution">;
10+
filtered_series: string[];
11+
available_series: Array<{
12+
code: string;
13+
name: string;
14+
priority: number;
15+
}>;
16+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use client";
2+
import useSWR from "swr";
3+
import { Enums } from "@/lib/database.types";
4+
import { fetchWithAuthRefresh } from "@/context/RestClientStore";
5+
6+
export const useStatisticalHistoryChanges = (
7+
unitType: UnitType,
8+
resolution: Enums<"history_resolution"> = "year",
9+
series_codes: string[]
10+
) => {
11+
const urlSearchParams = new URLSearchParams();
12+
13+
urlSearchParams.set("resolution", resolution);
14+
urlSearchParams.set("unit_type", unitType);
15+
urlSearchParams.set("series_codes", series_codes.join(","));
16+
17+
const fetcher = (url: string) =>
18+
fetchWithAuthRefresh(url).then((res) => res.json());
19+
20+
const { data: history, isLoading } = useSWR<StatisticalHistoryHighcharts>(
21+
`/api/reports/history-changes?${urlSearchParams.toString()}`,
22+
fetcher,
23+
{
24+
keepPreviousData: true,
25+
}
26+
);
27+
28+
return {
29+
history,
30+
isLoading,
31+
};
32+
};

app/src/components/command-palette/command-palette.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Database,
2323
FileSpreadsheet,
2424
Binary,
25+
ChartColumn,
2526
Users,
2627
} from "lucide-react";
2728

@@ -126,6 +127,13 @@ export function CommandPalette() {
126127
<BarChartHorizontal className="mr-2 h-4 w-4" />
127128
<span>Reports</span>
128129
</CommandItem>
130+
<CommandItem
131+
onSelect={() => navigate("/reports/history-changes")}
132+
value="History changes chart"
133+
>
134+
<ChartColumn className="mr-2 h-4 w-4" />
135+
<span>History chart</span>
136+
</CommandItem>
129137
<CommandItem onSelect={() => navigate("/profile")} value="Profile">
130138
<User className="mr-2 h-4 w-4" />
131139
<span>Profile</span>

0 commit comments

Comments
 (0)