feat: finalize kinerja divisi feature implementation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-02-11 12:38:28 +08:00
parent defdb2b7bd
commit ca2f97fa47
64 changed files with 725 additions and 38 deletions

View File

@@ -164,9 +164,6 @@ export function DashboardContent() {
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}>
<Users style={{ width: "70%", height: "70%" }} />
</ThemeIcon>
<Badge variant="light" radius="xl" size="lg" color="gray" style={{ position: 'absolute', top: 10, right: 10 }}>
87%
</Badge>
</Group>
</Card>
</Grid.Col>

View File

@@ -0,0 +1,271 @@
import React from "react";
import {
Button,
Card,
Badge,
Progress,
Title,
Text,
Group,
Stack,
Grid,
Table,
Box,
} from "@mantine/core";
// Sample Data
const kpiData = [
{
id: 1,
title: "Interaksi Hari Ini",
value: "61",
delta: "+15% dari kemarin",
deltaType: "positive",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6 text-muted-foreground"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H16.5m-13.5 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"
/>
</svg>
),
},
{
id: 2,
title: "Jawaban Otomatis",
value: "87%",
sub: "53 dari 61 interaksi",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6 text-muted-foreground"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.473-1.688 3.342-.48.485-.926.97-1.378 1.44c-1.472 1.58-2.306 2.787-2.91 3.514-.15.18-.207.33-.207.33A.75.75 0 0 1 15 21h-3c-1.104 0-2.08-.542-2.657-1.455-.139-.201-.264-.406-.38-.614l-.014-.025C8.85 18.067 8.156 17.2 7.5 16.325.728 12.56.728 7.44 7.5 3.675c3.04-.482 5.584.47 7.042 1.956.674.672 1.228 1.462 1.696 2.307.426.786.793 1.582 1.113 2.392h.001Z"
/>
</svg>
),
},
{
id: 3,
title: "Belum Ditindak",
value: "8",
sub: "Perlu respon manual",
deltaType: "negative",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6 text-muted-foreground"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
/>
</svg>
),
},
{
id: 4,
title: "Waktu Respon",
value: "2.3 sec",
sub: "Rata-rata",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6 text-muted-foreground"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
),
},
];
const chartData = [
{ day: "Sen", total: 100 },
{ day: "Sel", total: 120 },
{ day: "Rab", total: 90 },
{ day: "Kam", total: 150 },
{ day: "Jum", total: 110 },
{ day: "Sab", total: 80 },
{ day: "Min", total: 130 },
];
const topTopics = [
{ topic: "Cara mengurus KTP", count: 89 },
{ topic: "Syarat Kartu Keluarga", count: 76 },
{ topic: "Jadwal Posyandu", count: 64 },
{ topic: "Pengaduan jalan rusak", count: 52 },
{ topic: "Info program bansos", count: 48 },
];
const busyHours = [
{ period: "Pagi (0812)", percentage: 30 },
{ period: "Siang (1216)", percentage: 40 },
{ period: "Sore (1620)", percentage: 20 },
{ period: "Malam (2008)", percentage: 10 },
];
const JennaAnalytic = () => {
return (
<Box p="md">
<Stack gap="xl">
<Group justify="space-between" align="center">
<Title order={1} fw={700}>
Jenna Analytic
</Title>
<Button variant="filled">Export Data</Button>
</Group>
{/* KPI Cards */}
<Grid gutter="lg">
{kpiData.map((kpi) => (
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
<Card shadow="sm" padding="lg" radius="md" withBorder>
<Group justify="space-between" align="flex-start" mb="xs">
<Text size="sm" fw={500} c="dimmed">
{kpi.title}
</Text>
{React.cloneElement(kpi.icon, {
className: "h-6 w-6", // Keeping classes for now, can be replaced by Mantine Icon component if available or styled with sx prop
color: "var(--mantine-color-dimmed)", // Set color via prop
})}
</Group>
<Title order={3} fw={700} mt="xs">
{kpi.value}
</Title>
{kpi.delta && (
<Text
size="xs"
c={
kpi.deltaType === "positive"
? "green"
: kpi.deltaType === "negative"
? "red"
: "dimmed"
}
mt={4}
>
{kpi.delta}
</Text>
)}
{kpi.sub && (
<Text size="xs" c="dimmed" mt={2}>
{kpi.sub}
</Text>
)}
</Card>
</Grid.Col>
))}
</Grid>
{/* Charts and Lists Section */}
<Grid gutter="lg">
{/* Grafik Interaksi Chatbot (now Table) */}
<Grid.Col span={{ base: 12, lg: 6 }}>
<Card shadow="sm" padding="lg" radius="md" withBorder>
<Title order={3} fw={500} mb="md">
Interaksi Chatbot
</Title>
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Day</Table.Th>
<Table.Th>Total Interactions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{chartData.map((item, index) => (
<Table.Tr key={index}>
<Table.Td>{item.day}</Table.Td>
<Table.Td>{item.total}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Card>
</Grid.Col>
{/* Topik Pertanyaan Terbanyak & Jam Tersibuk */}
<Grid.Col span={{ base: 12, lg: 6 }}>
<Stack gap="lg">
{/* Topik Pertanyaan Terbanyak */}
<Card shadow="sm" padding="lg" radius="md" withBorder>
<Title order={3} fw={500} mb="md">
Topik Pertanyaan Terbanyak
</Title>
<Stack gap="xs">
{topTopics.map((item, index) => (
<Group
key={index}
justify="space-between"
align="center"
p="xs"
style={{ backgroundColor: 'var(--mantine-color-gray-0)', borderRadius: 'var(--mantine-radius-sm)' }}
>
<Text size="sm" fw={500}>
{item.topic}
</Text>
<Badge variant="light" color="gray">
{item.count}x
</Badge>
</Group>
))}
</Stack>
</Card>
{/* Jam Tersibuk */}
<Card shadow="sm" padding="lg" radius="md" withBorder>
<Title order={3} fw={500} mb="md">
Jam Tersibuk
</Title>
<Stack gap="sm">
{busyHours.map((item, index) => (
<Group key={index} align="center">
<Text w={80} size="sm">
{item.period}
</Text>
<Progress value={item.percentage} flex={1} />
<Text size="sm" fw={500}>
{item.percentage}%
</Text>
</Group>
))}
</Stack>
</Card>
</Stack>
</Grid.Col>
</Grid>
</Stack>
</Box>
);
}
export default JennaAnalytic;

View File

@@ -1,13 +1,13 @@
import { Badge } from "@/app/components/ui/badge";
import { Button } from "@/app/components/ui/button"; // Correct import for Button
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; // Correct import for Button
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/app/components/ui/card";
import { Progress } from "@/app/components/ui/progress";
} from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import {
Table,
TableBody,
@@ -15,7 +15,7 @@ import {
TableHead,
TableHeader,
TableRow,
} from "@/app/components/ui/table";
} from "@/components/ui/table";
const KinerjaDivisi = () => {
// Sample data for division performance
@@ -87,7 +87,7 @@ const KinerjaDivisi = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Divisi</CardTitle>
<CardTitle className="text-sm font-medium dark:text-gray-100">Total Divisi</CardTitle>
<div className="h-6 w-6 text-muted-foreground">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -113,7 +113,7 @@ const KinerjaDivisi = () => {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
<CardTitle className="text-sm font-medium dark:text-gray-100">
Rata-rata Pencapaian
</CardTitle>
<div className="h-6 w-6 text-muted-foreground">
@@ -141,7 +141,7 @@ const KinerjaDivisi = () => {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
<CardTitle className="text-sm font-medium dark:text-gray-100">
Divisi Melebihi Target
</CardTitle>
<div className="h-6 w-6 text-muted-foreground">
@@ -170,19 +170,19 @@ const KinerjaDivisi = () => {
<Card>
<CardHeader>
<CardTitle>Detail Kinerja Divisi</CardTitle>
<CardTitle className="dark:text-gray-100">Detail Kinerja Divisi</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Nama Divisi</TableHead>
<TableHead>Target (%)</TableHead>
<TableHead>Pencapaian (%)</TableHead>
<TableHead>Status</TableHead>
<TableHead>Proyek Aktif</TableHead>
<TableHead>Anggaran</TableHead>
<TableHead>Terakhir Diperbarui</TableHead>
<TableHead className="dark:text-white">Nama Divisi</TableHead>
<TableHead className="dark:text-white">Target (%)</TableHead>
<TableHead className="dark:text-white">Pencapaian (%)</TableHead>
<TableHead className="dark:text-white">Status</TableHead>
<TableHead className="dark:text-white">Proyek Aktif</TableHead>
<TableHead className="dark:text-white">Anggaran</TableHead>
<TableHead className="dark:text-white">Terakhir Diperbarui</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -236,9 +236,8 @@ const KinerjaDivisi = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>Grafik Pencapaian Divisi</CardTitle>
</CardHeader>
<CardContent>
<CardTitle className="dark:text-gray-100">Grafik Pencapaian Divisi</CardTitle>
</CardHeader> <CardContent>
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-gray-500 dark:text-gray-300">
Grafik pencapaian akan ditampilkan di sini
@@ -249,9 +248,8 @@ const KinerjaDivisi = () => {
<Card>
<CardHeader>
<CardTitle>Distribusi Anggaran Divisi</CardTitle>
</CardHeader>
<CardContent>
<CardTitle className="dark:text-gray-100">Distribusi Anggaran Divisi</CardTitle>
</CardHeader> <CardContent>
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-gray-500 dark:text-gray-300">
Diagram distribusi anggaran akan ditampilkan di sini

View File

@@ -1,15 +1,15 @@
import type React from "react";
import { useState } from "react";
import { Badge } from "@/app/components/ui/badge";
import { Button } from "@/app/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/app/components/ui/card";
import { Input } from "@/app/components/ui/input";
import { Select } from "@/app/components/ui/select";
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Select } from "@/components/ui/select";
import {
Table,
TableBody,
@@ -17,8 +17,8 @@ import {
TableHead,
TableHeader,
TableRow,
} from "@/app/components/ui/table";
import { Textarea } from "@/app/components/ui/textarea";
} from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
const PengaduanLayananPublik = () => {
const [activeTab, setActiveTab] = useState<"complaints" | "services">(

View File

@@ -23,7 +23,7 @@ export function Sidebar({ className }: SidebarProps) {
{ name: "Beranda", path: "/dashboard" },
{ name: "Kinerja Divisi", path: "/dashboard/kinerja-divisi" },
{ name: "Pengaduan & Layanan Publik", path: "/dashboard/pengaduan-layanan-publik" },
{ name: "Jenna Analytic", path: "/dashboard/analytic" },
{ name: "Jenna Analytic", path: "/dashboard/jenna-analytic" },
{ name: "Demografi & Kependudukan", path: "/dashboard/demografi" },
{ name: "Keuangan & Anggaran", path: "/dashboard/keuangan" },
{ name: "Bumdes & UMKM Desa", path: "/dashboard/bumdes" },

View File

@@ -22,6 +22,7 @@ import { Route as UsersIdRouteImport } from './routes/users/$id'
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
import { Route as DashboardPengaduanLayananPublikRouteImport } from './routes/dashboard/pengaduan-layanan-publik'
import { Route as DashboardKinerjaDivisiRouteImport } from './routes/dashboard/kinerja-divisi'
import { Route as DashboardJennaAnalyticRouteImport } from './routes/dashboard/jenna-analytic'
import { Route as AdminUsersRouteImport } from './routes/admin/users'
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
import { Route as AdminApikeyRouteImport } from './routes/admin/apikey'
@@ -92,6 +93,11 @@ const DashboardKinerjaDivisiRoute = DashboardKinerjaDivisiRouteImport.update({
path: '/kinerja-divisi',
getParentRoute: () => DashboardRouteRoute,
} as any)
const DashboardJennaAnalyticRoute = DashboardJennaAnalyticRouteImport.update({
id: '/jenna-analytic',
path: '/jenna-analytic',
getParentRoute: () => DashboardRouteRoute,
} as any)
const AdminUsersRoute = AdminUsersRouteImport.update({
id: '/users',
path: '/users',
@@ -117,6 +123,7 @@ export interface FileRoutesByFullPath {
'/admin/apikey': typeof AdminApikeyRoute
'/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
'/profile/edit': typeof ProfileEditRoute
@@ -133,6 +140,7 @@ export interface FileRoutesByTo {
'/admin/apikey': typeof AdminApikeyRoute
'/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
'/profile/edit': typeof ProfileEditRoute
@@ -152,6 +160,7 @@ export interface FileRoutesById {
'/admin/apikey': typeof AdminApikeyRoute
'/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
'/profile/edit': typeof ProfileEditRoute
@@ -172,6 +181,7 @@ export interface FileRouteTypes {
| '/admin/apikey'
| '/admin/settings'
| '/admin/users'
| '/dashboard/jenna-analytic'
| '/dashboard/kinerja-divisi'
| '/dashboard/pengaduan-layanan-publik'
| '/profile/edit'
@@ -188,6 +198,7 @@ export interface FileRouteTypes {
| '/admin/apikey'
| '/admin/settings'
| '/admin/users'
| '/dashboard/jenna-analytic'
| '/dashboard/kinerja-divisi'
| '/dashboard/pengaduan-layanan-publik'
| '/profile/edit'
@@ -206,6 +217,7 @@ export interface FileRouteTypes {
| '/admin/apikey'
| '/admin/settings'
| '/admin/users'
| '/dashboard/jenna-analytic'
| '/dashboard/kinerja-divisi'
| '/dashboard/pengaduan-layanan-publik'
| '/profile/edit'
@@ -321,6 +333,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardKinerjaDivisiRouteImport
parentRoute: typeof DashboardRouteRoute
}
'/dashboard/jenna-analytic': {
id: '/dashboard/jenna-analytic'
path: '/jenna-analytic'
fullPath: '/dashboard/jenna-analytic'
preLoaderRoute: typeof DashboardJennaAnalyticRouteImport
parentRoute: typeof DashboardRouteRoute
}
'/admin/users': {
id: '/admin/users'
path: '/users'
@@ -364,12 +383,14 @@ const AdminRouteRouteWithChildren = AdminRouteRoute._addFileChildren(
)
interface DashboardRouteRouteChildren {
DashboardJennaAnalyticRoute: typeof DashboardJennaAnalyticRoute
DashboardKinerjaDivisiRoute: typeof DashboardKinerjaDivisiRoute
DashboardPengaduanLayananPublikRoute: typeof DashboardPengaduanLayananPublikRoute
DashboardIndexRoute: typeof DashboardIndexRoute
}
const DashboardRouteRouteChildren: DashboardRouteRouteChildren = {
DashboardJennaAnalyticRoute: DashboardJennaAnalyticRoute,
DashboardKinerjaDivisiRoute: DashboardKinerjaDivisiRoute,
DashboardPengaduanLayananPublikRoute: DashboardPengaduanLayananPublikRoute,
DashboardIndexRoute: DashboardIndexRoute,

View File

@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import { DashboardContent } from "../../app/components/dashboard-content";
import { DashboardContent } from "@/components/dashboard-content";
export const Route = createFileRoute("/dashboard/")({
component: DashboardContent,
});

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import JennaAnalytic from '@/components/jenna-analytic'
export const Route = createFileRoute('/dashboard/jenna-analytic')({
component: JennaAnalytic,
})

View File

@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import KinerjaDivisi from "../../app/components/kinerja-divisi";
import KinerjaDivisi from "@/components/kinerja-divisi";
export const Route = createFileRoute("/dashboard/kinerja-divisi")({
component: KinerjaDivisi,
});

View File

@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import PengaduanLayananPublik from "../../app/components/pengaduan-layanan-publik";
import PengaduanLayananPublik from "@/components/pengaduan-layanan-publik";
export const Route = createFileRoute("/dashboard/pengaduan-layanan-publik")({
component: PengaduanLayananPublik,
});

View File

@@ -1,6 +1,6 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { Header } from "@/app/components/header";
import { Sidebar } from "@/app/components/sidebar";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { AppShell, Burger, Group } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";