Fix New UI Kinerja Divisi
This commit is contained in:
139
Kinerja-Divisi-New.md
Normal file
139
Kinerja-Divisi-New.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
Create a modern admin dashboard UI for a village management system using React 19 + Vite + TailwindCSS + Mantine components + Recharts.
|
||||||
|
|
||||||
|
Design style:
|
||||||
|
- Clean, soft UI with rounded corners (2xl)
|
||||||
|
- Light gray background (#f5f6f8)
|
||||||
|
- Card-based layout with subtle shadow
|
||||||
|
- Smooth spacing and consistent padding
|
||||||
|
- Professional government-style but still modern
|
||||||
|
- Use Inter or system font
|
||||||
|
- Primary color: dark blue
|
||||||
|
- Accent color: orange for progress
|
||||||
|
- Success color: green
|
||||||
|
- Use Mantine components where possible
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
- Responsive grid layout (desktop-first)
|
||||||
|
- 4 summary cards on top (horizontal)
|
||||||
|
- 2 columns main content below
|
||||||
|
- Left sidebar for division list
|
||||||
|
- Right content for charts and activity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 TOP CARDS (4 ITEMS)
|
||||||
|
Each card contains:
|
||||||
|
- Title (e.g: "Rakor 2025")
|
||||||
|
- Progress bar (orange)
|
||||||
|
- Date (small text)
|
||||||
|
- Status badge "Selesai" (green)
|
||||||
|
|
||||||
|
Use:
|
||||||
|
- Mantine Card
|
||||||
|
- Mantine Progress
|
||||||
|
- Mantine Badge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 LEFT PANEL - "Divisi teraktif"
|
||||||
|
Vertical list of divisions:
|
||||||
|
- Each item is clickable
|
||||||
|
- Show division name + number of activities
|
||||||
|
- Rounded container with hover effect
|
||||||
|
- Chevron icon on right
|
||||||
|
|
||||||
|
Example items:
|
||||||
|
- Kesejahteraan (37 kegiatan)
|
||||||
|
- Pemerintahan (26 kegiatan)
|
||||||
|
- Keuangan (17 kegiatan)
|
||||||
|
- etc
|
||||||
|
|
||||||
|
Use:
|
||||||
|
- Scrollable container
|
||||||
|
- Soft border + hover highlight
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 CENTER - BAR CHART (Jumlah Dokumen)
|
||||||
|
- Use Recharts
|
||||||
|
- Two bars:
|
||||||
|
- Gambar
|
||||||
|
- Dokumen
|
||||||
|
- Color:
|
||||||
|
- Yellow/orange
|
||||||
|
- Green
|
||||||
|
- Show Y axis scale (0–400)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 RIGHT - PIE CHART (Progres Kegiatan)
|
||||||
|
- Use Recharts PieChart
|
||||||
|
- Segments:
|
||||||
|
- Selesai (green ~83%)
|
||||||
|
- Dikerjakan (orange ~16%)
|
||||||
|
- Segera dikerjakan (blue)
|
||||||
|
- Dibatalkan (red)
|
||||||
|
- Include legend below
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 BOTTOM LEFT - Diskusi Panel
|
||||||
|
- List of discussion messages
|
||||||
|
- Each item:
|
||||||
|
- Title
|
||||||
|
- Sender name
|
||||||
|
- Date
|
||||||
|
- Styled like notification cards
|
||||||
|
- Compact and clean
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔹 BOTTOM RIGHT - "Acara Hari Ini"
|
||||||
|
- Empty state
|
||||||
|
- Show text: "Tidak ada acara hari ini"
|
||||||
|
- Centered, muted text
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ TECH REQUIREMENTS
|
||||||
|
|
||||||
|
- Use React functional components
|
||||||
|
- Use TanStack Router (file-based or route config)
|
||||||
|
- Use Mantine for UI components
|
||||||
|
- Use Tailwind for layout and spacing
|
||||||
|
- Use Recharts for charts
|
||||||
|
- State management: Valtio (simple global state)
|
||||||
|
- Date formatting: dayjs
|
||||||
|
- Icons: lucide-react
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 COMPONENT STRUCTURE
|
||||||
|
|
||||||
|
- components/
|
||||||
|
- DashboardCard.tsx
|
||||||
|
- DivisionList.tsx
|
||||||
|
- BarChartCard.tsx
|
||||||
|
- PieChartCard.tsx
|
||||||
|
- DiscussionList.tsx
|
||||||
|
- EmptyState.tsx
|
||||||
|
|
||||||
|
- routes/
|
||||||
|
- dashboard.tsx
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ EXTRA (IMPORTANT FOR VIBE CODING)
|
||||||
|
|
||||||
|
- Add subtle hover animations (scale 1.02)
|
||||||
|
- Smooth transitions (150–200ms)
|
||||||
|
- Keep spacing consistent (gap-4 / gap-6)
|
||||||
|
- Avoid clutter, prioritize readability
|
||||||
|
- Make it feel "calm and productive"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Output:
|
||||||
|
- Full React component code (modular, not monolithic)
|
||||||
|
- Clean, readable, production-ready
|
||||||
|
- No unnecessary comments
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Grid, Stack } from "@mantine/core";
|
import { Grid, Stack } from "@mantine/core";
|
||||||
import { ActivityCard, } from "./kinerja-divisi/activity-card";
|
import { ActivityCard } from "./kinerja-divisi/activity-card";
|
||||||
import { DivisionList } from "./kinerja-divisi/division-list";
|
import { DivisionList } from "./kinerja-divisi/division-list";
|
||||||
import { DocumentChart } from "./kinerja-divisi/document-chart";
|
import { DocumentChart } from "./kinerja-divisi/document-chart";
|
||||||
import { ProgressChart } from "./kinerja-divisi/progress-chart";
|
import { ProgressChart } from "./kinerja-divisi/progress-chart";
|
||||||
@@ -14,25 +14,25 @@ const programKegiatanData = [
|
|||||||
title: "Rakor 2025",
|
title: "Rakor 2025",
|
||||||
date: "3 Juli 2025",
|
date: "3 Juli 2025",
|
||||||
progress: 90,
|
progress: 90,
|
||||||
status: "selesai" as const,
|
status: "Selesai" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Pemutakhiran Indeks Desa",
|
title: "Pemutakhiran Indeks Desa",
|
||||||
date: "3 Juli 2025",
|
date: "3 Juli 2025",
|
||||||
progress: 85,
|
progress: 85,
|
||||||
status: "selesai" as const,
|
status: "Selesai" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Mengurus Akta Cerai Warga",
|
title: "Mengurus Akta Cerai Warga",
|
||||||
date: "3 Juli 2025",
|
date: "3 Juli 2025",
|
||||||
progress: 80,
|
progress: 80,
|
||||||
status: "selesai" as const,
|
status: "Selesai" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Pasek 7 Desa Adat",
|
title: "Pasek 7 Desa Adat",
|
||||||
date: "3 Juli 2025",
|
date: "3 Juli 2025",
|
||||||
progress: 92,
|
progress: 92,
|
||||||
status: "selesai" as const,
|
status: "Selesai" as const,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import {
|
import { Card, Text, Progress, Group, Box } from "@mantine/core";
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
Group,
|
|
||||||
Progress,
|
|
||||||
Text,
|
|
||||||
useMantineColorScheme,
|
|
||||||
} from "@mantine/core";
|
|
||||||
|
|
||||||
interface ActivityCardProps {
|
interface ActivityCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
date: string;
|
date: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
status: "selesai" | "berjalan" | "tertunda";
|
status: "Selesai" | "Berjalan" | "Tertunda";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActivityCard({
|
export function ActivityCard({
|
||||||
@@ -20,16 +13,13 @@ export function ActivityCard({
|
|||||||
progress,
|
progress,
|
||||||
status,
|
status,
|
||||||
}: ActivityCardProps) {
|
}: ActivityCardProps) {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const getStatusColor = () => {
|
||||||
const dark = colorScheme === "dark";
|
switch (status) {
|
||||||
|
case "Selesai":
|
||||||
const getStatusColor = (s: string) => {
|
|
||||||
switch (s) {
|
|
||||||
case "selesai":
|
|
||||||
return "#22C55E";
|
return "#22C55E";
|
||||||
case "berjalan":
|
case "Berjalan":
|
||||||
return "#3B82F6";
|
return "#3B82F6";
|
||||||
case "tertunda":
|
case "Tertunda":
|
||||||
return "#EF4444";
|
return "#EF4444";
|
||||||
default:
|
default:
|
||||||
return "#9CA3AF";
|
return "#9CA3AF";
|
||||||
@@ -38,58 +28,62 @@ export function ActivityCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
|
||||||
radius="xl"
|
radius="xl"
|
||||||
withBorder
|
p={0}
|
||||||
bg={dark ? "#1E293B" : "white"}
|
withBorder={false}
|
||||||
style={{
|
style={{
|
||||||
borderColor: dark ? "#334155" : "white",
|
backgroundColor: "#F3F4F6",
|
||||||
boxShadow: dark
|
overflow: "hidden",
|
||||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
|
||||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* 🔵 HEADER */}
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
borderLeft: `4px solid #3B82F6`,
|
backgroundColor: "#1E3A5F",
|
||||||
paddingLeft: 12,
|
padding: "16px",
|
||||||
marginBottom: 12,
|
textAlign: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
|
<Text c="white" fw={700} size="md">
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Group justify="space-between" mb="xs">
|
{/* CONTENT */}
|
||||||
<Text size="xs" c="dimmed">
|
<Box p="md">
|
||||||
|
{/* PROGRESS */}
|
||||||
|
<Progress
|
||||||
|
value={progress}
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
color="orange"
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<Group justify="space-between" mt="md">
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
{date}
|
{date}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: getStatusColor(status),
|
backgroundColor: getStatusColor(),
|
||||||
color: "white",
|
color: "white",
|
||||||
padding: "2px 8px",
|
padding: "4px 12px",
|
||||||
borderRadius: 4,
|
borderRadius: 999,
|
||||||
fontSize: 11,
|
fontSize: 12,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{status.toUpperCase()}
|
{status}
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
</Box>
|
||||||
<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>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ export function ArchiveCard({ item, onClick }: ArchiveCardProps) {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
transition: "transform 0.2s, box-shadow 0.2s",
|
transition: "transform 0.2s, box-shadow 0.2s",
|
||||||
}}
|
}}
|
||||||
|
h="100%"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export function DiscussionPanel() {
|
|||||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||||
}}
|
}}
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<Group gap="xs" mb="md">
|
<Group gap="xs" mb="md">
|
||||||
<MessageCircle size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
<MessageCircle size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Bar,
|
Bar,
|
||||||
BarChart,
|
BarChart,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
|
Cell,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
XAxis,
|
XAxis,
|
||||||
@@ -10,8 +11,8 @@ import {
|
|||||||
} from "recharts";
|
} from "recharts";
|
||||||
|
|
||||||
const documentData = [
|
const documentData = [
|
||||||
{ name: "Gambar", value: 300 },
|
{ name: "Gambar", jumlah: 300, color: "#FACC15" },
|
||||||
{ name: "Dokumen", value: 310 },
|
{ name: "Dokumen", jumlah: 310, color: "#22C55E" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function DocumentChart() {
|
export function DocumentChart() {
|
||||||
@@ -61,7 +62,11 @@ export function DocumentChart() {
|
|||||||
}}
|
}}
|
||||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="value" fill="#3B82F6" radius={[4, 4, 0, 0]} />
|
<Bar dataKey="jumlah" radius={[4, 4, 0, 0]}>
|
||||||
|
{documentData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export function EventCard({ agendas = [] }: EventCardProps) {
|
|||||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||||
}}
|
}}
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<Group gap="xs" mb="md">
|
<Group gap="xs" mb="md">
|
||||||
<Calendar size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
<Calendar size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
|
|||||||
|
|
||||||
const progressData = [
|
const progressData = [
|
||||||
{ name: "Selesai", value: 83.33, color: "#22C55E" },
|
{ name: "Selesai", value: 83.33, color: "#22C55E" },
|
||||||
{ name: "Dikerjakan", value: 16.67, color: "#FACC15" },
|
{ name: "Dikerjakan", value: 16.67, color: "#F59E0B" },
|
||||||
{ name: "Segera Dikerjakan", value: 0, color: "#3B82F6" },
|
{ name: "Segera Dikerjakan", value: 0, color: "#3B82F6" },
|
||||||
{ name: "Dibatalkan", value: 0, color: "#EF4444" },
|
{ name: "Dibatalkan", value: 0, color: "#EF4444" },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user