110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { DashboardActivity } from "@/components/dashboard-activity";
|
|
import { DashboardSkeleton } from "@/components/dashboard-skeleton";
|
|
import { HouseholdsTrendChart } from "@/components/households-trend-chart";
|
|
import { PriorityBreakdown } from "@/components/priority-breakdown";
|
|
import { PriorityScoreChart } from "@/components/priority-score-chart";
|
|
import { RecentHouseholds } from "@/components/recent-households";
|
|
import { DashboardStats } from "@/components/stats";
|
|
import type { ActivityItem, GasStationStats } from "@/lib/dashboard-types";
|
|
import type { HouseholdRow, PovertyOverview } from "@/lib/poverty-types";
|
|
|
|
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "http://localhost:4000";
|
|
|
|
type NamedRow = { id: number; name: string; created_at?: string };
|
|
|
|
export function Dashboard() {
|
|
const [loading, setLoading] = useState(true);
|
|
const [overview, setOverview] = useState<PovertyOverview | null>(null);
|
|
const [households, setHouseholds] = useState<HouseholdRow[]>([]);
|
|
const [gasStats, setGasStats] = useState<GasStationStats | null>(null);
|
|
const [landMarkers, setLandMarkers] = useState<NamedRow[]>([]);
|
|
const [landShapes, setLandShapes] = useState<NamedRow[]>([]);
|
|
const [gasMarkers, setGasMarkers] = useState<NamedRow[]>([]);
|
|
|
|
// Aggregates everything the overview widgets need, straight from the DB.
|
|
useEffect(() => {
|
|
const token = localStorage.getItem("auth_token");
|
|
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {};
|
|
const get = (path: string) => fetch(`${BACKEND_URL}${path}`, { headers }).then((r) => r.json());
|
|
const asRows = (r: PromiseSettledResult<unknown>) =>
|
|
r.status === "fulfilled" && Array.isArray(r.value) ? (r.value as NamedRow[]) : [];
|
|
|
|
Promise.allSettled([
|
|
get("/households/stats-overview"),
|
|
get("/households"),
|
|
get("/gas-station/stats"),
|
|
get("/land/markers"),
|
|
get("/land/shapes"),
|
|
get("/gas-station/markers"),
|
|
]).then(([overviewR, hhR, gasStatsR, landMarkersR, landShapesR, gasMarkersR]) => {
|
|
if (overviewR.status === "fulfilled" && overviewR.value?.totals) {
|
|
setOverview(overviewR.value as PovertyOverview);
|
|
}
|
|
if (hhR.status === "fulfilled" && Array.isArray(hhR.value)) {
|
|
setHouseholds(hhR.value as HouseholdRow[]);
|
|
}
|
|
if (gasStatsR.status === "fulfilled" && typeof gasStatsR.value?.total === "number") {
|
|
setGasStats(gasStatsR.value as GasStationStats);
|
|
}
|
|
setLandMarkers(asRows(landMarkersR));
|
|
setLandShapes(asRows(landShapesR));
|
|
setGasMarkers(asRows(gasMarkersR));
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
const activity = useMemo<ActivityItem[]>(() => {
|
|
const items: ActivityItem[] = [
|
|
...households.map((h) => ({
|
|
id: `hh-${h.id}`,
|
|
kind: "household" as const,
|
|
title: `Warga "${h.head_name}" terdata`,
|
|
created_at: h.created_at,
|
|
})),
|
|
...landMarkers.map((m) => ({
|
|
id: `lm-${m.id}`,
|
|
kind: "land" as const,
|
|
title: `Penanda lahan "${m.name}" ditambahkan`,
|
|
created_at: m.created_at ?? "",
|
|
})),
|
|
...landShapes.map((s) => ({
|
|
id: `ls-${s.id}`,
|
|
kind: "land" as const,
|
|
title: `Bidang lahan "${s.name}" digambar`,
|
|
created_at: s.created_at ?? "",
|
|
})),
|
|
...gasMarkers.map((g) => ({
|
|
id: `gs-${g.id}`,
|
|
kind: "gas" as const,
|
|
title: `Fasilitas "${g.name}" dipetakan`,
|
|
created_at: g.created_at ?? "",
|
|
})),
|
|
];
|
|
return items
|
|
.filter((i) => i.created_at)
|
|
.sort((a, b) => b.created_at.localeCompare(a.created_at))
|
|
.slice(0, 4);
|
|
}, [households, landMarkers, landShapes, gasMarkers]);
|
|
|
|
if (loading) return <DashboardSkeleton />;
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 gap-px bg-border border rounded-xl overflow-hidden md:grid-cols-2 lg:grid-cols-4 shadow-none">
|
|
<DashboardStats
|
|
overview={overview}
|
|
gasStats={gasStats}
|
|
landMarkerCount={landMarkers.length}
|
|
landShapeCount={landShapes.length}
|
|
/>
|
|
<HouseholdsTrendChart monthly={overview?.monthly ?? []} />
|
|
<PriorityScoreChart monthly={overview?.monthly ?? []} />
|
|
<RecentHouseholds households={households.slice(0, 4)} />
|
|
<PriorityBreakdown byLabel={overview?.by_label ?? null} />
|
|
<DashboardActivity items={activity} />
|
|
</div>
|
|
);
|
|
}
|