refactor: modularize kinerja-divisi components per PromptDashboard.md

- Create ActivityCard component for program kegiatan cards
- Create DivisionList component for divisi teraktif with arrow icons
- Create DocumentChart component for bar chart (jumlah dokumen)
- Create ProgressChart component for pie chart (progres kegiatan)
- Create DiscussionPanel component for diskusi internal
- Create EventCard component for agenda hari ini
- Create ArchiveCard component for arsip digital perangkat desa
- Refactor main KinerjaDivisi component to use new modular components
- Implement responsive 3-column grid layout
- Add proper dark mode support with specified colors
- Add hover effects and smooth animations

New components structure:
  src/components/kinerja-divisi/
    - activity-card.tsx
    - archive-card.tsx
    - discussion-panel.tsx
    - division-list.tsx
    - document-chart.tsx
    - event-card.tsx
    - progress-chart.tsx
    - index.ts (exports)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-13 16:47:19 +08:00
parent 952f7ecb16
commit 8c35d58b38
9 changed files with 613 additions and 527 deletions

View File

@@ -0,0 +1,95 @@
import {
Box,
Card,
Group,
Progress,
Text,
useMantineColorScheme,
} from "@mantine/core";
interface ActivityCardProps {
title: string;
date: string;
progress: number;
status: "selesai" | "berjalan" | "tertunda";
}
export function ActivityCard({
title,
date,
progress,
status,
}: ActivityCardProps) {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const getStatusColor = (s: string) => {
switch (s) {
case "selesai":
return "#22C55E";
case "berjalan":
return "#3B82F6";
case "tertunda":
return "#EF4444";
default:
return "#9CA3AF";
}
};
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
>
<Box
style={{
borderLeft: `4px solid #3B82F6`,
paddingLeft: 12,
marginBottom: 12,
}}
>
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
{title}
</Text>
</Box>
<Group justify="space-between" mb="xs">
<Text size="xs" c="dimmed">
{date}
</Text>
<Box
style={{
backgroundColor: getStatusColor(status),
color: "white",
padding: "2px 8px",
borderRadius: 4,
fontSize: 11,
fontWeight: 600,
}}
>
{status.toUpperCase()}
</Box>
</Group>
<Progress
value={progress}
size="sm"
radius="xl"
color={progress === 100 ? "green" : "yellow"}
animated={progress < 100}
/>
<Text size="xs" c="dimmed" mt="xs" ta="right">
{progress}%
</Text>
</Card>
);
}

View File

@@ -0,0 +1,41 @@
import { Card, Group, Text, useMantineColorScheme } from "@mantine/core";
import { FileText } from "lucide-react";
interface ArchiveItem {
name: string;
}
interface ArchiveCardProps {
item: ArchiveItem;
onClick?: () => void;
}
export function ArchiveCard({ item, onClick }: ArchiveCardProps) {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "#e5e7eb",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
cursor: "pointer",
transition: "transform 0.2s, box-shadow 0.2s",
}}
onClick={onClick}
>
<Group gap="md">
<FileText size={32} color={dark ? "#60A5FA" : "#3B82F6"} />
<Text size="sm" fw={500} c={dark ? "white" : "#1E3A5F"}>
{item.name}
</Text>
</Group>
</Card>
);
}

View File

@@ -0,0 +1,92 @@
import {
Box,
Card,
Group,
Stack,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { MessageCircle } from "lucide-react";
interface DiscussionItem {
message: string;
sender: string;
date: string;
}
const discussions: DiscussionItem[] = [
{
message: "Kepada Pelayanan, mohon di cek...",
sender: "I.B Surya Prabhawa Manu",
date: "12 Apr 2025",
},
{
message: "Kepada staf perencanaan @suar...",
sender: "Ni Nyoman Yuliani",
date: "14 Jun 2025",
},
{
message: "ijin atau mohon kepada KBD sar...",
sender: "Ni Wayan Martini",
date: "12 Apr 2025",
},
];
export function DiscussionPanel() {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
>
<Group gap="xs" mb="md">
<MessageCircle size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
Diskusi
</Text>
</Group>
<Stack gap="sm">
{discussions.map((discussion, index) => (
<Card
key={index}
p="sm"
radius="md"
withBorder
bg={dark ? "#334155" : "#F1F5F9"}
style={{
borderColor: dark ? "#334155" : "#F1F5F9",
}}
>
<Text
size="sm"
c={dark ? "white" : "#1E3A5F"}
fw={500}
mb="xs"
lineClamp={2}
>
{discussion.message}
</Text>
<Group justify="space-between">
<Text size="xs" c="dimmed">
{discussion.sender}
</Text>
<Text size="xs" c="dimmed">
{discussion.date}
</Text>
</Group>
</Card>
))}
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,77 @@
import {
Box,
Card,
Group,
Stack,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { ChevronRight } from "lucide-react";
interface DivisionItem {
name: string;
count: number;
}
const divisionData: DivisionItem[] = [
{ name: "Kesejahteraan", count: 37 },
{ name: "Pemerintahan", count: 26 },
{ name: "Keuangan", count: 17 },
{ name: "Sekretaris Desa", count: 15 },
{ name: "Tata Usaha TK", count: 14 },
{ name: "Perangkat Kewilayahan", count: 12 },
{ name: "Pelayanan", count: 10 },
{ name: "Perencanaan", count: 9 },
{ name: "Tata Usaha & Umum", count: 7 },
];
export function DivisionList() {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
h="100%"
>
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"} mb="md">
Divisi Teraktif
</Text>
<Stack gap="xs">
{divisionData.map((division, index) => (
<Group
key={index}
justify="space-between"
align="center"
style={{
padding: "8px 12px",
borderRadius: 8,
backgroundColor: dark ? "#334155" : "#F1F5F9",
transition: "background-color 0.2s",
cursor: "pointer",
}}
>
<Text size="sm" c={dark ? "white" : "#1E3A5F"}>
{division.name}
</Text>
<Group gap="xs">
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
{division.count}
</Text>
<ChevronRight size={16} color={dark ? "#94A3B8" : "#64748B"} />
</Group>
</Group>
))}
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,69 @@
import { Card, Text, useMantineColorScheme } from "@mantine/core";
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
const documentData = [
{ name: "Gambar", value: 300 },
{ name: "Dokumen", value: 310 },
];
export function DocumentChart() {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
h="100%"
>
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"} mb="md">
Jumlah Dokumen
</Text>
<ResponsiveContainer width="100%" height={200}>
<BarChart data={documentData}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#334155" : "#e5e7eb"}
/>
<XAxis
dataKey="name"
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" }}
/>
<Bar dataKey="value" fill="#3B82F6" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</Card>
);
}

View File

@@ -0,0 +1,65 @@
import {
Box,
Card,
Group,
Stack,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { Calendar } from "lucide-react";
interface AgendaItem {
time: string;
event: string;
}
interface EventCardProps {
agendas?: AgendaItem[];
}
export function EventCard({ agendas = [] }: EventCardProps) {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
>
<Group gap="xs" mb="md">
<Calendar size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
Acara Hari Ini
</Text>
</Group>
{agendas.length > 0 ? (
<Stack gap="sm">
{agendas.map((agenda, index) => (
<Group key={index} align="flex-start" gap="md">
<Box w={60}>
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
{agenda.time}
</Text>
</Box>
<Text size="sm" c={dark ? "white" : "#1E3A5F"}>
{agenda.event}
</Text>
</Group>
))}
</Stack>
) : (
<Text c="dimmed" ta="center" py="md">
Tidak ada acara hari ini
</Text>
)}
</Card>
);
}

View File

@@ -0,0 +1,7 @@
export { ActivityCard } from "./activity-card";
export { ArchiveCard } from "./archive-card";
export { DiscussionPanel } from "./discussion-panel";
export { DivisionList } from "./division-list";
export { DocumentChart } from "./document-chart";
export { EventCard } from "./event-card";
export { ProgressChart } from "./progress-chart";

View File

@@ -0,0 +1,84 @@
import {
Box,
Card,
Group,
Stack,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
const progressData = [
{ name: "Selesai", value: 83.33, color: "#22C55E" },
{ name: "Dikerjakan", value: 16.67, color: "#FACC15" },
{ name: "Segera Dikerjakan", value: 0, color: "#3B82F6" },
{ name: "Dibatalkan", value: 0, color: "#EF4444" },
];
export function ProgressChart() {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
bg={dark ? "#1E293B" : "white"}
style={{
borderColor: dark ? "#334155" : "white",
boxShadow: dark
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
}}
h="100%"
>
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"} mb="md">
Progres Kegiatan
</Text>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie
data={progressData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={2}
dataKey="value"
>
{progressData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1E293B" : "white",
borderColor: dark ? "#334155" : "#e5e7eb",
borderRadius: "8px",
}}
/>
</PieChart>
</ResponsiveContainer>
<Stack gap="xs" mt="md">
{progressData.map((item, index) => (
<Group key={index} justify="space-between">
<Group gap="xs">
<Box
w={12}
h={12}
style={{ backgroundColor: item.color, borderRadius: 2 }}
/>
<Text size="sm" c={dark ? "white" : "gray.7"}>
{item.name}
</Text>
</Group>
<Text size="sm" fw={600} c={dark ? "white" : "gray.9"}>
{item.value.toFixed(2)}%
</Text>
</Group>
))}
</Stack>
</Card>
);
}