feat: initial commit with seeded database and premium ui changes
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user