feat(dashboard): connect dashboard components to database
This commit is contained in:
@@ -239,6 +239,40 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/complaint/service-trends": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get service letter trends for last 6 months */
|
||||||
|
get: operations["getApiComplaintService-trends"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/complaint/service-weekly": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get service letter count for current week */
|
||||||
|
get: operations["getApiComplaintService-weekly"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/resident/stats": {
|
"/api/resident/stats": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -966,6 +1000,40 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"getApiComplaintService-trends": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiComplaintService-weekly": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
getApiResidentStats: {
|
getApiResidentStats: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
@@ -1477,6 +1477,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/complaint/service-trends": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getApiComplaintService-trends",
|
||||||
|
"summary": "Get service letter trends for last 6 months",
|
||||||
|
"responses": {
|
||||||
|
"200": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/complaint/service-weekly": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getApiComplaintService-weekly",
|
||||||
|
"summary": "Get service letter count for current week",
|
||||||
|
"responses": {
|
||||||
|
"200": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/resident/stats": {
|
"/api/resident/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getApiResidentStats",
|
"operationId": "getApiResidentStats",
|
||||||
|
|||||||
@@ -82,4 +82,56 @@ export const complaint = new Elysia({
|
|||||||
{
|
{
|
||||||
detail: { summary: "Get recent innovation ideas" },
|
detail: { summary: "Get recent innovation ideas" },
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/service-trends",
|
||||||
|
async ({ set }) => {
|
||||||
|
try {
|
||||||
|
// Get last 6 months trends for service letters
|
||||||
|
const trends = await prisma.$queryRaw<any[]>`
|
||||||
|
SELECT
|
||||||
|
TO_CHAR("createdAt", 'Mon') as month,
|
||||||
|
EXTRACT(MONTH FROM "createdAt") as month_num,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM service_letter
|
||||||
|
WHERE "createdAt" > NOW() - INTERVAL '6 months'
|
||||||
|
GROUP BY month, month_num
|
||||||
|
ORDER BY month_num ASC
|
||||||
|
`;
|
||||||
|
return { data: trends };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ error }, "Failed to fetch service trends");
|
||||||
|
set.status = 500;
|
||||||
|
return { error: "Internal Server Error" };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: { summary: "Get service letter trends for last 6 months" },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/service-weekly",
|
||||||
|
async ({ set }) => {
|
||||||
|
try {
|
||||||
|
const startOfWeek = new Date();
|
||||||
|
startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
|
||||||
|
startOfWeek.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const count = await prisma.serviceLetter.count({
|
||||||
|
where: {
|
||||||
|
createdAt: {
|
||||||
|
gte: startOfWeek,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return { data: { count } };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ error }, "Failed to fetch weekly service stats");
|
||||||
|
set.status = 500;
|
||||||
|
return { error: "Internal Server Error" };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: { summary: "Get service letter count for current week" },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,16 +37,20 @@ export function DashboardContent() {
|
|||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
complaints: { total: 0, baru: 0, proses: 0, selesai: 0 },
|
complaints: { total: 0, baru: 0, proses: 0, selesai: 0 },
|
||||||
residents: { total: 0, heads: 0, poor: 0 },
|
residents: { total: 0, heads: 0, poor: 0 },
|
||||||
|
weeklyService: 0,
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
try {
|
try {
|
||||||
const [complaintRes, residentRes] = await Promise.all([
|
const [complaintRes, residentRes, weeklyServiceRes] = await Promise.all(
|
||||||
apiClient.GET("/api/complaint/stats"),
|
[
|
||||||
apiClient.GET("/api/resident/stats"),
|
apiClient.GET("/api/complaint/stats"),
|
||||||
]);
|
apiClient.GET("/api/resident/stats"),
|
||||||
|
apiClient.GET("/api/complaint/service-weekly"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
complaints: (complaintRes.data as any)?.data || {
|
complaints: (complaintRes.data as any)?.data || {
|
||||||
@@ -60,6 +64,7 @@ export function DashboardContent() {
|
|||||||
heads: 0,
|
heads: 0,
|
||||||
poor: 0,
|
poor: 0,
|
||||||
},
|
},
|
||||||
|
weeklyService: (weeklyServiceRes.data as any)?.data?.count || 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -78,8 +83,8 @@ export function DashboardContent() {
|
|||||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Surat Minggu Ini"
|
title="Surat Minggu Ini"
|
||||||
value={0}
|
value={stats.weeklyService}
|
||||||
detail="Menunggu integrasi riil"
|
detail="Total surat diajukan"
|
||||||
trend="0%"
|
trend="0%"
|
||||||
trendValue={0}
|
trendValue={0}
|
||||||
icon={<FileText style={{ width: "70%", height: "70%" }} />}
|
icon={<FileText style={{ width: "70%", height: "70%" }} />}
|
||||||
|
|||||||
@@ -2,28 +2,51 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { Calendar } from "lucide-react";
|
import { Calendar } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
|
||||||
interface EventData {
|
interface EventData {
|
||||||
date: string;
|
date: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const events: EventData[] = [
|
|
||||||
{ date: "1 Oktober 2025", title: "Hari Kesaktian Pancasila" },
|
|
||||||
{ date: "15 Oktober 2025", title: "Davest" },
|
|
||||||
{ date: "19 Oktober 2025", title: "Rapat Koordinasi" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function ActivityList() {
|
export function ActivityList() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
|
const [data, setData] = useState<EventData[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchEvents() {
|
||||||
|
try {
|
||||||
|
const res = await apiClient.GET("/api/event/");
|
||||||
|
if (res.data?.data) {
|
||||||
|
setData(
|
||||||
|
(res.data.data as any[]).map((e) => ({
|
||||||
|
date: dayjs(e.startDate).format("D MMMM YYYY"),
|
||||||
|
title: e.title,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch events", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEvents();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
p="md"
|
||||||
@@ -48,22 +71,32 @@ export function ActivityList() {
|
|||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{events.map((event) => (
|
{loading ? (
|
||||||
<Box
|
<Group justify="center" py="xl">
|
||||||
key={`${event.title}-${event.date}`}
|
<Loader />
|
||||||
style={{
|
</Group>
|
||||||
borderLeft: "4px solid var(--mantine-color-blue-filled)",
|
) : data.length > 0 ? (
|
||||||
paddingLeft: 12,
|
data.map((event) => (
|
||||||
}}
|
<Box
|
||||||
>
|
key={`${event.title}-${event.date}`}
|
||||||
<Text size="sm" c="dimmed">
|
style={{
|
||||||
{event.date}
|
borderLeft: "4px solid var(--mantine-color-blue-filled)",
|
||||||
</Text>
|
paddingLeft: 12,
|
||||||
<Text fw={500} c={dark ? "white" : "gray.9"}>
|
}}
|
||||||
{event.title}
|
>
|
||||||
</Text>
|
<Text size="sm" c="dimmed">
|
||||||
</Box>
|
{event.date}
|
||||||
))}
|
</Text>
|
||||||
|
<Text fw={500} c={dark ? "white" : "gray.9"}>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
|
Tidak ada kegiatan mendatang
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
BarChart,
|
BarChart,
|
||||||
@@ -16,20 +18,37 @@ import {
|
|||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
const chartData = [
|
|
||||||
{ month: "Jan", value: 150 },
|
|
||||||
{ month: "Feb", value: 165 },
|
|
||||||
{ month: "Mar", value: 195 },
|
|
||||||
{ month: "Apr", value: 160 },
|
|
||||||
{ month: "Mei", value: 205 },
|
|
||||||
{ month: "Jun", value: 185 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function ChartSurat() {
|
export function ChartSurat() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchTrends() {
|
||||||
|
try {
|
||||||
|
const res = await apiClient.GET("/api/complaint/service-trends");
|
||||||
|
if (res.data?.data) {
|
||||||
|
setData(
|
||||||
|
(res.data.data as any[]).map((d) => ({
|
||||||
|
month: d.month,
|
||||||
|
value: Number(d.count),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch service trends", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTrends();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
p="md"
|
||||||
@@ -72,39 +91,44 @@ export function ChartSurat() {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
<BarChart data={chartData}>
|
{loading ? (
|
||||||
<CartesianGrid
|
<Group justify="center" align="center" h="100%">
|
||||||
strokeDasharray="3 3"
|
<Loader />
|
||||||
vertical={false}
|
</Group>
|
||||||
stroke={dark ? "#334155" : "#e5e7eb"}
|
) : (
|
||||||
/>
|
<BarChart data={data}>
|
||||||
<XAxis
|
<CartesianGrid
|
||||||
dataKey="month"
|
strokeDasharray="3 3"
|
||||||
axisLine={false}
|
vertical={false}
|
||||||
tickLine={false}
|
stroke={dark ? "#334155" : "#e5e7eb"}
|
||||||
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
/>
|
||||||
/>
|
<XAxis
|
||||||
<YAxis
|
dataKey="month"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
ticks={[0, 55, 110, 165, 220]}
|
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
||||||
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
/>
|
||||||
/>
|
<YAxis
|
||||||
<Tooltip
|
axisLine={false}
|
||||||
contentStyle={{
|
tickLine={false}
|
||||||
backgroundColor: dark ? "#1E293B" : "white",
|
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
||||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
/>
|
||||||
borderRadius: "8px",
|
<Tooltip
|
||||||
boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
|
contentStyle={{
|
||||||
}}
|
backgroundColor: dark ? "#1E293B" : "white",
|
||||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||||
/>
|
borderRadius: "8px",
|
||||||
<Bar
|
boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
|
||||||
dataKey="value"
|
}}
|
||||||
fill="var(--mantine-color-blue-filled)"
|
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||||
radius={[4, 4, 0, 0]}
|
/>
|
||||||
/>
|
<Bar
|
||||||
</BarChart>
|
dataKey="value"
|
||||||
|
fill="var(--mantine-color-blue-filled)"
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
)}
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,31 +2,52 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
Progress,
|
Progress,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
|
||||||
interface DivisionData {
|
interface DivisionData {
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const divisionData: DivisionData[] = [
|
|
||||||
{ name: "Kesejahteraan", value: 37 },
|
|
||||||
{ name: "Pemberdayaan", value: 26 },
|
|
||||||
{ name: "Keuangan", value: 17 },
|
|
||||||
{ name: "Sekretaris Desa", value: 15 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const max_value = 37;
|
|
||||||
|
|
||||||
export function DivisionProgress() {
|
export function DivisionProgress() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
|
const [data, setData] = useState<DivisionData[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchDivisions() {
|
||||||
|
try {
|
||||||
|
const res = await apiClient.GET("/api/division/");
|
||||||
|
if (res.data?.data) {
|
||||||
|
setData(
|
||||||
|
(res.data.data as any[]).map((d) => ({
|
||||||
|
name: d.name,
|
||||||
|
value: d._count?.activities || 0,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch division stats", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDivisions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const max_value = Math.max(...data.map((d) => d.value), 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
p="md"
|
||||||
@@ -45,25 +66,35 @@ export function DivisionProgress() {
|
|||||||
Divisi Teraktif
|
Divisi Teraktif
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{divisionData.map((divisi) => (
|
{loading ? (
|
||||||
<Box key={divisi.name}>
|
<Group justify="center" py="xl">
|
||||||
<Group justify="space-between" mb={5}>
|
<Loader />
|
||||||
<Text size="sm" fw={500} c={dark ? "white" : "gray.7"}>
|
</Group>
|
||||||
{divisi.name}
|
) : data.length > 0 ? (
|
||||||
</Text>
|
data.map((divisi) => (
|
||||||
<Text size="sm" fw={600} c={dark ? "white" : "gray.9"}>
|
<Box key={divisi.name}>
|
||||||
{divisi.value} Kegiatan
|
<Group justify="space-between" mb={5}>
|
||||||
</Text>
|
<Text size="sm" fw={500} c={dark ? "white" : "gray.7"}>
|
||||||
</Group>
|
{divisi.name}
|
||||||
<Progress
|
</Text>
|
||||||
value={(divisi.value / max_value) * 100}
|
<Text size="sm" fw={600} c={dark ? "white" : "gray.9"}>
|
||||||
size="sm"
|
{divisi.value} Kegiatan
|
||||||
radius="xl"
|
</Text>
|
||||||
color="blue"
|
</Group>
|
||||||
animated
|
<Progress
|
||||||
/>
|
value={(divisi.value / max_value) * 100}
|
||||||
</Box>
|
size="sm"
|
||||||
))}
|
radius="xl"
|
||||||
|
color="blue"
|
||||||
|
animated
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
|
Tidak ada data divisi
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user