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:
@@ -1,545 +1,101 @@
|
||||
import { Grid, Stack } from "@mantine/core";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Card,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
List,
|
||||
Badge as MantineBadge,
|
||||
Progress as MantineProgress,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
Pie,
|
||||
PieChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { Button } from "@/components/ui/button";
|
||||
ActivityCard,
|
||||
ArchiveCard,
|
||||
DiscussionPanel,
|
||||
DivisionList,
|
||||
DocumentChart,
|
||||
EventCard,
|
||||
ProgressChart,
|
||||
} from ".";
|
||||
|
||||
// Data for program kegiatan (Section 1)
|
||||
const programKegiatanData = [
|
||||
{
|
||||
title: "Rakor 2025",
|
||||
date: "3 Juli 2025",
|
||||
progress: 90,
|
||||
status: "selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Pemutakhiran Indeks Desa",
|
||||
date: "3 Juli 2025",
|
||||
progress: 85,
|
||||
status: "selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Mengurus Akta Cerai Warga",
|
||||
date: "3 Juli 2025",
|
||||
progress: 80,
|
||||
status: "selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Pasek 7 Desa Adat",
|
||||
date: "3 Juli 2025",
|
||||
progress: 92,
|
||||
status: "selesai" as const,
|
||||
},
|
||||
];
|
||||
|
||||
// Data for arsip digital (Section 5)
|
||||
const archiveData = [
|
||||
{ name: "Surat Keputusan" },
|
||||
{ name: "Dokumentasi" },
|
||||
{ name: "Laporan Keuangan" },
|
||||
{ name: "Notulensi Rapat" },
|
||||
];
|
||||
|
||||
const KinerjaDivisi = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === "dark";
|
||||
|
||||
// Data for division progress chart
|
||||
const divisionProgressData = [
|
||||
{ name: "Sekretariat", selesai: 12, berjalan: 5, tertunda: 2 },
|
||||
{ name: "Keuangan", selesai: 8, berjalan: 7, tertunda: 1 },
|
||||
{ name: "Sosial", selesai: 10, berjalan: 3, tertunda: 4 },
|
||||
{ name: "Humas", selesai: 6, berjalan: 9, tertunda: 3 },
|
||||
];
|
||||
|
||||
// Division task summaries
|
||||
const divisionTasks = [
|
||||
{
|
||||
name: "Sekretariat",
|
||||
tasks: [
|
||||
{ title: "Laporan Bulanan", status: "selesai" },
|
||||
{ title: "Arsip Dokumen", status: "berjalan" },
|
||||
{ title: "Undangan Rapat", status: "tertunda" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Keuangan",
|
||||
tasks: [
|
||||
{ title: "Laporan APBDes", status: "selesai" },
|
||||
{ title: "Verifikasi Dana", status: "tertunda" },
|
||||
{ title: "Pengeluaran Harian", status: "berjalan" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sosial",
|
||||
tasks: [
|
||||
{ title: "Program Bantuan", status: "selesai" },
|
||||
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
||||
{ title: "Monitoring Stunting", status: "tertunda" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Humas",
|
||||
tasks: [
|
||||
{ title: "Publikasi Kegiatan", status: "selesai" },
|
||||
{ title: "Koordinasi Media", status: "berjalan" },
|
||||
{ title: "Laporan Kegiatan", status: "tertunda" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Archive items
|
||||
const archiveItems = [
|
||||
{ name: "Surat Keputusan", count: 12 },
|
||||
{ name: "Laporan Keuangan", count: 8 },
|
||||
{ name: "Dokumentasi", count: 24 },
|
||||
{ name: "Notulensi Rapat", count: 15 },
|
||||
];
|
||||
|
||||
// Activity progress
|
||||
const activityProgress = [
|
||||
{
|
||||
name: "Pembangunan Jalan",
|
||||
progress: 75,
|
||||
date: "15 Feb 2026",
|
||||
status: "berjalan",
|
||||
},
|
||||
{
|
||||
name: "Posyandu Bulanan",
|
||||
progress: 100,
|
||||
date: "10 Feb 2026",
|
||||
status: "selesai",
|
||||
},
|
||||
{
|
||||
name: "Vaksinasi Massal",
|
||||
progress: 45,
|
||||
date: "20 Feb 2026",
|
||||
status: "berjalan",
|
||||
},
|
||||
{
|
||||
name: "Festival Budaya",
|
||||
progress: 20,
|
||||
date: "5 Mar 2026",
|
||||
status: "berjalan",
|
||||
},
|
||||
];
|
||||
|
||||
// Document statistics
|
||||
const documentStats = [
|
||||
{ name: "Gambar", value: 42 },
|
||||
{ name: "Dokumen", value: 87 },
|
||||
];
|
||||
|
||||
// Activity progress statistics
|
||||
const activityProgressStats = [
|
||||
{ name: "Selesai", value: 12 },
|
||||
{ name: "Dikerjakan", value: 8 },
|
||||
{ name: "Segera Dikerjakan", value: 5 },
|
||||
{ name: "Dibatalkan", value: 2 },
|
||||
];
|
||||
|
||||
const COLORS = ["#10B981", "#F59E0B", "#EF4444", "#6B7280"];
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
selesai: "green",
|
||||
berjalan: "blue",
|
||||
tertunda: "red",
|
||||
proses: "yellow",
|
||||
};
|
||||
|
||||
// Discussion data
|
||||
const discussions = [
|
||||
{
|
||||
title: "Pembahasan APBDes 2026",
|
||||
sender: "Kepala Desa",
|
||||
timestamp: "2 jam yang lalu",
|
||||
},
|
||||
{
|
||||
title: "Kegiatan Posyandu",
|
||||
sender: "Divisi Sosial",
|
||||
timestamp: "5 jam yang lalu",
|
||||
},
|
||||
{
|
||||
title: "Festival Budaya",
|
||||
sender: "Divisi Humas",
|
||||
timestamp: "1 hari yang lalu",
|
||||
},
|
||||
];
|
||||
|
||||
// Today's agenda
|
||||
const todayAgenda = [
|
||||
{ time: "09:00", event: "Rapat Evaluasi Bulanan" },
|
||||
{ time: "14:00", event: "Koordinasi Program Bantuan" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Grafik Progres Tugas per Divisi */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Grafik Progres Tugas per Divisi
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={divisionProgressData}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "#141D34" : "white"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark
|
||||
? "var(--mantine-color-text)"
|
||||
: "var(--mantine-color-text)",
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark
|
||||
? "var(--mantine-color-text)"
|
||||
: "var(--mantine-color-text)",
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={
|
||||
dark
|
||||
? {
|
||||
backgroundColor: "var(--mantine-color-dark-7)",
|
||||
borderColor: "var(--mantine-color-dark-6)",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="selesai"
|
||||
stackId="a"
|
||||
fill="#10B981"
|
||||
name="Selesai"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="berjalan"
|
||||
stackId="a"
|
||||
fill="#3B82F6"
|
||||
name="Berjalan"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="tertunda"
|
||||
stackId="a"
|
||||
fill="#EF4444"
|
||||
name="Tertunda"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
{/* Ringkasan Tugas per Divisi */}
|
||||
{/* SECTION 1 — PROGRAM KEGIATAN */}
|
||||
<Grid gutter="md">
|
||||
{divisionTasks.map((division, index) => (
|
||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} mb="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||
{division.name}
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{division.tasks.map((task, taskIndex) => (
|
||||
<Box key={taskIndex}>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||
{task.title}
|
||||
</Text>
|
||||
<MantineBadge
|
||||
color={STATUS_COLORS[task.status] || "gray"}
|
||||
variant="light"
|
||||
>
|
||||
{task.status}
|
||||
</MantineBadge>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
{programKegiatanData.map((kegiatan, index) => (
|
||||
<Grid.Col key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<ActivityCard
|
||||
title={kegiatan.title}
|
||||
date={kegiatan.date}
|
||||
progress={kegiatan.progress}
|
||||
status={kegiatan.status}
|
||||
/>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Arsip Digital Perangkat Desa */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Arsip Digital Perangkat Desa
|
||||
</Title>
|
||||
<Grid gutter="md">
|
||||
{archiveItems.map((item, index) => (
|
||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text c={dark ? "white" : "darmasaba-navy"} fw={700}>
|
||||
{item.count}
|
||||
</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
{/* SECTION 2 — GRID DASHBOARD (3 Columns) */}
|
||||
<Grid gutter="lg">
|
||||
{/* Left Column - Division List */}
|
||||
<Grid.Col span={{ base: 12, lg: 3 }}>
|
||||
<DivisionList />
|
||||
</Grid.Col>
|
||||
|
||||
{/* Kartu Progres Kegiatan */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Progres Kegiatan / Program
|
||||
</Title>
|
||||
<Stack gap="md">
|
||||
{activityProgress.map((activity, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||
>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||
{activity.name}
|
||||
</Text>
|
||||
<MantineBadge
|
||||
color={STATUS_COLORS[activity.status] || "gray"}
|
||||
variant="light"
|
||||
>
|
||||
{activity.status}
|
||||
</MantineBadge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<MantineProgress
|
||||
value={activity.progress}
|
||||
size="sm"
|
||||
radius="xl"
|
||||
color={activity.progress === 100 ? "green" : "blue"}
|
||||
w="calc(100% - 80px)"
|
||||
/>
|
||||
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||
{activity.progress}%
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed" mt="sm">
|
||||
{activity.date}
|
||||
</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
{/* Middle Column - Document Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 5 }}>
|
||||
<DocumentChart />
|
||||
</Grid.Col>
|
||||
|
||||
{/* Statistik Dokumen & Progres Kegiatan */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Jumlah Dokumen
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<BarChart data={documentStats}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "#141D34" : "white"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark
|
||||
? "var(--mantine-color-text)"
|
||||
: "var(--mantine-color-text)",
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark
|
||||
? "var(--mantine-color-text)"
|
||||
: "var(--mantine-color-text)",
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={
|
||||
dark
|
||||
? {
|
||||
backgroundColor: "var(--mantine-color-dark-7)",
|
||||
borderColor: "var(--mantine-color-dark-6)",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill={
|
||||
dark
|
||||
? "var(--mantine-color-blue-6)"
|
||||
: "var(--mantine-color-blue-filled)"
|
||||
}
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Progres Kegiatan
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={activityProgressStats}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) =>
|
||||
`${name}: ${percent ? (percent * 100).toFixed(0) : "0"}%`
|
||||
}
|
||||
>
|
||||
{activityProgressStats.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
contentStyle={
|
||||
dark
|
||||
? {
|
||||
backgroundColor: "var(--mantine-color-dark-7)",
|
||||
borderColor: "var(--mantine-color-dark-6)",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
{/* Right Column - Progress Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<ProgressChart />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
{/* Diskusi Internal */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Diskusi Internal
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{discussions.map((discussion, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||
{discussion.title}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{discussion.timestamp}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
{discussion.sender}
|
||||
</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
{/* SECTION 3 — DISCUSSION PANEL */}
|
||||
<DiscussionPanel />
|
||||
|
||||
{/* Agenda / Acara Hari Ini */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||
Agenda / Acara Hari Ini
|
||||
</Title>
|
||||
{todayAgenda.length > 0 ? (
|
||||
<Stack gap="sm">
|
||||
{todayAgenda.map((agenda, index) => (
|
||||
<Group key={index} align="flex-start">
|
||||
<Box w={60}>
|
||||
<Text c="dimmed">{agenda.time}</Text>
|
||||
</Box>
|
||||
<Divider orientation="vertical" mx="sm" />
|
||||
<Text c={dark ? "white" : "darmasaba-navy"}>
|
||||
{agenda.event}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Text c="dimmed" ta="center" py="md">
|
||||
Tidak ada acara hari ini
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
{/* SECTION 4 — ACARA HARI INI */}
|
||||
<EventCard />
|
||||
|
||||
{/* SECTION 5 — ARSIP DIGITAL PERANGKAT DESA */}
|
||||
<Grid gutter="md">
|
||||
{archiveData.map((item, index) => (
|
||||
<Grid.Col key={index} span={{ base: 12, md: 6 }}>
|
||||
<ArchiveCard item={item} />
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default KinerjaDivisi;
|
||||
export default KinerjaDivisi;
|
||||
|
||||
95
src/components/kinerja-divisi/activity-card.tsx
Normal file
95
src/components/kinerja-divisi/activity-card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
41
src/components/kinerja-divisi/archive-card.tsx
Normal file
41
src/components/kinerja-divisi/archive-card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
92
src/components/kinerja-divisi/discussion-panel.tsx
Normal file
92
src/components/kinerja-divisi/discussion-panel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
77
src/components/kinerja-divisi/division-list.tsx
Normal file
77
src/components/kinerja-divisi/division-list.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
69
src/components/kinerja-divisi/document-chart.tsx
Normal file
69
src/components/kinerja-divisi/document-chart.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
65
src/components/kinerja-divisi/event-card.tsx
Normal file
65
src/components/kinerja-divisi/event-card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/components/kinerja-divisi/index.ts
Normal file
7
src/components/kinerja-divisi/index.ts
Normal 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";
|
||||
84
src/components/kinerja-divisi/progress-chart.tsx
Normal file
84
src/components/kinerja-divisi/progress-chart.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user