feat: connect pengaduan-layanan-publik to live database

New API Endpoint:
- GET /api/complaint/trends - Fetch complaint trends for last 7 months

Component Updates:
- Removed hardcoded trenData array (7 months mock data)
- Removed hardcoded ideInovatif array (2 mock ideas)
- Added API calls to /api/complaint/trends and /api/complaint/innovation-ideas
- Added loading states for trend chart and innovation ideas
- Added empty states for both sections
- Connected LineChart to real complaint data
- Connected Innovation Ideas list to real InnovationIdea model

Features Added:
- Real-time complaint trend visualization
- Real innovation ideas from database
- Proper TypeScript typing for API responses
- Loading skeletons during data fetch
- Empty state messages when no data

Files changed:
- src/api/complaint.ts: Added /trends endpoint
- src/components/pengaduan-layanan-publik.tsx: Connected to APIs
- generated/api.ts: Regenerated types

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-27 16:26:24 +08:00
parent 097f9f34cc
commit 0736df8523
4 changed files with 397 additions and 105 deletions

View File

@@ -30,37 +30,18 @@ import { apiClient } from "@/utils/api-client";
dayjs.extend(relativeTime);
// Tren pengaduan data (Mock for now)
const trenData = [
{ bulan: "Apr", jumlah: 35 },
{ bulan: "Mei", jumlah: 48 },
{ bulan: "Jun", jumlah: 42 },
{ bulan: "Jul", jumlah: 55 },
{ bulan: "Agu", jumlah: 50 },
{ bulan: "Sep", jumlah: 58 },
{ bulan: "Okt", jumlah: 52 },
];
interface TrendData {
bulan: string;
jumlah: number;
}
// Ide inovatif data (Mock for now)
const ideInovatif = [
{
nama: "Andi Prasetyo",
judul: "Penerapan Smart Village",
waktu: "3 hari yang lalu",
kategori: "Teknologi",
},
{
nama: "Rina Kusuma",
judul: "Program Ekowisata Desa",
waktu: "5 hari yang lalu",
kategori: "Ekonomi",
},
];
interface Complaint {
interface InnovationIdea {
id: string;
title: string;
description: string;
category: string;
submitterName: string;
submitterContact?: string;
status: string;
createdAt: string;
}
@@ -77,6 +58,14 @@ interface ServiceApiResponse {
};
}
interface Complaint {
id: string;
title: string;
category: string;
status: string;
createdAt: string;
}
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case "baru":
@@ -103,16 +92,21 @@ const PengaduanLayananPublik = () => {
});
const [recentComplaints, setRecentComplaints] = useState<Complaint[]>([]);
const [serviceStats, setServiceStats] = useState<ServiceStat[]>([]);
const [trendData, setTrendData] = useState<TrendData[]>([]);
const [innovationIdeas, setInnovationIdeas] = useState<InnovationIdea[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const [statsRes, recentRes, serviceRes] = await Promise.all([
apiClient.GET("/api/complaint/stats"),
apiClient.GET("/api/complaint/recent"),
apiClient.GET("/api/complaint/service-stats"),
]);
const [statsRes, recentRes, serviceRes, trendsRes, ideasRes] =
await Promise.all([
apiClient.GET("/api/complaint/stats"),
apiClient.GET("/api/complaint/recent"),
apiClient.GET("/api/complaint/service-stats"),
apiClient.GET("/api/complaint/trends"),
apiClient.GET("/api/complaint/innovation-ideas"),
]);
if (statsRes.data?.data) setStats(statsRes.data.data);
if (recentRes.data?.data)
@@ -126,6 +120,18 @@ const PengaduanLayananPublik = () => {
}));
setServiceStats(mappedService);
}
if (trendsRes.data?.data) {
const mappedTrends = (
trendsRes.data.data as { month: string; count: number }[]
).map((item) => ({
bulan: item.month,
jumlah: item.count,
}));
setTrendData(mappedTrends);
}
if (ideasRes.data?.data) {
setInnovationIdeas(ideasRes.data.data as InnovationIdea[]);
}
} catch (error) {
console.error("Failed to fetch complaint data", error);
} finally {
@@ -228,44 +234,57 @@ const PengaduanLayananPublik = () => {
</Title>
</Group>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={trenData}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#334155" : "#e5e7eb"}
/>
<XAxis
dataKey="bulan"
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
/>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1E293B" : "white",
borderColor: dark ? "#334155" : "#e5e7eb",
borderRadius: "8px",
}}
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
/>
<Line
type="monotone"
dataKey="jumlah"
stroke="#396aaaff"
strokeWidth={2}
dot={{
fill: "#1E3A5F",
strokeWidth: 2,
r: 4,
}}
activeDot={{ r: 6 }}
/>
</LineChart>
{loading ? (
<Group justify="center" align="center" h="100%">
<Loader />
</Group>
) : trendData.length > 0 ? (
<LineChart data={trendData}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#334155" : "#e5e7eb"}
/>
<XAxis
dataKey="bulan"
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
allowDecimals={false}
/>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1E293B" : "white",
borderColor: dark ? "#334155" : "#e5e7eb",
borderRadius: "8px",
}}
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
/>
<Line
type="monotone"
dataKey="jumlah"
stroke="#396aaaff"
strokeWidth={2}
dot={{
fill: "#1E3A5F",
strokeWidth: 2,
r: 4,
}}
activeDot={{ r: 6 }}
/>
</LineChart>
) : (
<Group justify="center" align="center" h="100%">
<Text size="sm" c="dimmed">
Tidak ada data pengaduan 7 bulan terakhir
</Text>
</Group>
)}
</ResponsiveContainer>
</Card>
@@ -415,41 +434,51 @@ const PengaduanLayananPublik = () => {
Ajuan Ide Inovatif
</Title>
<Stack gap="sm">
{ideInovatif.map((item) => (
<Card
key={item.judul}
p="sm"
radius="md"
withBorder
bg={dark ? "#334155" : "#F1F5F9"}
style={{
borderColor: "transparent",
transition: "background-color 0.15s ease",
}}
>
<Group justify="space-between">
<Stack gap={0}>
<Text fw={600} c={dark ? "white" : "gray.9"}>
{item.judul}
</Text>
<Text size="sm" c="dimmed">
{item.nama}
</Text>
<Text size="xs" c="dimmed">
{item.waktu}
</Text>
</Stack>
<Button
size="xs"
variant="light"
color="darmasaba-blue"
radius="md"
>
Detail
</Button>
</Group>
</Card>
))}
{loading ? (
<Group justify="center" py="xl">
<Loader />
</Group>
) : innovationIdeas.length > 0 ? (
innovationIdeas.map((item) => (
<Card
key={item.id}
p="sm"
radius="md"
withBorder
bg={dark ? "#334155" : "#F1F5F9"}
style={{
borderColor: "transparent",
transition: "background-color 0.15s ease",
}}
>
<Group justify="space-between">
<Stack gap={0}>
<Text fw={600} c={dark ? "white" : "gray.9"}>
{item.title}
</Text>
<Text size="sm" c="dimmed">
{item.submitterName}
</Text>
<Text size="xs" c="dimmed">
{dayjs(item.createdAt).fromNow()}
</Text>
</Stack>
<Button
size="xs"
variant="light"
color="darmasaba-blue"
radius="md"
>
Detail
</Button>
</Group>
</Card>
))
) : (
<Text c="dimmed" ta="center">
Tidak ada ide inovatif
</Text>
)}
</Stack>
</Card>
</Grid.Col>