feat: improve header responsiveness and update seed initialization
- Add text truncation for title on mobile screens - Hide user info section on mobile, show simplified icons only - Update seed.ts to create admin and demo users with proper password hashing - Add bcryptjs for password hashing in seed script - Update QWEN.md documentation with seed command and default users Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
12
QWEN.md
12
QWEN.md
@@ -51,11 +51,13 @@ bun install
|
|||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Fill in your DATABASE_URL and BETTER_AUTH_SECRET
|
# Fill in your DATABASE_URL and BETTER_AUTH_SECRET
|
||||||
|
# Optional: Set ADMIN_EMAIL and ADMIN_PASSWORD for admin user
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database Initialization
|
### Database Initialization
|
||||||
```bash
|
```bash
|
||||||
bun x prisma migrate dev
|
bun x prisma migrate dev
|
||||||
|
bun run seed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start Development
|
### Start Development
|
||||||
@@ -109,4 +111,12 @@ bun run dev
|
|||||||
- `test:e2e`: Runs end-to-end tests
|
- `test:e2e`: Runs end-to-end tests
|
||||||
- `build`: Builds the application for production
|
- `build`: Builds the application for production
|
||||||
- `start`: Starts the production server
|
- `start`: Starts the production server
|
||||||
- `seed`: Seeds the database with initial data
|
- `seed`: Seeds the database with admin and demo users
|
||||||
|
|
||||||
|
## Default Users (after running `bun run seed`)
|
||||||
|
|
||||||
|
- **Admin**: `ADMIN_EMAIL` (from env) / `ADMIN_PASSWORD` (default: `admin123`)
|
||||||
|
- **Demo Users**:
|
||||||
|
- `demo1@example.com` / `demo123` (role: user)
|
||||||
|
- `demo2@example.com` / `demo123` (role: user)
|
||||||
|
- `moderator@example.com` / `demo123` (role: moderator)
|
||||||
6
bun.lock
6
bun.lock
@@ -47,6 +47,7 @@
|
|||||||
"@tabler/icons-react": "^3.36.1",
|
"@tabler/icons-react": "^3.36.1",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tanstack/react-router": "^1.158.1",
|
"@tanstack/react-router": "^1.158.1",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.4.18",
|
"better-auth": "^1.4.18",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cmdk": "^1.0.1",
|
"cmdk": "^1.0.1",
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"@tanstack/react-router-devtools": "^1.158.1",
|
"@tanstack/react-router-devtools": "^1.158.1",
|
||||||
"@tanstack/router-cli": "1.158.1",
|
"@tanstack/router-cli": "1.158.1",
|
||||||
"@tanstack/router-vite-plugin": "^1.158.1",
|
"@tanstack/router-vite-plugin": "^1.158.1",
|
||||||
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -628,6 +630,8 @@
|
|||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||||
|
|
||||||
|
"@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||||
@@ -776,6 +780,8 @@
|
|||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||||
|
|
||||||
|
"bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="],
|
||||||
|
|
||||||
"better-auth": ["better-auth@1.4.18", "", { "dependencies": { "@better-auth/core": "1.4.18", "@better-auth/telemetry": "1.4.18", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.3.5" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg=="],
|
"better-auth": ["better-auth@1.4.18", "", { "dependencies": { "@better-auth/core": "1.4.18", "@better-auth/telemetry": "1.4.18", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.3.5" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg=="],
|
||||||
|
|
||||||
"better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
|
"better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="],
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"@tabler/icons-react": "^3.36.1",
|
"@tabler/icons-react": "^3.36.1",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tanstack/react-router": "^1.158.1",
|
"@tanstack/react-router": "^1.158.1",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.4.18",
|
"better-auth": "^1.4.18",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"cmdk": "^1.0.1",
|
"cmdk": "^1.0.1",
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
"@tanstack/react-router-devtools": "^1.158.1",
|
"@tanstack/react-router-devtools": "^1.158.1",
|
||||||
"@tanstack/router-cli": "1.158.1",
|
"@tanstack/router-cli": "1.158.1",
|
||||||
"@tanstack/router-vite-plugin": "^1.158.1",
|
"@tanstack/router-vite-plugin": "^1.158.1",
|
||||||
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
import { hash } from "bcryptjs";
|
||||||
|
import { generateId } from "better-auth";
|
||||||
import { prisma } from "@/utils/db";
|
import { prisma } from "@/utils/db";
|
||||||
|
|
||||||
async function seedAdminUser() {
|
async function seedAdminUser() {
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
const adminEmail = process.env.ADMIN_EMAIL;
|
const adminEmail = process.env.ADMIN_EMAIL;
|
||||||
|
const adminPassword = process.env.ADMIN_PASSWORD || "admin123";
|
||||||
|
|
||||||
if (!adminEmail) {
|
if (!adminEmail) {
|
||||||
console.log(
|
console.log(
|
||||||
"No ADMIN_EMAIL environment variable found. Skipping admin role assignment.",
|
"No ADMIN_EMAIL environment variable found. Skipping admin user creation.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -30,9 +33,35 @@ async function seedAdminUser() {
|
|||||||
console.log(`User with email ${adminEmail} already has admin role.`);
|
console.log(`User with email ${adminEmail} already has admin role.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
// Create new admin user
|
||||||
`No user found with email ${adminEmail}. Skipping admin role assignment.`,
|
const hashedPassword = await hash(adminPassword, 12);
|
||||||
);
|
const userId = generateId();
|
||||||
|
|
||||||
|
await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
id: userId,
|
||||||
|
email: adminEmail,
|
||||||
|
name: "Admin User",
|
||||||
|
role: "admin",
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.account.create({
|
||||||
|
data: {
|
||||||
|
id: generateId(),
|
||||||
|
userId,
|
||||||
|
accountId: userId,
|
||||||
|
providerId: "credential",
|
||||||
|
password: hashedPassword,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Admin user created with email: ${adminEmail}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error seeding admin user:", error);
|
console.error("Error seeding admin user:", error);
|
||||||
@@ -40,10 +69,66 @@ async function seedAdminUser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function seedDemoUsers() {
|
||||||
|
const demoUsers = [
|
||||||
|
{ email: "demo1@example.com", name: "Demo User 1", role: "user" },
|
||||||
|
{ email: "demo2@example.com", name: "Demo User 2", role: "user" },
|
||||||
|
{
|
||||||
|
email: "moderator@example.com",
|
||||||
|
name: "Moderator User",
|
||||||
|
role: "moderator",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const userData of demoUsers) {
|
||||||
|
try {
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { email: userData.email },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
const userId = generateId();
|
||||||
|
const hashedPassword = await hash("demo123", 12);
|
||||||
|
|
||||||
|
await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
id: userId,
|
||||||
|
email: userData.email,
|
||||||
|
name: userData.name,
|
||||||
|
role: userData.role,
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.account.create({
|
||||||
|
data: {
|
||||||
|
id: generateId(),
|
||||||
|
userId,
|
||||||
|
accountId: userId,
|
||||||
|
providerId: "credential",
|
||||||
|
password: hashedPassword,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Demo user created: ${userData.email}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Demo user already exists: ${userData.email}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error seeding user ${userData.email}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log("Seeding database...");
|
console.log("Seeding database...");
|
||||||
|
|
||||||
await seedAdminUser();
|
await seedAdminUser();
|
||||||
|
await seedDemoUsers();
|
||||||
|
|
||||||
console.log("Database seeding completed.");
|
console.log("Database seeding completed.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,296 +1,389 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Badge,
|
||||||
Grid,
|
Button,
|
||||||
GridCol,
|
Card,
|
||||||
Group,
|
Grid,
|
||||||
Text,
|
GridCol,
|
||||||
Title,
|
Group,
|
||||||
Button,
|
Select,
|
||||||
Badge,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Stack,
|
Text,
|
||||||
Select,
|
Title,
|
||||||
useMantineColorScheme
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconBuildingStore, IconCategory, IconCurrency, IconUsers } from "@tabler/icons-react";
|
import {
|
||||||
|
IconBuildingStore,
|
||||||
|
IconCategory,
|
||||||
|
IconCurrency,
|
||||||
|
IconUsers,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const BumdesPage = () => {
|
const BumdesPage = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
||||||
|
|
||||||
// Sample data for KPI cards
|
// Sample data for KPI cards
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
{
|
{
|
||||||
title: "UMKM Aktif",
|
title: "UMKM Aktif",
|
||||||
value: 45,
|
value: 45,
|
||||||
icon: <IconUsers size={24} />,
|
icon: <IconUsers size={24} />,
|
||||||
color: "darmasaba-blue",
|
color: "darmasaba-blue",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "UMKM Terdaftar",
|
title: "UMKM Terdaftar",
|
||||||
value: 68,
|
value: 68,
|
||||||
icon: <IconBuildingStore size={24} />,
|
icon: <IconBuildingStore size={24} />,
|
||||||
color: "darmasaba-success",
|
color: "darmasaba-success",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Omzet",
|
title: "Omzet",
|
||||||
value: "Rp 48.000.000",
|
value: "Rp 48.000.000",
|
||||||
icon: <IconCurrency size={24} />,
|
icon: <IconCurrency size={24} />,
|
||||||
color: "darmasaba-warning",
|
color: "darmasaba-warning",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Kategori UMKM",
|
title: "Kategori UMKM",
|
||||||
value: 34,
|
value: 34,
|
||||||
icon: <IconCategory size={24} />,
|
icon: <IconCategory size={24} />,
|
||||||
color: "darmasaba-danger",
|
color: "darmasaba-danger",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sample data for top products
|
// Sample data for top products
|
||||||
const topProducts = [
|
const topProducts = [
|
||||||
{
|
{
|
||||||
rank: 1,
|
rank: 1,
|
||||||
name: "Beras Premium Organik",
|
name: "Beras Premium Organik",
|
||||||
umkmOwner: "Warung Pak Joko",
|
umkmOwner: "Warung Pak Joko",
|
||||||
growth: "+12%",
|
growth: "+12%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rank: 2,
|
rank: 2,
|
||||||
name: "Keripik Singkong",
|
name: "Keripik Singkong",
|
||||||
umkmOwner: "Ibu Sari Snack",
|
umkmOwner: "Ibu Sari Snack",
|
||||||
growth: "+8%",
|
growth: "+8%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rank: 3,
|
rank: 3,
|
||||||
name: "Madu Alami",
|
name: "Madu Alami",
|
||||||
umkmOwner: "Peternakan Lebah",
|
umkmOwner: "Peternakan Lebah",
|
||||||
growth: "+5%",
|
growth: "+5%",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sample data for product sales
|
// Sample data for product sales
|
||||||
const productSales = [
|
const productSales = [
|
||||||
{
|
{
|
||||||
produk: "Beras Premium Organik",
|
produk: "Beras Premium Organik",
|
||||||
penjualanBulanIni: "Rp 8.500.000",
|
penjualanBulanIni: "Rp 8.500.000",
|
||||||
bulanLalu: "Rp 8.500.000",
|
bulanLalu: "Rp 8.500.000",
|
||||||
trend: 10,
|
trend: 10,
|
||||||
volume: "650 Kg",
|
volume: "650 Kg",
|
||||||
stok: "850 Kg",
|
stok: "850 Kg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Keripik Singkong",
|
produk: "Keripik Singkong",
|
||||||
penjualanBulanIni: "Rp 4.200.000",
|
penjualanBulanIni: "Rp 4.200.000",
|
||||||
bulanLalu: "Rp 3.800.000",
|
bulanLalu: "Rp 3.800.000",
|
||||||
trend: 10,
|
trend: 10,
|
||||||
volume: "320 Kg",
|
volume: "320 Kg",
|
||||||
stok: "120 Kg",
|
stok: "120 Kg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Madu Alami",
|
produk: "Madu Alami",
|
||||||
penjualanBulanIni: "Rp 3.750.000",
|
penjualanBulanIni: "Rp 3.750.000",
|
||||||
bulanLalu: "Rp 4.100.000",
|
bulanLalu: "Rp 4.100.000",
|
||||||
trend: -8,
|
trend: -8,
|
||||||
volume: "150 Liter",
|
volume: "150 Liter",
|
||||||
stok: "45 Liter",
|
stok: "45 Liter",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Kecap Tradisional",
|
produk: "Kecap Tradisional",
|
||||||
penjualanBulanIni: "Rp 2.800.000",
|
penjualanBulanIni: "Rp 2.800.000",
|
||||||
bulanLalu: "Rp 2.500.000",
|
bulanLalu: "Rp 2.500.000",
|
||||||
trend: 12,
|
trend: 12,
|
||||||
volume: "280 Botol",
|
volume: "280 Botol",
|
||||||
stok: "95 Botol",
|
stok: "95 Botol",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* KPI Cards */}
|
{/* KPI Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{kpiData.map((kpi, index) => (
|
{kpiData.map((kpi, index) => (
|
||||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
|
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
{kpi.title}
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{typeof kpi.value === 'number' ? kpi.value.toLocaleString() : kpi.value}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
</Stack>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<Badge
|
{kpi.title}
|
||||||
variant="light"
|
</Text>
|
||||||
color={kpi.color}
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
p={8}
|
{typeof kpi.value === "number"
|
||||||
radius="md"
|
? kpi.value.toLocaleString()
|
||||||
>
|
: kpi.value}
|
||||||
{kpi.icon}
|
</Text>
|
||||||
</Badge>
|
</Stack>
|
||||||
</Group>
|
<Badge variant="light" color={kpi.color} p={8} radius="md">
|
||||||
</Card>
|
{kpi.icon}
|
||||||
</GridCol>
|
</Badge>
|
||||||
))}
|
</Group>
|
||||||
</Grid>
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* Update Penjualan Produk Header */}
|
{/* Update Penjualan Produk Header */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center" px="md" py="xs">
|
p="md"
|
||||||
<Title order={3} c={dark ? "dark.0" : "black"}>
|
radius="md"
|
||||||
Update Penjualan Produk
|
withBorder
|
||||||
</Title>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Group>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Button
|
>
|
||||||
variant={timeFilter === "minggu" ? "filled" : "light"}
|
<Group justify="space-between" align="center" px="md" py="xs">
|
||||||
onClick={() => setTimeFilter("minggu")}
|
<Title order={3} c={dark ? "dark.0" : "black"}>
|
||||||
color="darmasaba-blue"
|
Update Penjualan Produk
|
||||||
>
|
</Title>
|
||||||
Minggu ini
|
<Group>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
variant={timeFilter === "minggu" ? "filled" : "light"}
|
||||||
variant={timeFilter === "bulan" ? "filled" : "light"}
|
onClick={() => setTimeFilter("minggu")}
|
||||||
onClick={() => setTimeFilter("bulan")}
|
color="darmasaba-blue"
|
||||||
color="darmasaba-blue"
|
>
|
||||||
>
|
Minggu ini
|
||||||
Bulan ini
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
</Group>
|
variant={timeFilter === "bulan" ? "filled" : "light"}
|
||||||
</Group>
|
onClick={() => setTimeFilter("bulan")}
|
||||||
</Card>
|
color="darmasaba-blue"
|
||||||
|
>
|
||||||
|
Bulan ini
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Produk Unggulan (Left Column) */}
|
{/* Produk Unggulan (Left Column) */}
|
||||||
<GridCol span={{ base: 12, lg: 4 }}>
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{/* Total Penjualan, Produk Aktif, Total Transaksi */}
|
{/* Total Penjualan, Produk Aktif, Total Transaksi */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Stack gap="md">
|
p="md"
|
||||||
<Group justify="space-between">
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Penjualan</Text>
|
withBorder
|
||||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>Rp 28.500.000</Text>
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Group>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Group justify="space-between">
|
>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Produk Aktif</Text>
|
<Stack gap="md">
|
||||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>124 Produk</Text>
|
<Group justify="space-between">
|
||||||
</Group>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<Group justify="space-between">
|
Total Penjualan
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Transaksi</Text>
|
</Text>
|
||||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>1.240 Transaksi</Text>
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
</Group>
|
Rp 28.500.000
|
||||||
</Stack>
|
</Text>
|
||||||
</Card>
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Produk Aktif
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
124 Produk
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Total Transaksi
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
1.240 Transaksi
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Top 3 Produk Terlaris */}
|
{/* Top 3 Produk Terlaris */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>Top 3 Produk Terlaris</Title>
|
p="md"
|
||||||
<Stack gap="sm">
|
radius="md"
|
||||||
{topProducts.map((product) => (
|
withBorder
|
||||||
<Group key={product.rank} justify="space-between" align="center">
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Group gap="sm">
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Badge
|
>
|
||||||
variant="filled"
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
color={product.rank === 1 ? "gold" : product.rank === 2 ? "gray" : "bronze"}
|
Top 3 Produk Terlaris
|
||||||
radius="xl"
|
</Title>
|
||||||
size="lg"
|
<Stack gap="sm">
|
||||||
>
|
{topProducts.map((product) => (
|
||||||
{product.rank}
|
<Group
|
||||||
</Badge>
|
key={product.rank}
|
||||||
<Stack gap={0}>
|
justify="space-between"
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.name}</Text>
|
align="center"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{product.umkmOwner}</Text>
|
>
|
||||||
</Stack>
|
<Group gap="sm">
|
||||||
</Group>
|
<Badge
|
||||||
<Badge
|
variant="filled"
|
||||||
variant="light"
|
color={
|
||||||
color={product.growth.startsWith('+') ? "green" : "red"}
|
product.rank === 1
|
||||||
>
|
? "gold"
|
||||||
{product.growth}
|
: product.rank === 2
|
||||||
</Badge>
|
? "gray"
|
||||||
</Group>
|
: "bronze"
|
||||||
))}
|
}
|
||||||
</Stack>
|
radius="xl"
|
||||||
</Card>
|
size="lg"
|
||||||
</Stack>
|
>
|
||||||
</GridCol>
|
{product.rank}
|
||||||
|
</Badge>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.name}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{product.umkmOwner}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={product.growth.startsWith("+") ? "green" : "red"}
|
||||||
|
>
|
||||||
|
{product.growth}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Detail Penjualan Produk (Right Column) */}
|
{/* Detail Penjualan Produk (Right Column) */}
|
||||||
<GridCol span={{ base: 12, lg: 8 }}>
|
<GridCol span={{ base: 12, lg: 8 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" mb="md">
|
p="md"
|
||||||
<Title order={4} c={dark ? "dark.0" : "black"}>Detail Penjualan Produk</Title>
|
radius="md"
|
||||||
<Select
|
withBorder
|
||||||
placeholder="Filter kategori"
|
bg={dark ? "#141D34" : "white"}
|
||||||
data={[
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{ value: 'semua', label: 'Semua Kategori' },
|
>
|
||||||
{ value: 'makanan', label: 'Makanan' },
|
<Group justify="space-between" mb="md">
|
||||||
{ value: 'minuman', label: 'Minuman' },
|
<Title order={4} c={dark ? "dark.0" : "black"}>
|
||||||
{ value: 'kerajinan', label: 'Kerajinan' },
|
Detail Penjualan Produk
|
||||||
]}
|
</Title>
|
||||||
defaultValue="semua"
|
<Select
|
||||||
w={200}
|
placeholder="Filter kategori"
|
||||||
/>
|
data={[
|
||||||
</Group>
|
{ value: "semua", label: "Semua Kategori" },
|
||||||
|
{ value: "makanan", label: "Makanan" },
|
||||||
|
{ value: "minuman", label: "Minuman" },
|
||||||
|
{ value: "kerajinan", label: "Kerajinan" },
|
||||||
|
]}
|
||||||
|
defaultValue="semua"
|
||||||
|
w={200}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Table striped highlightOnHover withColumnBorders>
|
<Table striped highlightOnHover withColumnBorders>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Produk</Text></Table.Th>
|
<Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Penjualan Bulan Ini</Text></Table.Th>
|
<Text c={dark ? "white" : "dimmed"}>Produk</Text>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Bulan Lalu</Text></Table.Th>
|
</Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Trend</Text></Table.Th>
|
<Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Volume</Text></Table.Th>
|
<Text c={dark ? "white" : "dimmed"}>
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Stok</Text></Table.Th>
|
Penjualan Bulan Ini
|
||||||
<Table.Th><Text c={dark ? "white" : "dimmed"}>Aksi</Text></Table.Th>
|
</Text>
|
||||||
</Table.Tr>
|
</Table.Th>
|
||||||
</Table.Thead>
|
<Table.Th>
|
||||||
<Table.Tbody>
|
<Text c={dark ? "white" : "dimmed"}>Bulan Lalu</Text>
|
||||||
{productSales.map((product, index) => (
|
</Table.Th>
|
||||||
<Table.Tr key={index}>
|
<Table.Th>
|
||||||
<Table.Td>
|
<Text c={dark ? "white" : "dimmed"}>Trend</Text>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.produk}</Text>
|
</Table.Th>
|
||||||
</Table.Td>
|
<Table.Th>
|
||||||
<Table.Td>
|
<Text c={dark ? "white" : "dimmed"}>Volume</Text>
|
||||||
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.penjualanBulanIni}</Text>
|
</Table.Th>
|
||||||
</Table.Td>
|
<Table.Th>
|
||||||
<Table.Td>
|
<Text c={dark ? "white" : "dimmed"}>Stok</Text>
|
||||||
<Text fz={"sm"} c={dark ? "white" : "dimmed"}>{product.bulanLalu}</Text>
|
</Table.Th>
|
||||||
</Table.Td>
|
<Table.Th>
|
||||||
<Table.Td>
|
<Text c={dark ? "white" : "dimmed"}>Aksi</Text>
|
||||||
<Group gap="xs">
|
</Table.Th>
|
||||||
<Text c={product.trend >= 0 ? "green" : "red"}>
|
</Table.Tr>
|
||||||
{product.trend >= 0 ? '↑' : '↓'} {Math.abs(product.trend)}%
|
</Table.Thead>
|
||||||
</Text>
|
<Table.Tbody>
|
||||||
</Group>
|
{productSales.map((product, index) => (
|
||||||
</Table.Td>
|
<Table.Tr key={index}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.volume}</Text>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
</Table.Td>
|
{product.produk}
|
||||||
<Table.Td>
|
</Text>
|
||||||
<Badge
|
</Table.Td>
|
||||||
variant="light"
|
<Table.Td>
|
||||||
color={parseInt(product.stok) > 200 ? "green" : "yellow"}
|
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>
|
||||||
>
|
{product.penjualanBulanIni}
|
||||||
{product.stok}
|
</Text>
|
||||||
</Badge>
|
</Table.Td>
|
||||||
</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>
|
<Text fz={"sm"} c={dark ? "white" : "dimmed"}>
|
||||||
<Button variant="subtle" size="compact-sm" color="darmasaba-blue">
|
{product.bulanLalu}
|
||||||
Detail
|
</Text>
|
||||||
</Button>
|
</Table.Td>
|
||||||
</Table.Td>
|
<Table.Td>
|
||||||
</Table.Tr>
|
<Group gap="xs">
|
||||||
))}
|
<Text c={product.trend >= 0 ? "green" : "red"}>
|
||||||
</Table.Tbody>
|
{product.trend >= 0 ? "↑" : "↓"}{" "}
|
||||||
</Table>
|
{Math.abs(product.trend)}%
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Group>
|
||||||
</Grid>
|
</Table.Td>
|
||||||
</Stack>
|
<Table.Td>
|
||||||
);
|
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.volume}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={
|
||||||
|
parseInt(product.stok) > 200 ? "green" : "yellow"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{product.stok}
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
size="compact-sm"
|
||||||
|
color="darmasaba-blue"
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BumdesPage;
|
export default BumdesPage;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from "react";
|
|
||||||
// Import Mantine components directly
|
// Import Mantine components directly
|
||||||
import { Group, Text, ThemeIcon, Badge } from "@mantine/core";
|
import { Badge, Group, Text, ThemeIcon } from "@mantine/core";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
// Import custom Card and its sub-components
|
// Import custom Card and its sub-components
|
||||||
import { Card } from "./ui/card";
|
import { Card } from "./ui/card";
|
||||||
|
|
||||||
|
|||||||
@@ -13,25 +13,25 @@ import {
|
|||||||
Pie,
|
Pie,
|
||||||
PieChart,
|
PieChart,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
|
Tooltip, // Added Tooltip import
|
||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
Tooltip, // Added Tooltip import
|
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
|
|
||||||
// Import Mantine components
|
// Import Mantine components
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Grid,
|
|
||||||
Stack,
|
|
||||||
Group,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Progress,
|
|
||||||
Box,
|
|
||||||
Badge,
|
Badge,
|
||||||
ThemeIcon,
|
Box,
|
||||||
Card, // Added for icon containers
|
Card, // Added for icon containers
|
||||||
|
Grid,
|
||||||
|
Group,
|
||||||
|
Progress,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
useMantineColorScheme, // Add this import
|
useMantineColorScheme, // Add this import
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
|
||||||
@@ -68,13 +68,20 @@ const eventData = [
|
|||||||
|
|
||||||
export function DashboardContent() {
|
export function DashboardContent() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" w="100%">
|
<Group justify="space-between" align="flex-start" w="100%">
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="sm" c="dimmed" mb="xs">
|
<Text size="sm" c="dimmed" mb="xs">
|
||||||
@@ -92,14 +99,26 @@ export function DashboardContent() {
|
|||||||
12% dari minggu lalu ↗ +12%
|
12% dari minggu lalu ↗ +12%
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}>
|
<ThemeIcon
|
||||||
|
variant="filled"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
|
>
|
||||||
<FileText style={{ width: "70%", height: "70%" }} />
|
<FileText style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" w="100%">
|
<Group justify="space-between" align="flex-start" w="100%">
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="sm" c="dimmed" mb="xs">
|
<Text size="sm" c="dimmed" mb="xs">
|
||||||
@@ -114,14 +133,26 @@ export function DashboardContent() {
|
|||||||
14 baru, 14 diproses
|
14 baru, 14 diproses
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}>
|
<ThemeIcon
|
||||||
|
variant="filled"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
|
>
|
||||||
<MessageCircle style={{ width: "70%", height: "70%" }} />
|
<MessageCircle style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" w="100%">
|
<Group justify="space-between" align="flex-start" w="100%">
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="sm" c="dimmed" mb="xs">
|
<Text size="sm" c="dimmed" mb="xs">
|
||||||
@@ -139,14 +170,26 @@ export function DashboardContent() {
|
|||||||
+8%
|
+8%
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}>
|
<ThemeIcon
|
||||||
|
variant="filled"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
|
>
|
||||||
<CheckCircle style={{ width: "70%", height: "70%" }} />
|
<CheckCircle style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" w="100%">
|
<Group justify="space-between" align="flex-start" w="100%">
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="sm" c="dimmed" mb="xs">
|
<Text size="sm" c="dimmed" mb="xs">
|
||||||
@@ -161,7 +204,12 @@ export function DashboardContent() {
|
|||||||
dari 482 responden
|
dari 482 responden
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}>
|
<ThemeIcon
|
||||||
|
variant="filled"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
|
>
|
||||||
<Users style={{ width: "70%", height: "70%" }} />
|
<Users style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -171,7 +219,13 @@ export function DashboardContent() {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Bar Chart */}
|
{/* Bar Chart */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={4} mb={5}>
|
<Title order={4} mb={5}>
|
||||||
@@ -232,7 +286,13 @@ export function DashboardContent() {
|
|||||||
|
|
||||||
{/* Pie Chart */}
|
{/* Pie Chart */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Title order={4} mb={5}>
|
<Title order={4} mb={5}>
|
||||||
Tingkat Kepuasan
|
Tingkat Kepuasan
|
||||||
</Title>
|
</Title>
|
||||||
@@ -259,19 +319,35 @@ export function DashboardContent() {
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<Group justify="center" gap="md" mt="md">
|
<Group justify="center" gap="md" mt="md">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box w={12} h={12} style={{ backgroundColor: COLORS[0], borderRadius: "50%" }} />
|
<Box
|
||||||
|
w={12}
|
||||||
|
h={12}
|
||||||
|
style={{ backgroundColor: COLORS[0], borderRadius: "50%" }}
|
||||||
|
/>
|
||||||
<Text size="sm">Sangat puas (0%)</Text>
|
<Text size="sm">Sangat puas (0%)</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box w={12} h={12} style={{ backgroundColor: COLORS[1], borderRadius: "50%" }} />
|
<Box
|
||||||
|
w={12}
|
||||||
|
h={12}
|
||||||
|
style={{ backgroundColor: COLORS[1], borderRadius: "50%" }}
|
||||||
|
/>
|
||||||
<Text size="sm">Puas (0%)</Text>
|
<Text size="sm">Puas (0%)</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box w={12} h={12} style={{ backgroundColor: COLORS[2], borderRadius: "50%" }} />
|
<Box
|
||||||
|
w={12}
|
||||||
|
h={12}
|
||||||
|
style={{ backgroundColor: COLORS[2], borderRadius: "50%" }}
|
||||||
|
/>
|
||||||
<Text size="sm">Cukup (0%)</Text>
|
<Text size="sm">Cukup (0%)</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box w={12} h={12} style={{ backgroundColor: COLORS[3], borderRadius: "50%" }} />
|
<Box
|
||||||
|
w={12}
|
||||||
|
h={12}
|
||||||
|
style={{ backgroundColor: COLORS[3], borderRadius: "50%" }}
|
||||||
|
/>
|
||||||
<Text size="sm">Kurang (0%)</Text>
|
<Text size="sm">Kurang (0%)</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -283,7 +359,13 @@ export function DashboardContent() {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Divisi Teraktif */}
|
{/* Divisi Teraktif */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group gap="xs" mb="lg">
|
<Group gap="xs" mb="lg">
|
||||||
<Box>
|
<Box>
|
||||||
{/* Original SVG icon */}
|
{/* Original SVG icon */}
|
||||||
@@ -355,7 +437,13 @@ export function DashboardContent() {
|
|||||||
|
|
||||||
{/* Kalender */}
|
{/* Kalender */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Group gap="xs" mb="lg">
|
<Group gap="xs" mb="lg">
|
||||||
<Calendar style={{ width: 20, height: 20 }} />
|
<Calendar style={{ width: 20, height: 20 }} />
|
||||||
<Title order={4}>Kalender & Kegiatan Mendatang</Title>
|
<Title order={4}>Kalender & Kegiatan Mendatang</Title>
|
||||||
@@ -364,7 +452,10 @@ export function DashboardContent() {
|
|||||||
{eventData.map((event, index) => (
|
{eventData.map((event, index) => (
|
||||||
<Box
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
style={{ borderLeft: "4px solid var(--mantine-color-blue-filled)", paddingLeft: 12 }}
|
style={{
|
||||||
|
borderLeft: "4px solid var(--mantine-color-blue-filled)",
|
||||||
|
paddingLeft: 12,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{event.date}
|
{event.date}
|
||||||
@@ -378,7 +469,13 @@ export function DashboardContent() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* APBDes Chart */}
|
{/* APBDes Chart */}
|
||||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
>
|
||||||
<Title order={4} mb="lg">
|
<Title order={4} mb="lg">
|
||||||
Grafik APBDes
|
Grafik APBDes
|
||||||
</Title>
|
</Title>
|
||||||
@@ -387,19 +484,37 @@ export function DashboardContent() {
|
|||||||
<Text size="sm" fw={500} w={60}>
|
<Text size="sm" fw={500} w={60}>
|
||||||
Belanja
|
Belanja
|
||||||
</Text>
|
</Text>
|
||||||
<Progress value={70} size="lg" radius="xl" color="blue" style={{ flex: 1 }} />
|
<Progress
|
||||||
|
value={70}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color="blue"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Group align="center" gap="md">
|
<Group align="center" gap="md">
|
||||||
<Text size="sm" fw={500} w={60}>
|
<Text size="sm" fw={500} w={60}>
|
||||||
Pendapatan
|
Pendapatan
|
||||||
</Text>
|
</Text>
|
||||||
<Progress value={90} size="lg" radius="xl" color="green" style={{ flex: 1 }} />
|
<Progress
|
||||||
|
value={90}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color="green"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Group align="center" gap="md">
|
<Group align="center" gap="md">
|
||||||
<Text size="sm" fw={500} w={60}>
|
<Text size="sm" fw={500} w={60}>
|
||||||
Pembangunan
|
Pembangunan
|
||||||
</Text>
|
</Text>
|
||||||
<Progress value={50} size="lg" radius="xl" color="orange" style={{ flex: 1 }} />
|
<Progress
|
||||||
|
value={50}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color="orange"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import React from "react";
|
import { BarChart, PieChart } from "@mantine/charts";
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Title,
|
Grid,
|
||||||
Text,
|
|
||||||
Group,
|
Group,
|
||||||
Stack,
|
Stack,
|
||||||
Grid,
|
|
||||||
Box,
|
|
||||||
Table,
|
Table,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconBabyCarriage, IconSkull, IconArrowUp, IconArrowDown } from "@tabler/icons-react";
|
import {
|
||||||
import { BarChart, PieChart } from "@mantine/charts";
|
IconArrowDown,
|
||||||
|
IconArrowUp,
|
||||||
|
IconBabyCarriage,
|
||||||
|
IconSkull,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
// Sample Data
|
// Sample Data
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
@@ -71,7 +76,11 @@ const kpiData = [
|
|||||||
value: "23",
|
value: "23",
|
||||||
sub: "Tahun ini",
|
sub: "Tahun ini",
|
||||||
icon: (
|
icon: (
|
||||||
<IconBabyCarriage className="h-6 w-6 text-muted-foreground" role="img" aria-label="Icon kelahiran" />
|
<IconBabyCarriage
|
||||||
|
className="h-6 w-6 text-muted-foreground"
|
||||||
|
role="img"
|
||||||
|
aria-label="Icon kelahiran"
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -136,10 +145,30 @@ const banjarData = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const dynamicStats = [
|
const dynamicStats = [
|
||||||
{ title: "Kelahiran", value: "23", icon: <IconBabyCarriage size={16} />, color: "green" },
|
{
|
||||||
{ title: "Kematian", value: "12", icon: <IconSkull size={16} />, color: "red" },
|
title: "Kelahiran",
|
||||||
{ title: "Pindah Masuk", value: "45", icon: <IconArrowDown size={16} />, color: "blue" },
|
value: "23",
|
||||||
{ title: "Pindah Keluar", value: "32", icon: <IconArrowUp size={16} />, color: "orange" },
|
icon: <IconBabyCarriage size={16} />,
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kematian",
|
||||||
|
value: "12",
|
||||||
|
icon: <IconSkull size={16} />,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pindah Masuk",
|
||||||
|
value: "45",
|
||||||
|
icon: <IconArrowDown size={16} />,
|
||||||
|
color: "blue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pindah Keluar",
|
||||||
|
value: "32",
|
||||||
|
icon: <IconArrowUp size={16} />,
|
||||||
|
color: "orange",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const DemografiPekerjaan = () => {
|
const DemografiPekerjaan = () => {
|
||||||
@@ -152,14 +181,22 @@ const DemografiPekerjaan = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" mb="xs">
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
</Text>
|
</Text>
|
||||||
{React.cloneElement(kpi.icon, {
|
{React.cloneElement(kpi.icon, {
|
||||||
className: "h-6 w-6",
|
className: "h-6 w-6",
|
||||||
color: dark ? "var(--mantine-color-dark-3)" : "var(--mantine-color-dimmed)",
|
color: dark
|
||||||
|
? "var(--mantine-color-dark-3)"
|
||||||
|
: "var(--mantine-color-dimmed)",
|
||||||
})}
|
})}
|
||||||
</Group>
|
</Group>
|
||||||
<Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs">
|
<Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs">
|
||||||
@@ -173,7 +210,9 @@ const DemografiPekerjaan = () => {
|
|||||||
? "green"
|
? "green"
|
||||||
: kpi.deltaType === "negative"
|
: kpi.deltaType === "negative"
|
||||||
? "red"
|
? "red"
|
||||||
: dark ? "dark.3" : "dimmed"
|
: dark
|
||||||
|
? "dark.3"
|
||||||
|
: "dimmed"
|
||||||
}
|
}
|
||||||
mt={4}
|
mt={4}
|
||||||
>
|
>
|
||||||
@@ -194,7 +233,13 @@ const DemografiPekerjaan = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Grafik Pengelompokan Umur */}
|
{/* Grafik Pengelompokan Umur */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Grafik Pengelompokan Umur
|
Grafik Pengelompokan Umur
|
||||||
</Title>
|
</Title>
|
||||||
@@ -202,7 +247,7 @@ const DemografiPekerjaan = () => {
|
|||||||
h={300}
|
h={300}
|
||||||
data={ageDistributionData}
|
data={ageDistributionData}
|
||||||
dataKey="ageRange"
|
dataKey="ageRange"
|
||||||
series={[{ name: 'total', color: 'darmasaba-navy' }]}
|
series={[{ name: "total", color: "darmasaba-navy" }]}
|
||||||
withLegend
|
withLegend
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -210,7 +255,13 @@ const DemografiPekerjaan = () => {
|
|||||||
|
|
||||||
{/* Demografi Pekerjaan */}
|
{/* Demografi Pekerjaan */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Demografi Pekerjaan
|
Demografi Pekerjaan
|
||||||
</Title>
|
</Title>
|
||||||
@@ -218,7 +269,7 @@ const DemografiPekerjaan = () => {
|
|||||||
h={300}
|
h={300}
|
||||||
data={jobDistributionData}
|
data={jobDistributionData}
|
||||||
dataKey="job"
|
dataKey="job"
|
||||||
series={[{ name: 'total', color: 'darmasaba-navy' }]}
|
series={[{ name: "total", color: "darmasaba-navy" }]}
|
||||||
withLegend
|
withLegend
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -229,16 +280,22 @@ const DemografiPekerjaan = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Distribusi Agama */}
|
{/* Distribusi Agama */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Distribusi Agama
|
Distribusi Agama
|
||||||
</Title>
|
</Title>
|
||||||
<PieChart
|
<PieChart
|
||||||
h={300}
|
h={300}
|
||||||
data={religionData.map(item => ({
|
data={religionData.map((item) => ({
|
||||||
name: item.religion,
|
name: item.religion,
|
||||||
value: item.total,
|
value: item.total,
|
||||||
color: item.color
|
color: item.color,
|
||||||
}))}
|
}))}
|
||||||
withLabels
|
withLabels
|
||||||
withLabelsLine
|
withLabelsLine
|
||||||
@@ -250,27 +307,53 @@ const DemografiPekerjaan = () => {
|
|||||||
|
|
||||||
{/* Data per Banjar */}
|
{/* Data per Banjar */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
||||||
Data per Banjar
|
Data per Banjar
|
||||||
</Title>
|
</Title>
|
||||||
<Table striped highlightOnHover>
|
<Table striped highlightOnHover>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th><Text c={dark ? "dark.0" : "black"}>Banjar</Text></Table.Th>
|
<Table.Th>
|
||||||
<Table.Th><Text c={dark ? "dark.0" : "black"}>Penduduk</Text></Table.Th>
|
<Text c={dark ? "dark.0" : "black"}>Banjar</Text>
|
||||||
<Table.Th><Text c={dark ? "dark.0" : "black"}>KK</Text></Table.Th>
|
</Table.Th>
|
||||||
<Table.Th><Text c={dark ? "dark.0" : "black"}>Miskin</Text></Table.Th>
|
<Table.Th>
|
||||||
|
<Text c={dark ? "dark.0" : "black"}>Penduduk</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "dark.0" : "black"}>KK</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "dark.0" : "black"}>Miskin</Text>
|
||||||
|
</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{banjarData.map((item, index) => (
|
{banjarData.map((item, index) => (
|
||||||
<Table.Tr key={`${item.banjar}-${index}`}>
|
<Table.Tr key={`${item.banjar}-${index}`}>
|
||||||
<Table.Td><Text c={dark ? "dark.0" : "black"}>{item.banjar}</Text></Table.Td>
|
|
||||||
<Table.Td><Text c={dark ? "dark.0" : "black"}>{item.population.toLocaleString()}</Text></Table.Td>
|
|
||||||
<Table.Td><Text c={dark ? "dark.0" : "black"}>{item.kk.toLocaleString()}</Text></Table.Td>
|
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text c={dark ? "red.4" : "red"}>{item.poor.toLocaleString()}</Text>
|
<Text c={dark ? "dark.0" : "black"}>{item.banjar}</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.population.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.kk.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text c={dark ? "red.4" : "red"}>
|
||||||
|
{item.poor.toLocaleString()}
|
||||||
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
@@ -281,14 +364,29 @@ const DemografiPekerjaan = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Statistik Dinamika Penduduk */}
|
{/* Statistik Dinamika Penduduk */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
||||||
Statistik Dinamika Penduduk
|
Statistik Dinamika Penduduk
|
||||||
</Title>
|
</Title>
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{dynamicStats.map((stat, index) => (
|
{dynamicStats.map((stat, index) => (
|
||||||
<Grid.Col key={`${stat.title}-${index}`} span={{ base: 12, md: 3 }}>
|
<Grid.Col
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
key={`${stat.title}-${index}`}
|
||||||
|
span={{ base: 12, md: 3 }}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
||||||
@@ -298,9 +396,7 @@ const DemografiPekerjaan = () => {
|
|||||||
{stat.value}
|
{stat.value}
|
||||||
</Title>
|
</Title>
|
||||||
</Box>
|
</Box>
|
||||||
<Box c={stat.color}>
|
<Box c={stat.color}>{stat.icon}</Box>
|
||||||
{stat.icon}
|
|
||||||
</Box>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|||||||
@@ -1,108 +1,72 @@
|
|||||||
import { useLocation } from "@tanstack/react-router";
|
|
||||||
import { Bell, Moon, Sun, User as UserIcon } from "lucide-react"; // Renamed User to UserIcon to avoid conflict with Mantine's User component if it exists
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
|
||||||
ActionIcon,
|
|
||||||
Divider,
|
|
||||||
Avatar,
|
|
||||||
Box,
|
|
||||||
Badge,
|
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useLocation } from "@tanstack/react-router";
|
||||||
|
import { Bell, Moon, Sun } from "lucide-react";
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Define page titles based on route
|
const title =
|
||||||
const getPageTitle = () => {
|
location.pathname === "/"
|
||||||
switch (location.pathname) {
|
? "Desa Darmasaba"
|
||||||
case "/":
|
: "Desa Darmasaba";
|
||||||
return "Desa Darmasaba";
|
|
||||||
case "/kinerja-divisi":
|
|
||||||
return "Kinerja Divisi";
|
|
||||||
case "/pengaduan":
|
|
||||||
return "Pengaduan & Layanan Publik";
|
|
||||||
case "/analytic":
|
|
||||||
return "Jenna Analytic";
|
|
||||||
case "/demografi":
|
|
||||||
return "Demografi & Kependudukan";
|
|
||||||
case "/keuangan":
|
|
||||||
return "Keuangan & Anggaran";
|
|
||||||
case "/bumdes":
|
|
||||||
return "Bumdes & UMKM Desa";
|
|
||||||
case "/sosial":
|
|
||||||
case "/keamanan":
|
|
||||||
return "Keamanan";
|
|
||||||
case "/bantuan":
|
|
||||||
return "Bantuan";
|
|
||||||
case "/pengaturan":
|
|
||||||
return "Pengaturan";
|
|
||||||
default:
|
|
||||||
return "Desa Darmasaba";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group justify="space-between" w="100%">
|
<Box
|
||||||
{/* Title */}
|
style={{
|
||||||
<Title order={3} c={"white"}>{getPageTitle()}</Title>
|
display: "grid",
|
||||||
|
gridTemplateColumns: "1fr auto 1fr",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* LEFT SPACER (burger sudah di luar) */}
|
||||||
|
<Box />
|
||||||
|
|
||||||
{/* Right Section */}
|
{/* CENTER TITLE */}
|
||||||
<Group gap="md">
|
<Text
|
||||||
|
c="white"
|
||||||
|
fw={600}
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* RIGHT ICONS */}
|
||||||
|
<Group gap="xs" justify="flex-end">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={toggleColorScheme}
|
||||||
|
variant="subtle"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
{dark ? <Sun size={18} /> : <Moon size={18} />}
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
{/* User Info */}
|
<ActionIcon variant="subtle" radius="xl" pos="relative">
|
||||||
<Group gap="sm">
|
<Bell size={18} />
|
||||||
<Box ta="right">
|
<Badge
|
||||||
<Text c={"white"} size="sm" fw={500}>
|
size="xs"
|
||||||
I. B. Surya Prabhawa M...
|
color="red"
|
||||||
</Text>
|
style={{ position: "absolute", top: -4, right: -4 }}
|
||||||
<Text c={"white"} size="xs">
|
|
||||||
Kepala Desa
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Avatar color="blue" radius="xl">
|
|
||||||
<UserIcon color="white" style={{ width: "70%", height: "70%" }} />
|
|
||||||
</Avatar>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{/* Divider */}
|
|
||||||
<Divider orientation="vertical" h={30} />
|
|
||||||
|
|
||||||
{/* Icons */}
|
|
||||||
<Group gap="sm">
|
|
||||||
<ActionIcon
|
|
||||||
onClick={() => toggleColorScheme()}
|
|
||||||
variant="subtle"
|
|
||||||
size="lg"
|
|
||||||
radius="xl"
|
|
||||||
aria-label="Toggle color scheme"
|
|
||||||
>
|
>
|
||||||
{dark ? (
|
10
|
||||||
<Sun color="white" style={{ width: "70%", height: "70%" }} />
|
</Badge>
|
||||||
) : (
|
</ActionIcon>
|
||||||
<Moon color="white" style={{ width: "70%", height: "70%" }} />
|
|
||||||
)}
|
|
||||||
</ActionIcon>
|
|
||||||
<ActionIcon variant="subtle" size="lg" radius="xl" pos="relative">
|
|
||||||
<Bell color="white" style={{ width: "70%", height: "70%" }} />
|
|
||||||
<Badge
|
|
||||||
size="xs"
|
|
||||||
color="red"
|
|
||||||
variant="filled"
|
|
||||||
style={{ position: "absolute", top: 0, right: 0 }}
|
|
||||||
radius={"xl"}
|
|
||||||
>
|
|
||||||
10
|
|
||||||
</Badge>
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,248 +1,435 @@
|
|||||||
import { Container, Grid, Title, Text, SimpleGrid, Box, Accordion, Stack, useMantineColorScheme } from '@mantine/core';
|
import {
|
||||||
import { HelpCard } from '@/components/ui/help-card';
|
Accordion,
|
||||||
import { IconBook, IconVideo, IconHelpCircle, IconMessage, IconFileText, IconHeadphones } from '@tabler/icons-react';
|
Box,
|
||||||
import { useState } from 'react';
|
Container,
|
||||||
|
Grid,
|
||||||
|
SimpleGrid,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconBook,
|
||||||
|
IconFileText,
|
||||||
|
IconHeadphones,
|
||||||
|
IconHelpCircle,
|
||||||
|
IconMessage,
|
||||||
|
IconVideo,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { HelpCard } from "@/components/ui/help-card";
|
||||||
|
|
||||||
const HelpPage = () => {
|
const HelpPage = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
// Sample data for sections
|
// Sample data for sections
|
||||||
const guideItems = [
|
const guideItems = [
|
||||||
{ title: 'Cara Login', description: 'Langkah-langkah untuk login ke dashboard' },
|
{
|
||||||
{ title: 'Navigasi Dashboard', description: 'Penjelasan tentang tata letak dan navigasi' },
|
title: "Cara Login",
|
||||||
{ title: 'Fitur Dasar', description: 'Panduan penggunaan fitur-fitur utama' },
|
description: "Langkah-langkah untuk login ke dashboard",
|
||||||
{ title: 'Tips & Trik', description: 'Tips untuk meningkatkan produktivitas' },
|
},
|
||||||
];
|
{
|
||||||
|
title: "Navigasi Dashboard",
|
||||||
|
description: "Penjelasan tentang tata letak dan navigasi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fitur Dasar",
|
||||||
|
description: "Panduan penggunaan fitur-fitur utama",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Tips & Trik",
|
||||||
|
description: "Tips untuk meningkatkan produktivitas",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const videoItems = [
|
const videoItems = [
|
||||||
{ title: 'Dashboard Overview', duration: '5:23' },
|
{ title: "Dashboard Overview", duration: "5:23" },
|
||||||
{ title: 'Analisis Data', duration: '8:45' },
|
{ title: "Analisis Data", duration: "8:45" },
|
||||||
{ title: 'Membuat Laporan', duration: '6:12' },
|
{ title: "Membuat Laporan", duration: "6:12" },
|
||||||
{ title: 'Export Data', duration: '4:30' },
|
{ title: "Export Data", duration: "4:30" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const faqItems = [
|
const faqItems = [
|
||||||
{ question: 'Bagaimana cara reset password?', answer: 'Anda dapat mereset password melalui halaman login dengan klik "Lupa Password"' },
|
{
|
||||||
{ question: 'Apakah saya bisa mengakses data offline?', answer: 'Saat ini aplikasi hanya dapat diakses secara online' },
|
question: "Bagaimana cara reset password?",
|
||||||
{ question: 'Berapa lama waktu respon support?', answer: 'Tim support kami biasanya merespon dalam waktu kurang dari 24 jam' },
|
answer:
|
||||||
{ question: 'Bagaimana cara menambahkan pengguna baru?', answer: 'Fitur penambahan pengguna dapat ditemukan di menu Pengaturan > Manajemen Pengguna' },
|
'Anda dapat mereset password melalui halaman login dengan klik "Lupa Password"',
|
||||||
];
|
},
|
||||||
|
{
|
||||||
|
question: "Apakah saya bisa mengakses data offline?",
|
||||||
|
answer: "Saat ini aplikasi hanya dapat diakses secara online",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Berapa lama waktu respon support?",
|
||||||
|
answer:
|
||||||
|
"Tim support kami biasanya merespon dalam waktu kurang dari 24 jam",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Bagaimana cara menambahkan pengguna baru?",
|
||||||
|
answer:
|
||||||
|
"Fitur penambahan pengguna dapat ditemukan di menu Pengaturan > Manajemen Pengguna",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const documentationItems = [
|
const documentationItems = [
|
||||||
{ title: 'API Reference', description: 'Dokumentasi lengkap untuk integrasi API' },
|
{
|
||||||
{ title: 'Integrasi Sistem', description: 'Cara mengintegrasikan dengan sistem eksternal' },
|
title: "API Reference",
|
||||||
{ title: 'Format Data', description: 'Spesifikasi format data yang didukung' },
|
description: "Dokumentasi lengkap untuk integrasi API",
|
||||||
{ title: 'Best Practices', description: 'Praktik terbaik dalam penggunaan platform' },
|
},
|
||||||
];
|
{
|
||||||
|
title: "Integrasi Sistem",
|
||||||
|
description: "Cara mengintegrasikan dengan sistem eksternal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Format Data",
|
||||||
|
description: "Spesifikasi format data yang didukung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Best Practices",
|
||||||
|
description: "Praktik terbaik dalam penggunaan platform",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{ value: '150+', label: 'Artikel Panduan' },
|
{ value: "150+", label: "Artikel Panduan" },
|
||||||
{ value: '50+', label: 'Video Tutorial' },
|
{ value: "50+", label: "Video Tutorial" },
|
||||||
{ value: '24/7', label: 'Support Aktif' },
|
{ value: "24/7", label: "Support Aktif" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// State for chat functionality
|
// State for chat functionality
|
||||||
const [messages, setMessages] = useState([
|
const [messages, setMessages] = useState([
|
||||||
{ id: 1, text: 'Halo! Saya Jenna, asisten virtual Anda. Bagaimana saya bisa membantu hari ini?', sender: 'jenna' }
|
{
|
||||||
]);
|
id: 1,
|
||||||
const [inputValue, setInputValue] = useState('');
|
text: "Halo! Saya Jenna, asisten virtual Anda. Bagaimana saya bisa membantu hari ini?",
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
sender: "jenna",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleSendMessage = () => {
|
const handleSendMessage = () => {
|
||||||
if (inputValue.trim() === '') return;
|
if (inputValue.trim() === "") return;
|
||||||
|
|
||||||
// Add user message
|
// Add user message
|
||||||
const newUserMessage = {
|
const newUserMessage = {
|
||||||
id: messages.length + 1,
|
id: messages.length + 1,
|
||||||
text: inputValue,
|
text: inputValue,
|
||||||
sender: 'user'
|
sender: "user",
|
||||||
};
|
};
|
||||||
|
|
||||||
setMessages(prev => [...prev, newUserMessage]);
|
setMessages((prev) => [...prev, newUserMessage]);
|
||||||
setInputValue('');
|
setInputValue("");
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// Simulate Jenna's response after delay
|
// Simulate Jenna's response after delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const jennaResponse = {
|
const jennaResponse = {
|
||||||
id: messages.length + 2,
|
id: messages.length + 2,
|
||||||
text: 'Terima kasih atas pertanyaan Anda. Saat ini saya adalah versi awal dari asisten virtual. Tim kami sedang mengembangkan kemampuan saya lebih lanjut.',
|
text: "Terima kasih atas pertanyaan Anda. Saat ini saya adalah versi awal dari asisten virtual. Tim kami sedang mengembangkan kemampuan saya lebih lanjut.",
|
||||||
sender: 'jenna'
|
sender: "jenna",
|
||||||
};
|
};
|
||||||
setMessages(prev => [...prev, jennaResponse]);
|
setMessages((prev) => [...prev, jennaResponse]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSendMessage();
|
handleSendMessage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="lg" py="xl">
|
<Container size="lg" py="xl">
|
||||||
<Title order={1} mb="xl" ta="center">Pusat Bantuan</Title>
|
<Title order={1} mb="xl" ta="center">
|
||||||
<Text size="lg" color="dimmed" ta="center" mb="xl">
|
Pusat Bantuan
|
||||||
Temukan jawaban untuk pertanyaan Anda atau hubungi tim support kami
|
</Title>
|
||||||
</Text>
|
<Text size="lg" color="dimmed" ta="center" mb="xl">
|
||||||
|
Temukan jawaban untuk pertanyaan Anda atau hubungi tim support kami
|
||||||
|
</Text>
|
||||||
|
|
||||||
{/* Statistics Section */}
|
{/* Statistics Section */}
|
||||||
<SimpleGrid cols={3} spacing="lg" mb="xl">
|
<SimpleGrid cols={3} spacing="lg" mb="xl">
|
||||||
{stats.map((stat, index) => (
|
{stats.map((stat, index) => (
|
||||||
<HelpCard key={index} bg={dark ? "#141D34" : "white"} p="lg" style={{ textAlign: 'center', borderColor: dark ? "#141D34" : "white" }} h="100%" >
|
<HelpCard
|
||||||
<Text size="xl" fw={700} style={{ fontSize: '32px' }}>{stat.value}</Text>
|
key={index}
|
||||||
<Text size="sm" color="dimmed">{stat.label}</Text>
|
bg={dark ? "#141D34" : "white"}
|
||||||
</HelpCard>
|
p="lg"
|
||||||
))}
|
style={{
|
||||||
</SimpleGrid>
|
textAlign: "center",
|
||||||
|
borderColor: dark ? "#141D34" : "white",
|
||||||
|
}}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Text size="xl" fw={700} style={{ fontSize: "32px" }}>
|
||||||
|
{stat.value}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" color="dimmed">
|
||||||
|
{stat.label}
|
||||||
|
</Text>
|
||||||
|
</HelpCard>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Box>
|
<Box>
|
||||||
<Grid gutter="lg" justify="center">
|
<Grid gutter="lg" justify="center">
|
||||||
{/* Panduan Memulai */}
|
{/* Panduan Memulai */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconBook size={24} />} title="Panduan Memulai" h="100%">
|
<HelpCard
|
||||||
<Box>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{guideItems.map((item, index) => (
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Navigasi ke ${item.title}`)}>
|
icon={<IconBook size={24} />}
|
||||||
<Text fw={500}>{item.title}</Text>
|
title="Panduan Memulai"
|
||||||
<Text size="sm" color="dimmed">{item.description}</Text>
|
h="100%"
|
||||||
</Box>
|
>
|
||||||
))}
|
<Box>
|
||||||
</Box>
|
{guideItems.map((item, index) => (
|
||||||
</HelpCard>
|
<Box
|
||||||
</Grid.Col>
|
key={index}
|
||||||
|
py="sm"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid #eee",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => alert(`Navigasi ke ${item.title}`)}
|
||||||
|
>
|
||||||
|
<Text fw={500}>{item.title}</Text>
|
||||||
|
<Text size="sm" color="dimmed">
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</HelpCard>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
{/* Video Tutorial */}
|
{/* Video Tutorial */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconVideo size={24} />} title="Video Tutorial" h="100%">
|
<HelpCard
|
||||||
<Box>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{videoItems.map((item, index) => (
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Buka video: ${item.title}`)}>
|
icon={<IconVideo size={24} />}
|
||||||
<Text fw={500}>{item.title}</Text>
|
title="Video Tutorial"
|
||||||
<Text size="sm" color="dimmed">{item.duration}</Text>
|
h="100%"
|
||||||
</Box>
|
>
|
||||||
))}
|
<Box>
|
||||||
</Box>
|
{videoItems.map((item, index) => (
|
||||||
</HelpCard>
|
<Box
|
||||||
</Grid.Col>
|
key={index}
|
||||||
|
py="sm"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid #eee",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => alert(`Buka video: ${item.title}`)}
|
||||||
|
>
|
||||||
|
<Text fw={500}>{item.title}</Text>
|
||||||
|
<Text size="sm" color="dimmed">
|
||||||
|
{item.duration}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</HelpCard>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
{/* FAQ */}
|
{/* FAQ */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconHelpCircle size={24} />} title="FAQ" h="100%">
|
<HelpCard
|
||||||
<Accordion variant="separated" >
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{faqItems.map((item, index) => (
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Accordion.Item style={{ backgroundColor: dark ? "#263852ff" : "#F1F5F9" }} key={index} value={`faq-${index}`}>
|
icon={<IconHelpCircle size={24} />}
|
||||||
<Accordion.Control>{item.question}</Accordion.Control>
|
title="FAQ"
|
||||||
<Accordion.Panel>
|
h="100%"
|
||||||
<Text size="sm">{item.answer}</Text>
|
>
|
||||||
</Accordion.Panel>
|
<Accordion variant="separated">
|
||||||
</Accordion.Item>
|
{faqItems.map((item, index) => (
|
||||||
))}
|
<Accordion.Item
|
||||||
</Accordion>
|
style={{
|
||||||
</HelpCard>
|
backgroundColor: dark ? "#263852ff" : "#F1F5F9",
|
||||||
</Grid.Col>
|
}}
|
||||||
</Grid>
|
key={index}
|
||||||
</Box>
|
value={`faq-${index}`}
|
||||||
|
>
|
||||||
|
<Accordion.Control>{item.question}</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<Text size="sm">{item.answer}</Text>
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
</HelpCard>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Grid>
|
<Grid>
|
||||||
{/* Hubungi Support */}
|
{/* Hubungi Support */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconHeadphones size={24} />} title="Hubungi Support" h="100%">
|
<HelpCard
|
||||||
<Box>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text fw={500}>Email</Text>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Text size="sm" color="dimmed" mb="md"><a href="mailto:support@example.com">support@example.com</a></Text>
|
icon={<IconHeadphones size={24} />}
|
||||||
|
title="Hubungi Support"
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text fw={500}>Email</Text>
|
||||||
|
<Text size="sm" color="dimmed" mb="md">
|
||||||
|
<a href="mailto:support@example.com">support@example.com</a>
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Text fw={500}>WhatsApp</Text>
|
<Text fw={500}>WhatsApp</Text>
|
||||||
<Text size="sm" color="dimmed" mb="md"><a href="https://wa.me/1234567890">+62 123 456 7890</a></Text>
|
<Text size="sm" color="dimmed" mb="md">
|
||||||
|
<a href="https://wa.me/1234567890">+62 123 456 7890</a>
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Text fw={500}>Jam Kerja</Text>
|
<Text fw={500}>Jam Kerja</Text>
|
||||||
<Text size="sm" color="dimmed">Senin - Jumat, 09:00 - 17:00 WIB</Text>
|
<Text size="sm" color="dimmed">
|
||||||
|
Senin - Jumat, 09:00 - 17:00 WIB
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Text fw={500} mt="md">Waktu Respon</Text>
|
<Text fw={500} mt="md">
|
||||||
<Text size="sm" color="dimmed">Rata-rata 2-4 jam kerja</Text>
|
Waktu Respon
|
||||||
</Box>
|
</Text>
|
||||||
</HelpCard>
|
<Text size="sm" color="dimmed">
|
||||||
</Grid.Col>
|
Rata-rata 2-4 jam kerja
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</HelpCard>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
{/* Dokumentasi */}
|
{/* Dokumentasi */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconFileText size={24} />} title="Dokumentasi" h="100%">
|
<HelpCard
|
||||||
<Box>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{documentationItems.map((item, index) => (
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Navigasi ke dokumentasi: ${item.title}`)}>
|
icon={<IconFileText size={24} />}
|
||||||
<Text fw={500}>{item.title}</Text>
|
title="Dokumentasi"
|
||||||
<Text size="sm" color="dimmed">{item.description}</Text>
|
h="100%"
|
||||||
</Box>
|
>
|
||||||
))}
|
<Box>
|
||||||
</Box>
|
{documentationItems.map((item, index) => (
|
||||||
</HelpCard>
|
<Box
|
||||||
</Grid.Col>
|
key={index}
|
||||||
|
py="sm"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid #eee",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
alert(`Navigasi ke dokumentasi: ${item.title}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text fw={500}>{item.title}</Text>
|
||||||
|
<Text size="sm" color="dimmed">
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</HelpCard>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
{/* Jenna - Virtual Assistant */}
|
{/* Jenna - Virtual Assistant */}
|
||||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||||
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconMessage size={24} />} title="Jenna - Virtual Assistant" h="100%">
|
<HelpCard
|
||||||
<Box style={{ height: '300px', display: 'flex', flexDirection: 'column' }}>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Box style={{ flex: 1, overflowY: 'auto', marginBottom: '12px', maxHeight: '200px' }}>
|
bg={dark ? "#141D34" : "white"}
|
||||||
{messages.map((msg) => (
|
icon={<IconMessage size={24} />}
|
||||||
<Box
|
title="Jenna - Virtual Assistant"
|
||||||
key={msg.id}
|
h="100%"
|
||||||
style={{
|
>
|
||||||
alignSelf: msg.sender === 'user' ? 'flex-end' : 'flex-start',
|
<Box
|
||||||
backgroundColor: msg.sender === 'user' ? dark ? "#263852ff" : "#F1F5F9" : dark ? "#263852ff" : "#F1F5F9",
|
style={{
|
||||||
color: msg.sender === 'user' ? dark ? "#F1F5F9" : "#263852ff" : dark ? "#F1F5F9" : "#263852ff",
|
height: "300px",
|
||||||
padding: '8px 12px',
|
display: "flex",
|
||||||
borderRadius: '8px',
|
flexDirection: "column",
|
||||||
marginBottom: '8px',
|
}}
|
||||||
maxWidth: '80%'
|
>
|
||||||
}}
|
<Box
|
||||||
>
|
style={{
|
||||||
{msg.text}
|
flex: 1,
|
||||||
</Box>
|
overflowY: "auto",
|
||||||
))}
|
marginBottom: "12px",
|
||||||
</Box>
|
maxHeight: "200px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{messages.map((msg) => (
|
||||||
|
<Box
|
||||||
|
key={msg.id}
|
||||||
|
style={{
|
||||||
|
alignSelf:
|
||||||
|
msg.sender === "user" ? "flex-end" : "flex-start",
|
||||||
|
backgroundColor:
|
||||||
|
msg.sender === "user"
|
||||||
|
? dark
|
||||||
|
? "#263852ff"
|
||||||
|
: "#F1F5F9"
|
||||||
|
: dark
|
||||||
|
? "#263852ff"
|
||||||
|
: "#F1F5F9",
|
||||||
|
color:
|
||||||
|
msg.sender === "user"
|
||||||
|
? dark
|
||||||
|
? "#F1F5F9"
|
||||||
|
: "#263852ff"
|
||||||
|
: dark
|
||||||
|
? "#F1F5F9"
|
||||||
|
: "#263852ff",
|
||||||
|
padding: "8px 12px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
marginBottom: "8px",
|
||||||
|
maxWidth: "80%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg.text}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box style={{ display: 'flex', gap: '8px' }}>
|
<Box style={{ display: "flex", gap: "8px" }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
placeholder="Ketik pesan Anda..."
|
placeholder="Ketik pesan Anda..."
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: '8px 12px',
|
padding: "8px 12px",
|
||||||
borderRadius: '20px',
|
borderRadius: "20px",
|
||||||
border: '1px solid #ccc',
|
border: "1px solid #ccc",
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
disabled={isLoading || inputValue.trim() === ''}
|
disabled={isLoading || inputValue.trim() === ""}
|
||||||
style={{
|
style={{
|
||||||
padding: '8px 16px',
|
padding: "8px 16px",
|
||||||
borderRadius: '20px',
|
borderRadius: "20px",
|
||||||
backgroundColor: '#3B82F6',
|
backgroundColor: "#3B82F6",
|
||||||
color: 'white',
|
color: "white",
|
||||||
border: 'none',
|
border: "none",
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Kirim
|
Kirim
|
||||||
</button>
|
</button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</HelpCard>
|
</HelpCard>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HelpPage;
|
export default HelpPage;
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from "react";
|
import { BarChart } from "@mantine/charts";
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Badge,
|
|
||||||
Progress,
|
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
Group,
|
|
||||||
Stack,
|
|
||||||
Grid,
|
Grid,
|
||||||
Box,
|
Group,
|
||||||
|
Progress,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { BarChart } from "@mantine/charts";
|
import React from "react";
|
||||||
|
|
||||||
// Sample Data
|
// Sample Data
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
@@ -144,7 +144,13 @@ const JennaAnalytic = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" mb="xs">
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
<Text size="sm" fw={500} c="dimmed">
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
@@ -182,7 +188,13 @@ const JennaAnalytic = () => {
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Interaksi Chatbot
|
Interaksi Chatbot
|
||||||
</Title>
|
</Title>
|
||||||
@@ -190,7 +202,7 @@ const JennaAnalytic = () => {
|
|||||||
h={300}
|
h={300}
|
||||||
data={chartData}
|
data={chartData}
|
||||||
dataKey="day"
|
dataKey="day"
|
||||||
series={[{ name: 'total', color: 'blue' }]}
|
series={[{ name: "total", color: "blue" }]}
|
||||||
withLegend
|
withLegend
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -199,16 +211,21 @@ const JennaAnalytic = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Grafik Interaksi Chatbot (now Bar Chart) */}
|
{/* Grafik Interaksi Chatbot (now Bar Chart) */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Jam Tersibuk
|
Jam Tersibuk
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{busyHours.map((item, index) => (
|
{busyHours.map((item, index) => (
|
||||||
<Box key={index}>
|
<Box key={index}>
|
||||||
<Text size="sm">
|
<Text size="sm">{item.period}</Text>
|
||||||
{item.period}
|
|
||||||
</Text>
|
|
||||||
<Group align="center">
|
<Group align="center">
|
||||||
<Progress value={item.percentage} flex={1} />
|
<Progress value={item.percentage} flex={1} />
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
@@ -225,7 +242,14 @@ const JennaAnalytic = () => {
|
|||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Topik Pertanyaan Terbanyak */}
|
{/* Topik Pertanyaan Terbanyak */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Topik Pertanyaan Terbanyak
|
Topik Pertanyaan Terbanyak
|
||||||
</Title>
|
</Title>
|
||||||
@@ -251,12 +275,9 @@ const JennaAnalytic = () => {
|
|||||||
{/* Jam Tersibuk */}
|
{/* Jam Tersibuk */}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid >
|
</Grid>
|
||||||
|
</Stack>
|
||||||
</Stack >
|
</Box>
|
||||||
</Box >
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
}
|
|
||||||
export default JennaAnalytic;
|
export default JennaAnalytic;
|
||||||
|
|
||||||
|
|||||||
@@ -1,225 +1,325 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Badge,
|
||||||
Grid,
|
Box,
|
||||||
GridCol,
|
Card,
|
||||||
Group,
|
Grid,
|
||||||
Text,
|
GridCol,
|
||||||
Title,
|
Group,
|
||||||
Stack,
|
List,
|
||||||
useMantineColorScheme,
|
Stack,
|
||||||
Badge,
|
Text,
|
||||||
List,
|
ThemeIcon,
|
||||||
ThemeIcon,
|
Title,
|
||||||
Box
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconCamera,
|
IconAlertTriangle,
|
||||||
IconAlertTriangle,
|
IconCamera,
|
||||||
IconMapPin,
|
IconClock,
|
||||||
IconClock,
|
IconEye,
|
||||||
IconEye,
|
IconMapPin,
|
||||||
IconShieldLock
|
IconShieldLock,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const KeamananPage = () => {
|
const KeamananPage = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Sample data for KPI cards
|
// Sample data for KPI cards
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
{
|
{
|
||||||
title: "CCTV Aktif",
|
title: "CCTV Aktif",
|
||||||
value: 20,
|
value: 20,
|
||||||
subtitle: "Kamera Online",
|
subtitle: "Kamera Online",
|
||||||
icon: <IconCamera size={24} />,
|
icon: <IconCamera size={24} />,
|
||||||
color: "darmasaba-success",
|
color: "darmasaba-success",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Laporan Keamanan",
|
title: "Laporan Keamanan",
|
||||||
value: 15,
|
value: 15,
|
||||||
subtitle: "Minggu ini",
|
subtitle: "Minggu ini",
|
||||||
icon: <IconAlertTriangle size={24} />,
|
icon: <IconAlertTriangle size={24} />,
|
||||||
color: "darmasaba-danger",
|
color: "darmasaba-danger",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sample data for CCTV locations
|
// Sample data for CCTV locations
|
||||||
const cctvLocations = [
|
const cctvLocations = [
|
||||||
{ id: "CCTV-01", lat: -8.5, lng: 115.2, status: "active", lastSeen: "2 jam yang lalu", location: "Balai Desa" },
|
{
|
||||||
{ id: "CCTV-02", lat: -8.6, lng: 115.3, status: "active", lastSeen: "1 jam yang lalu", location: "Pintu Masuk Desa" },
|
id: "CCTV-01",
|
||||||
{ id: "CCTV-03", lat: -8.4, lng: 115.1, status: "offline", lastSeen: "1 hari yang lalu", location: "Taman Desa" },
|
lat: -8.5,
|
||||||
{ id: "CCTV-04", lat: -8.7, lng: 115.4, status: "active", lastSeen: "30 menit yang lalu", location: "Pasar Desa" },
|
lng: 115.2,
|
||||||
];
|
status: "active",
|
||||||
|
lastSeen: "2 jam yang lalu",
|
||||||
|
location: "Balai Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "CCTV-02",
|
||||||
|
lat: -8.6,
|
||||||
|
lng: 115.3,
|
||||||
|
status: "active",
|
||||||
|
lastSeen: "1 jam yang lalu",
|
||||||
|
location: "Pintu Masuk Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "CCTV-03",
|
||||||
|
lat: -8.4,
|
||||||
|
lng: 115.1,
|
||||||
|
status: "offline",
|
||||||
|
lastSeen: "1 hari yang lalu",
|
||||||
|
location: "Taman Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "CCTV-04",
|
||||||
|
lat: -8.7,
|
||||||
|
lng: 115.4,
|
||||||
|
status: "active",
|
||||||
|
lastSeen: "30 menit yang lalu",
|
||||||
|
location: "Pasar Desa",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Sample data for security reports
|
// Sample data for security reports
|
||||||
const securityReports = [
|
const securityReports = [
|
||||||
{
|
{
|
||||||
id: "REP-001",
|
id: "REP-001",
|
||||||
title: "Pencurian Motor",
|
title: "Pencurian Motor",
|
||||||
reportedAt: "2 jam yang lalu",
|
reportedAt: "2 jam yang lalu",
|
||||||
date: "12 Feb 2026, 14:30",
|
date: "12 Feb 2026, 14:30",
|
||||||
location: "Jl. Kecubung 20",
|
location: "Jl. Kecubung 20",
|
||||||
status: "baru",
|
status: "baru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "REP-002",
|
id: "REP-002",
|
||||||
title: "Kerusuhan Antar Warga",
|
title: "Kerusuhan Antar Warga",
|
||||||
reportedAt: "4 jam yang lalu",
|
reportedAt: "4 jam yang lalu",
|
||||||
date: "12 Feb 2026, 12:15",
|
date: "12 Feb 2026, 12:15",
|
||||||
location: "RT 05 RW 02",
|
location: "RT 05 RW 02",
|
||||||
status: "baru",
|
status: "baru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "REP-003",
|
id: "REP-003",
|
||||||
title: "Kebakaran Rumah",
|
title: "Kebakaran Rumah",
|
||||||
reportedAt: "1 hari yang lalu",
|
reportedAt: "1 hari yang lalu",
|
||||||
date: "11 Feb 2026, 08:45",
|
date: "11 Feb 2026, 08:45",
|
||||||
location: "Jl. Flamboyan 15",
|
location: "Jl. Flamboyan 15",
|
||||||
status: "diproses",
|
status: "diproses",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "REP-004",
|
id: "REP-004",
|
||||||
title: "Kehilangan Barang",
|
title: "Kehilangan Barang",
|
||||||
reportedAt: "2 hari yang lalu",
|
reportedAt: "2 hari yang lalu",
|
||||||
date: "10 Feb 2026, 16:20",
|
date: "10 Feb 2026, 16:20",
|
||||||
location: "Taman Desa",
|
location: "Taman Desa",
|
||||||
status: "selesai",
|
status: "selesai",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Page Header */}
|
{/* Page Header */}
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||||
Keamanan Lingkungan Desa
|
Keamanan Lingkungan Desa
|
||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* KPI Cards */}
|
{/* KPI Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{kpiData.map((kpi, index) => (
|
{kpiData.map((kpi, index) => (
|
||||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
|
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
{kpi.subtitle}
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Group gap="xs" align="center">
|
h="100%"
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{kpi.value}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
{kpi.title}
|
{kpi.subtitle}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
<Group gap="xs" align="center">
|
||||||
</Stack>
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
<ThemeIcon variant="light" color={kpi.color} size="xl" radius="xl">
|
{kpi.value}
|
||||||
{kpi.icon}
|
</Text>
|
||||||
</ThemeIcon>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
</Group>
|
{kpi.title}
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Group>
|
||||||
))}
|
</Stack>
|
||||||
</Grid>
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color={kpi.color}
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
{kpi.icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Peta Keamanan CCTV */}
|
{/* Peta Keamanan CCTV */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Peta Keamanan CCTV</Title>
|
p="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">Titik Lokasi CCTV</Text>
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Peta Keamanan CCTV
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">
|
||||||
|
Titik Lokasi CCTV
|
||||||
|
</Text>
|
||||||
|
|
||||||
{/* Placeholder for map */}
|
{/* Placeholder for map */}
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: dark ? '#2d3748' : '#e2e8f0',
|
backgroundColor: dark ? "#2d3748" : "#e2e8f0",
|
||||||
borderRadius: '8px',
|
borderRadius: "8px",
|
||||||
height: '400px',
|
height: "400px",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center'
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
<IconMapPin size={48} stroke={1.5} color={dark ? '#94a3b8' : '#64748b'} />
|
<IconMapPin
|
||||||
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
|
size={48}
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">Integrasi dengan Google Maps atau Mapbox akan ditampilkan di sini</Text>
|
stroke={1.5}
|
||||||
</Stack>
|
color={dark ? "#94a3b8" : "#64748b"}
|
||||||
</Box>
|
/>
|
||||||
|
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">
|
||||||
|
Integrasi dengan Google Maps atau Mapbox akan ditampilkan di
|
||||||
|
sini
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* CCTV Locations List */}
|
{/* CCTV Locations List */}
|
||||||
<Stack mt="md" gap="sm">
|
<Stack mt="md" gap="sm">
|
||||||
<Title order={4} c={dark ? "dark.0" : "black"}>Daftar CCTV</Title>
|
<Title order={4} c={dark ? "dark.0" : "black"}>
|
||||||
{cctvLocations.map((cctv, index) => (
|
Daftar CCTV
|
||||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
</Title>
|
||||||
<Group justify="space-between">
|
{cctvLocations.map((cctv, index) => (
|
||||||
<Stack gap={0}>
|
<Card
|
||||||
<Group gap="xs">
|
key={index}
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{cctv.id}</Text>
|
p="md"
|
||||||
<Badge
|
radius="md"
|
||||||
variant="dot"
|
withBorder
|
||||||
color={cctv.status === "active" ? "green" : "gray"}
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
>
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
{cctv.status === "active" ? "Online" : "Offline"}
|
>
|
||||||
</Badge>
|
<Group justify="space-between">
|
||||||
</Group>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.location}</Text>
|
<Group gap="xs">
|
||||||
</Stack>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
<Group gap="xs">
|
{cctv.id}
|
||||||
<IconClock size={16} stroke={1.5} />
|
</Text>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.lastSeen}</Text>
|
<Badge
|
||||||
</Group>
|
variant="dot"
|
||||||
</Group>
|
color={cctv.status === "active" ? "green" : "gray"}
|
||||||
</Card>
|
>
|
||||||
))}
|
{cctv.status === "active" ? "Online" : "Offline"}
|
||||||
</Stack>
|
</Badge>
|
||||||
</Card>
|
</Group>
|
||||||
</GridCol>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{cctv.location}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Group gap="xs">
|
||||||
|
<IconClock size={16} stroke={1.5} />
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{cctv.lastSeen}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Daftar Laporan Keamanan */}
|
{/* Daftar Laporan Keamanan */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Laporan Keamanan Lingkungan</Title>
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Laporan Keamanan Lingkungan
|
||||||
|
</Title>
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{securityReports.map((report, index) => (
|
{securityReports.map((report, index) => (
|
||||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
<Card
|
||||||
<Group justify="space-between" mb="sm">
|
key={index}
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{report.title}</Text>
|
p="md"
|
||||||
<Badge
|
radius="md"
|
||||||
variant="light"
|
withBorder
|
||||||
color={
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
report.status === "baru" ? "red" :
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
report.status === "diproses" ? "yellow" : "green"
|
>
|
||||||
}
|
<Group justify="space-between" mb="sm">
|
||||||
>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
{report.status}
|
{report.title}
|
||||||
</Badge>
|
</Text>
|
||||||
</Group>
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={
|
||||||
|
report.status === "baru"
|
||||||
|
? "red"
|
||||||
|
: report.status === "diproses"
|
||||||
|
? "yellow"
|
||||||
|
: "green"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{report.status}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconMapPin size={16} stroke={1.5} />
|
<IconMapPin size={16} stroke={1.5} />
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.location}</Text>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
</Group>
|
{report.location}
|
||||||
<Group gap="xs">
|
</Text>
|
||||||
<IconClock size={16} stroke={1.5} />
|
</Group>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.reportedAt}</Text>
|
<Group gap="xs">
|
||||||
</Group>
|
<IconClock size={16} stroke={1.5} />
|
||||||
</Group>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{report.reportedAt}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mt="sm">{report.date}</Text>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mt="sm">
|
||||||
</Card>
|
{report.date}
|
||||||
))}
|
</Text>
|
||||||
</Stack>
|
</Card>
|
||||||
</Card>
|
))}
|
||||||
</GridCol>
|
</Stack>
|
||||||
</Grid>
|
</Card>
|
||||||
</Stack>
|
</GridCol>
|
||||||
);
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KeamananPage;
|
export default KeamananPage;
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
import React from "react";
|
import { BarChart } from "@mantine/charts";
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Badge,
|
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
Group,
|
|
||||||
Stack,
|
|
||||||
Grid,
|
Grid,
|
||||||
Box,
|
Group,
|
||||||
Progress,
|
Progress,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconTrendingUp, IconTrendingDown, IconCurrency } from "@tabler/icons-react";
|
import {
|
||||||
import { BarChart } from "@mantine/charts";
|
IconCurrency,
|
||||||
|
IconTrendingDown,
|
||||||
|
IconTrendingUp,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
// Sample Data
|
// Sample Data
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
@@ -22,9 +26,7 @@ const kpiData = [
|
|||||||
title: "Total APBDes",
|
title: "Total APBDes",
|
||||||
value: "Rp 5.2M",
|
value: "Rp 5.2M",
|
||||||
sub: "Tahun 2025",
|
sub: "Tahun 2025",
|
||||||
icon: (
|
icon: <IconCurrency className="h-6 w-6 text-muted-foreground" />,
|
||||||
<IconCurrency className="h-6 w-6 text-muted-foreground" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -55,18 +57,14 @@ const kpiData = [
|
|||||||
sub: "Bulan ini",
|
sub: "Bulan ini",
|
||||||
delta: "+8%",
|
delta: "+8%",
|
||||||
deltaType: "positive",
|
deltaType: "positive",
|
||||||
icon: (
|
icon: <IconTrendingUp className="h-6 w-6 text-muted-foreground" />,
|
||||||
<IconTrendingUp className="h-6 w-6 text-muted-foreground" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
title: "Pengeluaran",
|
title: "Pengeluaran",
|
||||||
value: "Rp 520jt",
|
value: "Rp 520jt",
|
||||||
sub: "Bulan ini",
|
sub: "Bulan ini",
|
||||||
icon: (
|
icon: <IconTrendingDown className="h-6 w-6 text-muted-foreground" />,
|
||||||
<IconTrendingDown className="h-6 w-6 text-muted-foreground" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -125,7 +123,14 @@ const KeuanganAnggaran = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Group justify="space-between" align="flex-start" mb="xs">
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
<Text size="sm" fw={500} c="dimmed">
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
@@ -167,7 +172,13 @@ const KeuanganAnggaran = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Grafik Pemasukan vs Pengeluaran */}
|
{/* Grafik Pemasukan vs Pengeluaran */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Pemasukan vs Pengeluaran
|
Pemasukan vs Pengeluaran
|
||||||
</Title>
|
</Title>
|
||||||
@@ -176,8 +187,8 @@ const KeuanganAnggaran = () => {
|
|||||||
data={incomeExpenseData}
|
data={incomeExpenseData}
|
||||||
dataKey="month"
|
dataKey="month"
|
||||||
series={[
|
series={[
|
||||||
{ name: 'income', color: 'green', label: 'Pemasukan' },
|
{ name: "income", color: "green", label: "Pemasukan" },
|
||||||
{ name: 'expense', color: 'red', label: 'Pengeluaran' },
|
{ name: "expense", color: "red", label: "Pengeluaran" },
|
||||||
]}
|
]}
|
||||||
withLegend
|
withLegend
|
||||||
/>
|
/>
|
||||||
@@ -186,7 +197,13 @@ const KeuanganAnggaran = () => {
|
|||||||
|
|
||||||
{/* Alokasi Anggaran Per Sektor */}
|
{/* Alokasi Anggaran Per Sektor */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Alokasi Anggaran Per Sektor
|
Alokasi Anggaran Per Sektor
|
||||||
</Title>
|
</Title>
|
||||||
@@ -194,7 +211,9 @@ const KeuanganAnggaran = () => {
|
|||||||
h={300}
|
h={300}
|
||||||
data={allocationData}
|
data={allocationData}
|
||||||
dataKey="sector"
|
dataKey="sector"
|
||||||
series={[{ name: 'amount', color: 'darmasaba-navy', label: 'Jumlah' }]}
|
series={[
|
||||||
|
{ name: "amount", color: "darmasaba-navy", label: "Jumlah" },
|
||||||
|
]}
|
||||||
withLegend
|
withLegend
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
/>
|
/>
|
||||||
@@ -205,7 +224,13 @@ const KeuanganAnggaran = () => {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
{/* Dana Bantuan & Hibah */}
|
{/* Dana Bantuan & Hibah */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Dana Bantuan & Hibah
|
Dana Bantuan & Hibah
|
||||||
</Title>
|
</Title>
|
||||||
@@ -243,13 +268,21 @@ const KeuanganAnggaran = () => {
|
|||||||
|
|
||||||
{/* Laporan APBDes */}
|
{/* Laporan APBDes */}
|
||||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={3} fw={500} mb="md">
|
<Title order={3} fw={500} mb="md">
|
||||||
Laporan APBDes
|
Laporan APBDes
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Box mb="md">
|
<Box mb="md">
|
||||||
<Title order={4} mb="sm">Pendapatan</Title>
|
<Title order={4} mb="sm">
|
||||||
|
Pendapatan
|
||||||
|
</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{apbdReport.income.map((item, index) => (
|
{apbdReport.income.map((item, index) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={index} justify="space-between">
|
||||||
@@ -269,7 +302,9 @@ const KeuanganAnggaran = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={4} mb="sm">Belanja</Title>
|
<Title order={4} mb="sm">
|
||||||
|
Belanja
|
||||||
|
</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{apbdReport.expenses.map((item, index) => (
|
{apbdReport.expenses.map((item, index) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={index} justify="space-between">
|
||||||
@@ -288,11 +323,26 @@ const KeuanganAnggaran = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box mt="md" pt="md" style={{ borderTop: '1px solid var(--mantine-color-gray-3)' }}>
|
<Box
|
||||||
|
mt="md"
|
||||||
|
pt="md"
|
||||||
|
style={{ borderTop: "1px solid var(--mantine-color-gray-3)" }}
|
||||||
|
>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text fw={700}>Saldo:</Text>
|
<Text fw={700}>Saldo:</Text>
|
||||||
<Text fw={700} c={apbdReport.totalIncome > apbdReport.totalExpenses ? "green" : "red"}>
|
<Text
|
||||||
Rp {(apbdReport.totalIncome - apbdReport.totalExpenses).toLocaleString()}jt
|
fw={700}
|
||||||
|
c={
|
||||||
|
apbdReport.totalIncome > apbdReport.totalExpenses
|
||||||
|
? "green"
|
||||||
|
: "red"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Rp{" "}
|
||||||
|
{(
|
||||||
|
apbdReport.totalIncome - apbdReport.totalExpenses
|
||||||
|
).toLocaleString()}
|
||||||
|
jt
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,27 +1,38 @@
|
|||||||
import {
|
import {
|
||||||
Stack,
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
ActionIcon,
|
|
||||||
Progress as MantineProgress,
|
|
||||||
Box,
|
|
||||||
Badge as MantineBadge,
|
|
||||||
Card,
|
|
||||||
useMantineColorScheme,
|
|
||||||
ThemeIcon,
|
|
||||||
List,
|
List,
|
||||||
Divider,
|
Badge as MantineBadge,
|
||||||
Skeleton
|
Progress as MantineProgress,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import {
|
||||||
|
Bar,
|
||||||
|
BarChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Cell,
|
||||||
|
Pie,
|
||||||
|
PieChart,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Tooltip,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from "recharts";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
|
|
||||||
|
|
||||||
const KinerjaDivisi = () => {
|
const KinerjaDivisi = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Data for division progress chart
|
// Data for division progress chart
|
||||||
const divisionProgressData = [
|
const divisionProgressData = [
|
||||||
@@ -39,7 +50,7 @@ const KinerjaDivisi = () => {
|
|||||||
{ title: "Laporan Bulanan", status: "selesai" },
|
{ title: "Laporan Bulanan", status: "selesai" },
|
||||||
{ title: "Arsip Dokumen", status: "berjalan" },
|
{ title: "Arsip Dokumen", status: "berjalan" },
|
||||||
{ title: "Undangan Rapat", status: "tertunda" },
|
{ title: "Undangan Rapat", status: "tertunda" },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Keuangan",
|
name: "Keuangan",
|
||||||
@@ -47,7 +58,7 @@ const KinerjaDivisi = () => {
|
|||||||
{ title: "Laporan APBDes", status: "selesai" },
|
{ title: "Laporan APBDes", status: "selesai" },
|
||||||
{ title: "Verifikasi Dana", status: "tertunda" },
|
{ title: "Verifikasi Dana", status: "tertunda" },
|
||||||
{ title: "Pengeluaran Harian", status: "berjalan" },
|
{ title: "Pengeluaran Harian", status: "berjalan" },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Sosial",
|
name: "Sosial",
|
||||||
@@ -55,7 +66,7 @@ const KinerjaDivisi = () => {
|
|||||||
{ title: "Program Bantuan", status: "selesai" },
|
{ title: "Program Bantuan", status: "selesai" },
|
||||||
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
||||||
{ title: "Monitoring Stunting", status: "tertunda" },
|
{ title: "Monitoring Stunting", status: "tertunda" },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Humas",
|
name: "Humas",
|
||||||
@@ -63,7 +74,7 @@ const KinerjaDivisi = () => {
|
|||||||
{ title: "Publikasi Kegiatan", status: "selesai" },
|
{ title: "Publikasi Kegiatan", status: "selesai" },
|
||||||
{ title: "Koordinasi Media", status: "berjalan" },
|
{ title: "Koordinasi Media", status: "berjalan" },
|
||||||
{ title: "Laporan Kegiatan", status: "tertunda" },
|
{ title: "Laporan Kegiatan", status: "tertunda" },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -77,10 +88,30 @@ const KinerjaDivisi = () => {
|
|||||||
|
|
||||||
// Activity progress
|
// Activity progress
|
||||||
const activityProgress = [
|
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: "Pembangunan Jalan",
|
||||||
{ name: "Vaksinasi Massal", progress: 45, date: "20 Feb 2026", status: "berjalan" },
|
progress: 75,
|
||||||
{ name: "Festival Budaya", progress: 20, date: "5 Mar 2026", status: "berjalan" },
|
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
|
// Document statistics
|
||||||
@@ -97,19 +128,31 @@ const KinerjaDivisi = () => {
|
|||||||
{ name: "Dibatalkan", value: 2 },
|
{ name: "Dibatalkan", value: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const COLORS = ['#10B981', '#F59E0B', '#EF4444', '#6B7280'];
|
const COLORS = ["#10B981", "#F59E0B", "#EF4444", "#6B7280"];
|
||||||
const STATUS_COLORS: Record<string, string> = {
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
selesai: 'green',
|
selesai: "green",
|
||||||
berjalan: 'blue',
|
berjalan: "blue",
|
||||||
tertunda: 'red',
|
tertunda: "red",
|
||||||
proses: 'yellow'
|
proses: "yellow",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Discussion data
|
// Discussion data
|
||||||
const discussions = [
|
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: "Pembahasan APBDes 2026",
|
||||||
{ title: "Festival Budaya", sender: "Divisi Humas", timestamp: "1 hari yang lalu" },
|
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
|
// Today's agenda
|
||||||
@@ -121,32 +164,73 @@ const KinerjaDivisi = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Grafik Progres Tugas per Divisi */}
|
{/* Grafik Progres Tugas per Divisi */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} >
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Grafik Progres Tugas per Divisi
|
||||||
</Title>
|
</Title>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
<BarChart data={divisionProgressData}>
|
<BarChart data={divisionProgressData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} />
|
<CartesianGrid
|
||||||
|
strokeDasharray="3 3"
|
||||||
|
vertical={false}
|
||||||
|
stroke={dark ? "#141D34" : "white"}
|
||||||
|
/>
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="name"
|
dataKey="name"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={dark
|
contentStyle={
|
||||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
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]}
|
||||||
/>
|
/>
|
||||||
<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>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -155,17 +239,26 @@ const KinerjaDivisi = () => {
|
|||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{divisionTasks.map((division, index) => (
|
{divisionTasks.map((division, index) => (
|
||||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
<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%">
|
<Card
|
||||||
<Title order={4} mb="sm" c={dark ? 'white' : 'darmasaba-navy'}>
|
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}
|
{division.name}
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{division.tasks.map((task, taskIndex) => (
|
{division.tasks.map((task, taskIndex) => (
|
||||||
<Box key={taskIndex}>
|
<Box key={taskIndex}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{task.title}</Text>
|
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
{task.title}
|
||||||
|
</Text>
|
||||||
<MantineBadge
|
<MantineBadge
|
||||||
color={STATUS_COLORS[task.status] || 'gray'}
|
color={STATUS_COLORS[task.status] || "gray"}
|
||||||
variant="light"
|
variant="light"
|
||||||
>
|
>
|
||||||
{task.status}
|
{task.status}
|
||||||
@@ -180,17 +273,33 @@ const KinerjaDivisi = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Arsip Digital Perangkat Desa */}
|
{/* Arsip Digital Perangkat Desa */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Arsip Digital Perangkat Desa
|
||||||
</Title>
|
</Title>
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{archiveItems.map((item, index) => (
|
{archiveItems.map((item, index) => (
|
||||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
<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" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{item.name}</Text>
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={700}>{item.count}</Text>
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={700}>
|
||||||
|
{item.count}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
@@ -199,17 +308,32 @@ const KinerjaDivisi = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Kartu Progres Kegiatan */}
|
{/* Kartu Progres Kegiatan */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Progres Kegiatan / Program
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{activityProgress.map((activity, index) => (
|
{activityProgress.map((activity, index) => (
|
||||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
<Card
|
||||||
|
key={index}
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
>
|
||||||
<Group justify="space-between" mb="sm">
|
<Group justify="space-between" mb="sm">
|
||||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{activity.name}</Text>
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
|
{activity.name}
|
||||||
|
</Text>
|
||||||
<MantineBadge
|
<MantineBadge
|
||||||
color={STATUS_COLORS[activity.status] || 'gray'}
|
color={STATUS_COLORS[activity.status] || "gray"}
|
||||||
variant="light"
|
variant="light"
|
||||||
>
|
>
|
||||||
{activity.status}
|
{activity.status}
|
||||||
@@ -223,9 +347,13 @@ const KinerjaDivisi = () => {
|
|||||||
color={activity.progress === 100 ? "green" : "blue"}
|
color={activity.progress === 100 ? "green" : "blue"}
|
||||||
w="calc(100% - 80px)"
|
w="calc(100% - 80px)"
|
||||||
/>
|
/>
|
||||||
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{activity.progress}%</Text>
|
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
{activity.progress}%
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="sm" c="dimmed" mt="sm">{activity.date}</Text>
|
<Text size="sm" c="dimmed" mt="sm">
|
||||||
|
{activity.date}
|
||||||
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -234,38 +362,75 @@ const KinerjaDivisi = () => {
|
|||||||
{/* Statistik Dokumen & Progres Kegiatan */}
|
{/* Statistik Dokumen & Progres Kegiatan */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Jumlah Dokumen
|
||||||
</Title>
|
</Title>
|
||||||
<ResponsiveContainer width="100%" height={200}>
|
<ResponsiveContainer width="100%" height={200}>
|
||||||
<BarChart data={documentStats}>
|
<BarChart data={documentStats}>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} />
|
<CartesianGrid
|
||||||
|
strokeDasharray="3 3"
|
||||||
|
vertical={false}
|
||||||
|
stroke={dark ? "#141D34" : "white"}
|
||||||
|
/>
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="name"
|
dataKey="name"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={dark
|
contentStyle={
|
||||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
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]}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="value" fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"} radius={[4, 4, 0, 0]} />
|
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Progres Kegiatan
|
||||||
</Title>
|
</Title>
|
||||||
<ResponsiveContainer width="100%" height={200}>
|
<ResponsiveContainer width="100%" height={200}>
|
||||||
@@ -278,16 +443,26 @@ const KinerjaDivisi = () => {
|
|||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
label={({ name, percent }) => `${name}: ${percent ? (percent * 100).toFixed(0) : '0'}%`}
|
label={({ name, percent }) =>
|
||||||
|
`${name}: ${percent ? (percent * 100).toFixed(0) : "0"}%`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{activityProgressStats.map((entry, index) => (
|
{activityProgressStats.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={COLORS[index % COLORS.length]}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={dark
|
contentStyle={
|
||||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
dark
|
||||||
: {}}
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@@ -296,26 +471,51 @@ const KinerjaDivisi = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Diskusi Internal */}
|
{/* Diskusi Internal */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Diskusi Internal
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{discussions.map((discussion, index) => (
|
{discussions.map((discussion, index) => (
|
||||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
<Card
|
||||||
|
key={index}
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{discussion.title}</Text>
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
<Text size="sm" c="dimmed">{discussion.timestamp}</Text>
|
{discussion.title}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{discussion.timestamp}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="sm" c="dimmed">{discussion.sender}</Text>
|
<Text size="sm" c="dimmed">
|
||||||
|
{discussion.sender}
|
||||||
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Agenda / Acara Hari Ini */}
|
{/* Agenda / Acara Hari Ini */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
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
|
Agenda / Acara Hari Ini
|
||||||
</Title>
|
</Title>
|
||||||
{todayAgenda.length > 0 ? (
|
{todayAgenda.length > 0 ? (
|
||||||
@@ -326,7 +526,9 @@ const KinerjaDivisi = () => {
|
|||||||
<Text c="dimmed">{agenda.time}</Text>
|
<Text c="dimmed">{agenda.time}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider orientation="vertical" mx="sm" />
|
<Divider orientation="vertical" mx="sm" />
|
||||||
<Text c={dark ? 'white' : 'darmasaba-navy'}>{agenda.event}</Text>
|
<Text c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
{agenda.event}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,38 +1,54 @@
|
|||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
Divider,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
TextInput,
|
|
||||||
Textarea,
|
|
||||||
Select,
|
|
||||||
Table,
|
|
||||||
Badge,
|
|
||||||
Stack,
|
|
||||||
useMantineColorScheme,
|
|
||||||
List,
|
List,
|
||||||
Divider,
|
Select,
|
||||||
ActionIcon,
|
Stack,
|
||||||
Box
|
Table,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconMessage, IconAlertTriangle, IconClock, IconCheck, IconChevronRight } from "@tabler/icons-react";
|
import {
|
||||||
import { Line, LineChart, Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
|
IconAlertTriangle,
|
||||||
|
IconCheck,
|
||||||
|
IconChevronRight,
|
||||||
|
IconClock,
|
||||||
|
IconMessage,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import type React from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Bar,
|
||||||
|
BarChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Line,
|
||||||
|
LineChart,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Tooltip,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from "recharts";
|
||||||
|
|
||||||
const PengaduanLayananPublik = () => {
|
const PengaduanLayananPublik = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Summary data
|
// Summary data
|
||||||
const summaryData = {
|
const summaryData = {
|
||||||
total: 42,
|
total: 42,
|
||||||
baru: 14,
|
baru: 14,
|
||||||
diproses: 14,
|
diproses: 14,
|
||||||
selesai: 14
|
selesai: 14,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tren pengaduan data
|
// Tren pengaduan data
|
||||||
@@ -42,7 +58,7 @@ const PengaduanLayananPublik = () => {
|
|||||||
{ bulan: "Mar", jumlah: 42 },
|
{ bulan: "Mar", jumlah: 42 },
|
||||||
{ bulan: "Apr", jumlah: 38 },
|
{ bulan: "Apr", jumlah: 38 },
|
||||||
{ bulan: "Mei", jumlah: 45 },
|
{ bulan: "Mei", jumlah: 45 },
|
||||||
{ bulan: "Jun", jumlah: 42 }
|
{ bulan: "Jun", jumlah: 42 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Surat terbanyak data
|
// Surat terbanyak data
|
||||||
@@ -51,24 +67,65 @@ const PengaduanLayananPublik = () => {
|
|||||||
{ jenis: "KK", jumlah: 18 },
|
{ jenis: "KK", jumlah: 18 },
|
||||||
{ jenis: "Domisili", jumlah: 15 },
|
{ jenis: "Domisili", jumlah: 15 },
|
||||||
{ jenis: "Usaha", jumlah: 12 },
|
{ jenis: "Usaha", jumlah: 12 },
|
||||||
{ jenis: "Lainnya", jumlah: 8 }
|
{ jenis: "Lainnya", jumlah: 8 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Pengajuan terbaru data
|
// Pengajuan terbaru data
|
||||||
const pengajuanTerbaru = [
|
const pengajuanTerbaru = [
|
||||||
{ nama: "Budi Santoso", jenis: "Ketertiban Umum", waktu: "2 jam yang lalu", status: "baru" },
|
{
|
||||||
{ nama: "Siti Rahayu", jenis: "Pelayanan Kesehatan", waktu: "5 jam yang lalu", status: "diproses" },
|
nama: "Budi Santoso",
|
||||||
{ nama: "Ahmad Fauzi", jenis: "Infrastruktur", waktu: "1 hari yang lalu", status: "selesai" },
|
jenis: "Ketertiban Umum",
|
||||||
{ nama: "Dewi Lestari", jenis: "Administrasi", waktu: "1 hari yang lalu", status: "baru" },
|
waktu: "2 jam yang lalu",
|
||||||
{ nama: "Joko Widodo", jenis: "Keamanan", waktu: "2 hari yang lalu", status: "diproses" }
|
status: "baru",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Siti Rahayu",
|
||||||
|
jenis: "Pelayanan Kesehatan",
|
||||||
|
waktu: "5 jam yang lalu",
|
||||||
|
status: "diproses",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Ahmad Fauzi",
|
||||||
|
jenis: "Infrastruktur",
|
||||||
|
waktu: "1 hari yang lalu",
|
||||||
|
status: "selesai",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Dewi Lestari",
|
||||||
|
jenis: "Administrasi",
|
||||||
|
waktu: "1 hari yang lalu",
|
||||||
|
status: "baru",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Joko Widodo",
|
||||||
|
jenis: "Keamanan",
|
||||||
|
waktu: "2 hari yang lalu",
|
||||||
|
status: "diproses",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ide inovatif data
|
// Ide inovatif data
|
||||||
const ideInovatif = [
|
const ideInovatif = [
|
||||||
{ nama: "Andi Prasetyo", judul: "Penerapan Smart Village", kategori: "Teknologi" },
|
{
|
||||||
{ nama: "Rina Kusuma", judul: "Program Ekowisata Desa", kategori: "Ekonomi" },
|
nama: "Andi Prasetyo",
|
||||||
{ nama: "Bambang Suryono", judul: "Peningkatan Sanitasi", kategori: "Kesehatan" },
|
judul: "Penerapan Smart Village",
|
||||||
{ nama: "Lina Marlina", judul: "Pusat Kreatif Anak Muda", kategori: "Pendidikan" }
|
kategori: "Teknologi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Rina Kusuma",
|
||||||
|
judul: "Program Ekowisata Desa",
|
||||||
|
kategori: "Ekonomi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Bambang Suryono",
|
||||||
|
judul: "Peningkatan Sanitasi",
|
||||||
|
kategori: "Kesehatan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Lina Marlina",
|
||||||
|
judul: "Pusat Kreatif Anak Muda",
|
||||||
|
kategori: "Pendidikan",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<"complaints" | "services">(
|
const [activeTab, setActiveTab] = useState<"complaints" | "services">(
|
||||||
@@ -229,10 +286,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
// Status badge color mapping
|
// Status badge color mapping
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'baru': return 'red';
|
case "baru":
|
||||||
case 'diproses': return 'yellow';
|
return "red";
|
||||||
case 'selesai': return 'green';
|
case "diproses":
|
||||||
default: return 'gray';
|
return "yellow";
|
||||||
|
case "selesai":
|
||||||
|
return "green";
|
||||||
|
default:
|
||||||
|
return "gray";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,7 +304,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
{/* Summary Cards */}
|
{/* Summary Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
@@ -266,7 +334,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
@@ -276,12 +351,7 @@ const PengaduanLayananPublik = () => {
|
|||||||
{summaryData.baru}
|
{summaryData.baru}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Badge
|
<Badge variant="light" color="red" p={8} radius="md">
|
||||||
variant="light"
|
|
||||||
color="red"
|
|
||||||
p={8}
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<IconAlertTriangle size={20} />
|
<IconAlertTriangle size={20} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -289,7 +359,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
@@ -299,12 +376,7 @@ const PengaduanLayananPublik = () => {
|
|||||||
{summaryData.diproses}
|
{summaryData.diproses}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Badge
|
<Badge variant="light" color="yellow" p={8} radius="md">
|
||||||
variant="light"
|
|
||||||
color="yellow"
|
|
||||||
p={8}
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<IconClock size={20} />
|
<IconClock size={20} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -312,7 +384,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
@@ -322,12 +401,7 @@ const PengaduanLayananPublik = () => {
|
|||||||
{summaryData.selesai}
|
{summaryData.selesai}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Badge
|
<Badge variant="light" color="green" p={8} radius="md">
|
||||||
variant="light"
|
|
||||||
color="green"
|
|
||||||
p={8}
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<IconCheck size={20} />
|
<IconCheck size={20} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -336,7 +410,13 @@ const PengaduanLayananPublik = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Grafik Tren Pengaduan */}
|
{/* Grafik Tren Pengaduan */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} >
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
Grafik Tren Pengaduan
|
Grafik Tren Pengaduan
|
||||||
</Title>
|
</Title>
|
||||||
@@ -345,31 +425,58 @@ const PengaduanLayananPublik = () => {
|
|||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
vertical={false}
|
vertical={false}
|
||||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
stroke={
|
||||||
|
dark
|
||||||
|
? "var(--mantine-color-gray-7)"
|
||||||
|
: "var(--mantine-color-gray-3)"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="bulan"
|
dataKey="bulan"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={dark
|
contentStyle={
|
||||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
dark
|
||||||
: {}}
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="jumlah"
|
dataKey="jumlah"
|
||||||
stroke={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
stroke={
|
||||||
|
dark
|
||||||
|
? "var(--mantine-color-blue-6)"
|
||||||
|
: "var(--mantine-color-blue-filled)"
|
||||||
|
}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={{ stroke: dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)", strokeWidth: 2, r: 4 }}
|
dot={{
|
||||||
activeDot={{ r: 6, stroke: '#fff', strokeWidth: 2 }}
|
stroke: dark
|
||||||
|
? "var(--mantine-color-blue-6)"
|
||||||
|
: "var(--mantine-color-blue-filled)",
|
||||||
|
strokeWidth: 2,
|
||||||
|
r: 4,
|
||||||
|
}}
|
||||||
|
activeDot={{ r: 6, stroke: "#fff", strokeWidth: 2 }}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@@ -379,7 +486,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Surat Terbanyak */}
|
{/* Surat Terbanyak */}
|
||||||
<GridCol span={{ base: 12, lg: 4 }}>
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
Surat Terbanyak
|
Surat Terbanyak
|
||||||
</Title>
|
</Title>
|
||||||
@@ -388,30 +502,51 @@ const PengaduanLayananPublik = () => {
|
|||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
horizontal={false}
|
horizontal={false}
|
||||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
stroke={
|
||||||
|
dark
|
||||||
|
? "var(--mantine-color-gray-7)"
|
||||||
|
: "var(--mantine-color-gray-3)"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="jumlah"
|
dataKey="jumlah"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
dataKey="jenis"
|
dataKey="jenis"
|
||||||
type="category"
|
type="category"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
width={80}
|
width={80}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={dark
|
contentStyle={
|
||||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
dark
|
||||||
: {}}
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Bar
|
<Bar
|
||||||
dataKey="jumlah"
|
dataKey="jumlah"
|
||||||
fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
fill={
|
||||||
|
dark
|
||||||
|
? "var(--mantine-color-blue-6)"
|
||||||
|
: "var(--mantine-color-blue-filled)"
|
||||||
|
}
|
||||||
radius={[0, 4, 4, 0]}
|
radius={[0, 4, 4, 0]}
|
||||||
/>
|
/>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
@@ -421,7 +556,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
|
|
||||||
{/* Pengajuan Terbaru */}
|
{/* Pengajuan Terbaru */}
|
||||||
<GridCol span={{ base: 12, lg: 4 }}>
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
Pengajuan Terbaru
|
Pengajuan Terbaru
|
||||||
</Title>
|
</Title>
|
||||||
@@ -429,14 +571,23 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Box key={index}>
|
<Box key={index}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.jenis}</Text>
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{item.jenis}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack gap={0} align="flex-end">
|
<Stack gap={0} align="flex-end">
|
||||||
<Badge color={getStatusColor(item.status)} variant="light">
|
<Badge
|
||||||
|
color={getStatusColor(item.status)}
|
||||||
|
variant="light"
|
||||||
|
>
|
||||||
{item.status}
|
{item.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Text size="xs" c={dark ? "dark.4" : "dimmed"}>{item.waktu}</Text>
|
<Text size="xs" c={dark ? "dark.4" : "dimmed"}>
|
||||||
|
{item.waktu}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider my="sm" />
|
<Divider my="sm" />
|
||||||
@@ -447,7 +598,14 @@ const PengaduanLayananPublik = () => {
|
|||||||
|
|
||||||
{/* Ajuan Ide Inovatif */}
|
{/* Ajuan Ide Inovatif */}
|
||||||
<GridCol span={{ base: 12, lg: 4 }}>
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
Ajuan Ide Inovatif
|
Ajuan Ide Inovatif
|
||||||
</Title>
|
</Title>
|
||||||
@@ -455,8 +613,12 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Box key={index}>
|
<Box key={index}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.judul}</Text>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.nama}</Text>
|
{item.judul}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Group>
|
<Group>
|
||||||
<Badge color="blue" variant="light">
|
<Badge color="blue" variant="light">
|
||||||
@@ -478,9 +640,18 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Complaint Submission Form */}
|
{/* Complaint Submission Form */}
|
||||||
<GridCol span={{ base: 12, lg: 4 }}>
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<Card p="md" withBorder radius="md" h="100%" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
p="md"
|
||||||
|
withBorder
|
||||||
|
radius="md"
|
||||||
|
h="100%"
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Card.Section withBorder inheritPadding py="xs">
|
<Card.Section withBorder inheritPadding py="xs">
|
||||||
<Title order={3} py="xs">Ajukan Pengaduan</Title>
|
<Title order={3} py="xs">
|
||||||
|
Ajukan Pengaduan
|
||||||
|
</Title>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<form onSubmit={handleSubmitComplaint}>
|
<form onSubmit={handleSubmitComplaint}>
|
||||||
@@ -537,24 +708,39 @@ const PengaduanLayananPublik = () => {
|
|||||||
|
|
||||||
{/* Complaints List */}
|
{/* Complaints List */}
|
||||||
<GridCol span={{ base: 12, lg: 8 }}>
|
<GridCol span={{ base: 12, lg: 8 }}>
|
||||||
<Card withBorder radius="md" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
|
withBorder
|
||||||
|
radius="md"
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
<Card.Section withBorder inheritPadding py="xs">
|
<Card.Section withBorder inheritPadding py="xs">
|
||||||
<Title order={3} py="xs">Daftar Pengaduan</Title>
|
<Title order={3} py="xs">
|
||||||
|
Daftar Pengaduan
|
||||||
|
</Title>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section py="md" px="xs">
|
<Card.Section py="md" px="xs">
|
||||||
<Table withColumnBorders>
|
<Table withColumnBorders>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Judul</Text></Table.Th>
|
<Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Kategori</Text></Table.Th>
|
<Text c={dark ? "white" : "dark.3"}>Judul</Text>
|
||||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Status</Text></Table.Th>
|
</Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Prioritas</Text></Table.Th>
|
<Table.Th>
|
||||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Tanggal</Text></Table.Th>
|
<Text c={dark ? "white" : "dark.3"}>Kategori</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dark.3"}>Status</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dark.3"}>Prioritas</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dark.3"}>Tanggal</Text>
|
||||||
|
</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>{complaintRows}</Table.Tbody>
|
||||||
{complaintRows}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
</Table>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -565,15 +751,19 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Card withBorder radius="md">
|
<Card withBorder radius="md">
|
||||||
<Card.Section withBorder inheritPadding py="xs">
|
<Card.Section withBorder inheritPadding py="xs">
|
||||||
<Title order={3} py="xs">Layanan Publik Tersedia</Title>
|
<Title order={3} py="xs">
|
||||||
|
Layanan Publik Tersedia
|
||||||
|
</Title>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section pt="md">
|
<Card.Section pt="md">
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{services.map((service) => (
|
{services.map((service) => (
|
||||||
<GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}>
|
<GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}>
|
||||||
<Card withBorder radius="md" h="100%">
|
<Card withBorder radius="md" h="100%">
|
||||||
<Title order={4} mb="sm">{service.name}</Title>
|
<Title order={4} mb="sm">
|
||||||
<Text size="sm" c={dark ? "white" : "dark.3" } mb="md">
|
{service.name}
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c={dark ? "white" : "dark.3"} mb="md">
|
||||||
{service.description}
|
{service.description}
|
||||||
</Text>
|
</Text>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
@@ -589,11 +779,11 @@ const PengaduanLayananPublik = () => {
|
|||||||
>
|
>
|
||||||
{service.status}
|
{service.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Text size="sm" c={dark ? "white" : "dark.3" }>
|
<Text size="sm" c={dark ? "white" : "dark.3"}>
|
||||||
{service.category}
|
{service.category}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="xs" c={dark ? "white" : "dark.3" } mt="sm">
|
<Text size="xs" c={dark ? "white" : "dark.3"} mt="sm">
|
||||||
Terakhir diperbarui: {service.lastUpdated}
|
Terakhir diperbarui: {service.lastUpdated}
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -605,13 +795,17 @@ const PengaduanLayananPublik = () => {
|
|||||||
|
|
||||||
<Card withBorder radius="md">
|
<Card withBorder radius="md">
|
||||||
<Card.Section withBorder inheritPadding py="xs">
|
<Card.Section withBorder inheritPadding py="xs">
|
||||||
<Title order={3} py="xs">Statistik Layanan</Title>
|
<Title order={3} py="xs">
|
||||||
|
Statistik Layanan
|
||||||
|
</Title>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section pt="md">
|
<Card.Section pt="md">
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
<GridCol span={{ base: 12, md: 4 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||||
<Title order={4} mb="xs">Jumlah Layanan Tersedia</Title>
|
<Title order={4} mb="xs">
|
||||||
|
Jumlah Layanan Tersedia
|
||||||
|
</Title>
|
||||||
<Text size="xl" fw={700} c="darmasaba-blue">
|
<Text size="xl" fw={700} c="darmasaba-blue">
|
||||||
12
|
12
|
||||||
</Text>
|
</Text>
|
||||||
@@ -619,7 +813,9 @@ const PengaduanLayananPublik = () => {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 4 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||||
<Title order={4} mb="xs">Layanan Terpopuler</Title>
|
<Title order={4} mb="xs">
|
||||||
|
Layanan Terpopuler
|
||||||
|
</Title>
|
||||||
<Text size="xl" fw={700} c="darmasaba-success">
|
<Text size="xl" fw={700} c="darmasaba-success">
|
||||||
4
|
4
|
||||||
</Text>
|
</Text>
|
||||||
@@ -627,7 +823,9 @@ const PengaduanLayananPublik = () => {
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 4 }}>
|
<GridCol span={{ base: 12, md: 4 }}>
|
||||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||||
<Title order={4} mb="xs">Permintaan Baru</Title>
|
<Title order={4} mb="xs">
|
||||||
|
Permintaan Baru
|
||||||
|
</Title>
|
||||||
<Text size="xl" fw={700} c="darmasaba-warning">
|
<Text size="xl" fw={700} c="darmasaba-warning">
|
||||||
23
|
23
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,125 +1,190 @@
|
|||||||
import { Card, Title, Text, Space, Button, Group, Alert, Table, ActionIcon, Modal, TextInput, Select, useMantineColorScheme } from '@mantine/core';
|
import {
|
||||||
import { IconInfoCircle, IconUserPlus, IconTrash, IconEdit, IconUser } from '@tabler/icons-react';
|
ActionIcon,
|
||||||
import { useState } from 'react';
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconEdit,
|
||||||
|
IconInfoCircle,
|
||||||
|
IconTrash,
|
||||||
|
IconUser,
|
||||||
|
IconUserPlus,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const AksesDanTimSettings = () => {
|
const AksesDanTimSettings = () => {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Sample team members data
|
// Sample team members data
|
||||||
const teamMembers = [
|
const teamMembers = [
|
||||||
{ id: 1, name: 'Admin Utama', email: 'admin@desa.go.id', role: 'Administrator', status: 'Aktif' },
|
{
|
||||||
{ id: 2, name: 'Operator Desa', email: 'operator@desa.go.id', role: 'Operator', status: 'Aktif' },
|
id: 1,
|
||||||
{ id: 3, name: 'Staff Keuangan', email: 'keuangan@desa.go.id', role: 'Keuangan', status: 'Aktif' },
|
name: "Admin Utama",
|
||||||
{ id: 4, name: 'Staff Umum', email: 'umum@desa.go.id', role: 'Umum', status: 'Nonaktif' },
|
email: "admin@desa.go.id",
|
||||||
];
|
role: "Administrator",
|
||||||
|
status: "Aktif",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Operator Desa",
|
||||||
|
email: "operator@desa.go.id",
|
||||||
|
role: "Operator",
|
||||||
|
status: "Aktif",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Staff Keuangan",
|
||||||
|
email: "keuangan@desa.go.id",
|
||||||
|
role: "Keuangan",
|
||||||
|
status: "Aktif",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Staff Umum",
|
||||||
|
email: "umum@desa.go.id",
|
||||||
|
role: "Umum",
|
||||||
|
status: "Nonaktif",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const roles = [
|
const roles = [
|
||||||
{ value: 'administrator', label: 'Administrator' },
|
{ value: "administrator", label: "Administrator" },
|
||||||
{ value: 'operator', label: 'Operator' },
|
{ value: "operator", label: "Operator" },
|
||||||
{ value: 'keuangan', label: 'Keuangan' },
|
{ value: "keuangan", label: "Keuangan" },
|
||||||
{ value: 'umum', label: 'Umum' },
|
{ value: "umum", label: "Umum" },
|
||||||
{ value: 'keamanan', label: 'Keamanan' },
|
{ value: "keamanan", label: "Keamanan" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Modal
|
withBorder
|
||||||
opened={opened}
|
radius="md"
|
||||||
onClose={() => setOpened(false)}
|
p="xl"
|
||||||
title="Tambah Anggota Tim"
|
bg={dark ? "#141D34" : "white"}
|
||||||
size="lg"
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
>
|
>
|
||||||
<TextInput
|
<Modal
|
||||||
label="Nama Lengkap"
|
opened={opened}
|
||||||
placeholder="Masukkan nama lengkap anggota tim"
|
onClose={() => setOpened(false)}
|
||||||
mb="md"
|
title="Tambah Anggota Tim"
|
||||||
/>
|
size="lg"
|
||||||
<TextInput
|
>
|
||||||
label="Alamat Email"
|
<TextInput
|
||||||
placeholder="Masukkan alamat email"
|
label="Nama Lengkap"
|
||||||
mb="md"
|
placeholder="Masukkan nama lengkap anggota tim"
|
||||||
/>
|
mb="md"
|
||||||
<Select
|
/>
|
||||||
label="Peran"
|
<TextInput
|
||||||
placeholder="Pilih peran anggota tim"
|
label="Alamat Email"
|
||||||
data={roles}
|
placeholder="Masukkan alamat email"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
<Group justify="flex-end" mt="xl">
|
<Select
|
||||||
<Button variant="outline" onClick={() => setOpened(false)}>Batal</Button>
|
label="Peran"
|
||||||
<Button>Undang Anggota</Button>
|
placeholder="Pilih peran anggota tim"
|
||||||
</Group>
|
data={roles}
|
||||||
</Modal>
|
mb="md"
|
||||||
|
/>
|
||||||
|
<Group justify="flex-end" mt="xl">
|
||||||
|
<Button variant="outline" onClick={() => setOpened(false)}>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button>Undang Anggota</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Title order={2} mb="lg">Akses & Tim</Title>
|
<Title order={2} mb="lg">
|
||||||
<Text color="dimmed" mb="xl">Kelola akses dan anggota tim Anda</Text>
|
Akses & Tim
|
||||||
|
</Title>
|
||||||
|
<Text color="dimmed" mb="xl">
|
||||||
|
Kelola akses dan anggota tim Anda
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
|
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Anggota Tim</Title>
|
<Title order={4}>Anggota Tim</Title>
|
||||||
<Button leftSection={<IconUserPlus size={16} />} onClick={() => setOpened(true)}>
|
<Button
|
||||||
Tambah Anggota
|
leftSection={<IconUserPlus size={16} />}
|
||||||
</Button>
|
onClick={() => setOpened(true)}
|
||||||
</Group>
|
>
|
||||||
|
Tambah Anggota
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>Nama</Table.Th>
|
<Table.Th>Nama</Table.Th>
|
||||||
<Table.Th>Email</Table.Th>
|
<Table.Th>Email</Table.Th>
|
||||||
<Table.Th>Peran</Table.Th>
|
<Table.Th>Peran</Table.Th>
|
||||||
<Table.Th>Status</Table.Th>
|
<Table.Th>Status</Table.Th>
|
||||||
<Table.Th>Aksi</Table.Th>
|
<Table.Th>Aksi</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{teamMembers.map((member) => (
|
{teamMembers.map((member) => (
|
||||||
<Table.Tr key={member.id}>
|
<Table.Tr key={member.id}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<IconUser size={20} />
|
<IconUser size={20} />
|
||||||
<Text>{member.name}</Text>
|
<Text>{member.name}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>{member.email}</Table.Td>
|
<Table.Td>{member.email}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text fw={500}>{member.role}</Text>
|
<Text fw={500}>{member.role}</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text c={member.status === 'Aktif' ? 'green' : 'red'} fw={500}>
|
<Text c={member.status === "Aktif" ? "green" : "red"} fw={500}>
|
||||||
{member.status}
|
{member.status}
|
||||||
</Text>
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon variant="subtle" color="blue">
|
<ActionIcon variant="subtle" color="blue">
|
||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon variant="subtle" color="red">
|
<ActionIcon variant="subtle" color="red">
|
||||||
<IconTrash size={16} />
|
<IconTrash size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
|
|
||||||
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md">
|
<Alert
|
||||||
Administrator memiliki akses penuh ke semua fitur. Peran lainnya memiliki akses terbatas sesuai kebutuhan.
|
icon={<IconInfoCircle size={16} />}
|
||||||
</Alert>
|
title="Informasi"
|
||||||
|
color="blue"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Administrator memiliki akses penuh ke semua fitur. Peran lainnya
|
||||||
|
memiliki akses terbatas sesuai kebutuhan.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Group justify="flex-end" mt="xl">
|
<Group justify="flex-end" mt="xl">
|
||||||
<Button variant="outline">Batal</Button>
|
<Button variant="outline">Batal</Button>
|
||||||
<Button>Simpan Perubahan</Button>
|
<Button>Simpan Perubahan</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AksesDanTimSettings;
|
export default AksesDanTimSettings;
|
||||||
@@ -1,57 +1,90 @@
|
|||||||
import { Card, Title, Text, Space, Button, Group, Alert, PasswordInput, Switch, useMantineColorScheme } from '@mantine/core';
|
import {
|
||||||
import { IconInfoCircle, IconLock } from '@tabler/icons-react';
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
PasswordInput,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconInfoCircle, IconLock } from "@tabler/icons-react";
|
||||||
|
|
||||||
const KeamananSettings = () => {
|
const KeamananSettings = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
return (
|
return (
|
||||||
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={2} mb="lg">Pengaturan Keamanan</Title>
|
withBorder
|
||||||
<Text color="dimmed" mb="xl">Kelola keamanan akun Anda</Text>
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={2} mb="lg">
|
||||||
|
Pengaturan Keamanan
|
||||||
|
</Title>
|
||||||
|
<Text color="dimmed" mb="xl">
|
||||||
|
Kelola keamanan akun Anda
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
|
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
label="Kata Sandi Saat Ini"
|
label="Kata Sandi Saat Ini"
|
||||||
placeholder="Masukkan kata sandi saat ini"
|
placeholder="Masukkan kata sandi saat ini"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
label="Kata Sandi Baru"
|
label="Kata Sandi Baru"
|
||||||
placeholder="Masukkan kata sandi baru"
|
placeholder="Masukkan kata sandi baru"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
label="Konfirmasi Kata Sandi Baru"
|
label="Konfirmasi Kata Sandi Baru"
|
||||||
placeholder="Konfirmasi kata sandi baru"
|
placeholder="Konfirmasi kata sandi baru"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Switch label="Verifikasi Dua Langkah" />
|
<Switch label="Verifikasi Dua Langkah" />
|
||||||
<Switch label="Login Otentikasi Aplikasi" />
|
<Switch label="Login Otentikasi Aplikasi" />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<Alert icon={<IconLock size={16} />} title="Keamanan" color="orange" mb="md">
|
<Alert
|
||||||
Gunakan kata sandi yang kuat dan unik. Hindari menggunakan kata sandi yang sama di banyak layanan.
|
icon={<IconLock size={16} />}
|
||||||
</Alert>
|
title="Keamanan"
|
||||||
|
color="orange"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Gunakan kata sandi yang kuat dan unik. Hindari menggunakan kata sandi
|
||||||
|
yang sama di banyak layanan.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md">
|
<Alert
|
||||||
Setelah mengganti kata sandi, Anda akan diminta logout dari semua perangkat.
|
icon={<IconInfoCircle size={16} />}
|
||||||
</Alert>
|
title="Informasi"
|
||||||
|
color="blue"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Setelah mengganti kata sandi, Anda akan diminta logout dari semua
|
||||||
|
perangkat.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Group justify="flex-end" mt="xl">
|
<Group justify="flex-end" mt="xl">
|
||||||
<Button variant="outline">Batal</Button>
|
<Button variant="outline">Batal</Button>
|
||||||
<Button>Perbarui Kata Sandi</Button>
|
<Button>Perbarui Kata Sandi</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KeamananSettings;
|
export default KeamananSettings;
|
||||||
@@ -1,55 +1,86 @@
|
|||||||
import { Card, Title, Text, Space, Switch, Group, Alert, Checkbox, Button, useMantineColorScheme } from '@mantine/core';
|
import {
|
||||||
import { IconInfoCircle } from '@tabler/icons-react';
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
Group,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
const NotifikasiSettings = () => {
|
const NotifikasiSettings = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
return (
|
return (
|
||||||
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={2} mb="lg">Pengaturan Notifikasi</Title>
|
withBorder
|
||||||
<Text color="dimmed" mb="xl">Kelola preferensi notifikasi Anda</Text>
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={2} mb="lg">
|
||||||
|
Pengaturan Notifikasi
|
||||||
|
</Title>
|
||||||
|
<Text color="dimmed" mb="xl">
|
||||||
|
Kelola preferensi notifikasi Anda
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
|
|
||||||
<Checkbox.Group defaultValue={['email', 'push']} mb="md">
|
<Checkbox.Group defaultValue={["email", "push"]} mb="md">
|
||||||
<Title order={4} mb="sm">Metode Notifikasi</Title>
|
<Title order={4} mb="sm">
|
||||||
<Group>
|
Metode Notifikasi
|
||||||
<Checkbox value="email" label="Email" />
|
</Title>
|
||||||
<Checkbox value="push" label="Notifikasi Push" />
|
<Group>
|
||||||
<Checkbox value="sms" label="SMS" />
|
<Checkbox value="email" label="Email" />
|
||||||
</Group>
|
<Checkbox value="push" label="Notifikasi Push" />
|
||||||
</Checkbox.Group>
|
<Checkbox value="sms" label="SMS" />
|
||||||
|
</Group>
|
||||||
|
</Checkbox.Group>
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Switch label="Notifikasi Email" defaultChecked />
|
<Switch label="Notifikasi Email" defaultChecked />
|
||||||
<Switch label="Notifikasi Push" defaultChecked />
|
<Switch label="Notifikasi Push" defaultChecked />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<Title order={4} mb="sm">Jenis Notifikasi</Title>
|
<Title order={4} mb="sm">
|
||||||
<Group align="start">
|
Jenis Notifikasi
|
||||||
<Switch label="Pengaduan Baru" defaultChecked />
|
</Title>
|
||||||
<Switch label="Update Status Pengaduan" defaultChecked />
|
<Group align="start">
|
||||||
<Switch label="Laporan Mingguan" />
|
<Switch label="Pengaduan Baru" defaultChecked />
|
||||||
<Switch label="Pemberitahuan Keamanan" defaultChecked />
|
<Switch label="Update Status Pengaduan" defaultChecked />
|
||||||
<Switch label="Aktivitas Akun" defaultChecked />
|
<Switch label="Laporan Mingguan" />
|
||||||
</Group>
|
<Switch label="Pemberitahuan Keamanan" defaultChecked />
|
||||||
|
<Switch label="Aktivitas Akun" defaultChecked />
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<Alert icon={<IconInfoCircle size={16} />} title="Tip" color="blue" mb="md">
|
<Alert
|
||||||
Anda dapat menyesuaikan frekuensi notifikasi mingguan sesuai kebutuhan Anda.
|
icon={<IconInfoCircle size={16} />}
|
||||||
</Alert>
|
title="Tip"
|
||||||
|
color="blue"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Anda dapat menyesuaikan frekuensi notifikasi mingguan sesuai kebutuhan
|
||||||
|
Anda.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Group justify="flex-end" mt="xl">
|
<Group justify="flex-end" mt="xl">
|
||||||
<Button variant="outline">Batal</Button>
|
<Button variant="outline">Batal</Button>
|
||||||
<Button>Simpan Preferensi</Button>
|
<Button>Simpan Preferensi</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotifikasiSettings;
|
export default NotifikasiSettings;
|
||||||
@@ -1,58 +1,86 @@
|
|||||||
import { Card, Title, Text, Space, TextInput, Select, Button, Group, Switch, Alert, useMantineColorScheme } from '@mantine/core';
|
import {
|
||||||
import { IconInfoCircle } from '@tabler/icons-react';
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
const UmumSettings = () => {
|
const UmumSettings = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
return (
|
return (
|
||||||
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={2} mb="lg">Pengaturan Umum</Title>
|
withBorder
|
||||||
<Text color="dimmed" mb="xl">Kelola pengaturan umum aplikasi Anda</Text>
|
radius="md"
|
||||||
|
p="xl"
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={2} mb="lg">
|
||||||
|
Pengaturan Umum
|
||||||
|
</Title>
|
||||||
|
<Text color="dimmed" mb="xl">
|
||||||
|
Kelola pengaturan umum aplikasi Anda
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Nama Aplikasi"
|
label="Nama Aplikasi"
|
||||||
placeholder="Masukkan nama aplikasi"
|
placeholder="Masukkan nama aplikasi"
|
||||||
defaultValue="Dashboard Desa Plus"
|
defaultValue="Dashboard Desa Plus"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label="Bahasa Aplikasi"
|
label="Bahasa Aplikasi"
|
||||||
data={[
|
data={[
|
||||||
{ value: 'id', label: 'Indonesia' },
|
{ value: "id", label: "Indonesia" },
|
||||||
{ value: 'en', label: 'English' },
|
{ value: "en", label: "English" },
|
||||||
]}
|
]}
|
||||||
defaultValue="id"
|
defaultValue="id"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label="Zona Waktu"
|
label="Zona Waktu"
|
||||||
data={[
|
data={[
|
||||||
{ value: 'Asia/Jakarta', label: 'Asia/Jakarta (GMT+7)' },
|
{ value: "Asia/Jakarta", label: "Asia/Jakarta (GMT+7)" },
|
||||||
{ value: 'Asia/Makassar', label: 'Asia/Makassar (GMT+8)' },
|
{ value: "Asia/Makassar", label: "Asia/Makassar (GMT+8)" },
|
||||||
{ value: 'Asia/Jayapura', label: 'Asia/Jayapura (GMT+9)' },
|
{ value: "Asia/Jayapura", label: "Asia/Jayapura (GMT+9)" },
|
||||||
]}
|
]}
|
||||||
defaultValue="Asia/Jakarta"
|
defaultValue="Asia/Jakarta"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Switch label="Notifikasi Email" defaultChecked />
|
<Switch label="Notifikasi Email" defaultChecked />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md">
|
<Alert
|
||||||
Beberapa pengaturan mungkin memerlukan restart aplikasi untuk diterapkan sepenuhnya.
|
icon={<IconInfoCircle size={16} />}
|
||||||
</Alert>
|
title="Informasi"
|
||||||
|
color="blue"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Beberapa pengaturan mungkin memerlukan restart aplikasi untuk diterapkan
|
||||||
|
sepenuhnya.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<Group justify="flex-end" mt="xl">
|
<Group justify="flex-end" mt="xl">
|
||||||
<Button variant="outline">Batal</Button>
|
<Button variant="outline">Batal</Button>
|
||||||
<Button>Simpan Perubahan</Button>
|
<Button>Simpan Perubahan</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UmumSettings;
|
export default UmumSettings;
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useNavigate, useLocation } from "@tanstack/react-router";
|
|
||||||
import { Search, ChevronDown, ChevronUp } from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
Stack,
|
|
||||||
Group,
|
|
||||||
Text,
|
|
||||||
Badge,
|
Badge,
|
||||||
|
Box,
|
||||||
|
Collapse,
|
||||||
|
Group,
|
||||||
Input,
|
Input,
|
||||||
NavLink as MantineNavLink,
|
NavLink as MantineNavLink,
|
||||||
Box,
|
Stack,
|
||||||
|
Text,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
Collapse,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { ChevronDown, ChevronUp, Search } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
@@ -21,22 +21,28 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
const isActiveBg = colorScheme === 'dark' ? "#182949" : "#E6F0FF";
|
const isActiveBg = colorScheme === "dark" ? "#182949" : "#E6F0FF";
|
||||||
const isActiveBorder = colorScheme === 'dark' ? "#00398D" : "#1F41AE";
|
const isActiveBorder = colorScheme === "dark" ? "#00398D" : "#1F41AE";
|
||||||
|
|
||||||
// State for settings submenu collapse
|
// State for settings submenu collapse
|
||||||
const [settingsOpen, setSettingsOpen] = useState(
|
const [settingsOpen, setSettingsOpen] = useState(
|
||||||
location.pathname.startsWith('/dashboard/pengaturan')
|
location.pathname.startsWith("/dashboard/pengaturan"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define menu items with their paths
|
// Define menu items with their paths
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ name: "Beranda", path: "/dashboard" },
|
{ name: "Beranda", path: "/dashboard" },
|
||||||
{ name: "Kinerja Divisi", path: "/dashboard/kinerja-divisi" },
|
{ name: "Kinerja Divisi", path: "/dashboard/kinerja-divisi" },
|
||||||
{ name: "Pengaduan & Layanan Publik", path: "/dashboard/pengaduan-layanan-publik" },
|
{
|
||||||
|
name: "Pengaduan & Layanan Publik",
|
||||||
|
path: "/dashboard/pengaduan-layanan-publik",
|
||||||
|
},
|
||||||
{ name: "Jenna Analytic", path: "/dashboard/jenna-analytic" },
|
{ name: "Jenna Analytic", path: "/dashboard/jenna-analytic" },
|
||||||
{ name: "Demografi & Kependudukan", path: "/dashboard/demografi-pekerjaan" },
|
{
|
||||||
|
name: "Demografi & Kependudukan",
|
||||||
|
path: "/dashboard/demografi-pekerjaan",
|
||||||
|
},
|
||||||
{ name: "Keuangan & Anggaran", path: "/dashboard/keuangan-anggaran" },
|
{ name: "Keuangan & Anggaran", path: "/dashboard/keuangan-anggaran" },
|
||||||
{ name: "Bumdes & UMKM Desa", path: "/dashboard/bumdes" },
|
{ name: "Bumdes & UMKM Desa", path: "/dashboard/bumdes" },
|
||||||
{ name: "Sosial", path: "/dashboard/sosial" },
|
{ name: "Sosial", path: "/dashboard/sosial" },
|
||||||
@@ -53,14 +59,17 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Check if any settings submenu is active
|
// Check if any settings submenu is active
|
||||||
const isSettingsActive = settingsItems.some(item =>
|
const isSettingsActive = settingsItems.some(
|
||||||
location.pathname === item.path
|
(item) => location.pathname === item.path,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Box p="md" style={{ borderBottom: "1px solid var(--mantine-color-gray-3)" }}>
|
<Box
|
||||||
|
p="md"
|
||||||
|
style={{ borderBottom: "1px solid var(--mantine-color-gray-3)" }}
|
||||||
|
>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Badge
|
<Badge
|
||||||
color="dark"
|
color="dark"
|
||||||
@@ -112,7 +121,9 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
style={{
|
style={{
|
||||||
background: isActive ? isActiveBg : "transparent",
|
background: isActive ? isActiveBg : "transparent",
|
||||||
fontWeight: isActive ? "bold" : "normal",
|
fontWeight: isActive ? "bold" : "normal",
|
||||||
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent",
|
borderLeft: isActive
|
||||||
|
? `4px solid ${isActiveBorder}`
|
||||||
|
: "4px solid transparent",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
transition: "all 200ms ease",
|
transition: "all 200ms ease",
|
||||||
margin: "2px 0",
|
margin: "2px 0",
|
||||||
@@ -121,8 +132,8 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
body: {
|
body: {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: "#F1F5F9",
|
background: "#F1F5F9",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -132,7 +143,9 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
<Box>
|
<Box>
|
||||||
<MantineNavLink
|
<MantineNavLink
|
||||||
onClick={() => setSettingsOpen(!settingsOpen)}
|
onClick={() => setSettingsOpen(!settingsOpen)}
|
||||||
rightSection={settingsOpen ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
rightSection={
|
||||||
|
settingsOpen ? <ChevronUp size={16} /> : <ChevronDown size={16} />
|
||||||
|
}
|
||||||
label="Pengaturan"
|
label="Pengaturan"
|
||||||
active={isSettingsActive}
|
active={isSettingsActive}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
@@ -140,7 +153,9 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
style={{
|
style={{
|
||||||
background: isSettingsActive ? isActiveBg : "transparent",
|
background: isSettingsActive ? isActiveBg : "transparent",
|
||||||
fontWeight: isSettingsActive ? "bold" : "normal",
|
fontWeight: isSettingsActive ? "bold" : "normal",
|
||||||
borderLeft: isSettingsActive ? `4px solid ${isActiveBorder}` : "4px solid transparent",
|
borderLeft: isSettingsActive
|
||||||
|
? `4px solid ${isActiveBorder}`
|
||||||
|
: "4px solid transparent",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
transition: "all 200ms ease",
|
transition: "all 200ms ease",
|
||||||
margin: "2px 0",
|
margin: "2px 0",
|
||||||
@@ -149,12 +164,16 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
body: {
|
body: {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: "#F1F5F9",
|
background: "#F1F5F9",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Collapse in={settingsOpen}>
|
<Collapse in={settingsOpen}>
|
||||||
<Stack gap={0} ml="lg" style={{ overflowY: 'auto', maxHeight: '200px' }}>
|
<Stack
|
||||||
|
gap={0}
|
||||||
|
ml="lg"
|
||||||
|
style={{ overflowY: "auto", maxHeight: "200px" }}
|
||||||
|
>
|
||||||
{settingsItems.map((item, index) => {
|
{settingsItems.map((item, index) => {
|
||||||
const isActive = location.pathname === item.path;
|
const isActive = location.pathname === item.path;
|
||||||
return (
|
return (
|
||||||
@@ -168,7 +187,9 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
style={{
|
style={{
|
||||||
background: isActive ? isActiveBg : "transparent",
|
background: isActive ? isActiveBg : "transparent",
|
||||||
fontWeight: isActive ? "bold" : "normal",
|
fontWeight: isActive ? "bold" : "normal",
|
||||||
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent",
|
borderLeft: isActive
|
||||||
|
? `4px solid ${isActiveBorder}`
|
||||||
|
: "4px solid transparent",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
transition: "all 200ms ease",
|
transition: "all 200ms ease",
|
||||||
margin: "2px 0",
|
margin: "2px 0",
|
||||||
@@ -177,8 +198,8 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
body: {
|
body: {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: "#F1F5F9",
|
background: "#F1F5F9",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,289 +1,465 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Badge,
|
||||||
Grid,
|
Card,
|
||||||
GridCol,
|
Grid,
|
||||||
Group,
|
GridCol,
|
||||||
Text,
|
Group,
|
||||||
Title,
|
List,
|
||||||
Progress,
|
Progress,
|
||||||
Stack,
|
Stack,
|
||||||
useMantineColorScheme,
|
Text,
|
||||||
Badge,
|
ThemeIcon,
|
||||||
List,
|
Title,
|
||||||
ThemeIcon
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconHeartbeat,
|
IconAward,
|
||||||
IconBabyCarriage,
|
IconBabyCarriage,
|
||||||
IconStethoscope,
|
IconBook,
|
||||||
IconMedicalCross,
|
IconCalendarEvent,
|
||||||
IconSchool,
|
IconHeartbeat,
|
||||||
IconBook,
|
IconMedicalCross,
|
||||||
IconCalendarEvent,
|
IconSchool,
|
||||||
IconAward
|
IconStethoscope,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const SosialPage = () => {
|
const SosialPage = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Sample data for health statistics
|
// Sample data for health statistics
|
||||||
const healthStats = {
|
const healthStats = {
|
||||||
ibuHamil: 87,
|
ibuHamil: 87,
|
||||||
balita: 342,
|
balita: 342,
|
||||||
alertStunting: 12,
|
alertStunting: 12,
|
||||||
posyanduAktif: 8,
|
posyanduAktif: 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sample data for health progress
|
// Sample data for health progress
|
||||||
const healthProgress = [
|
const healthProgress = [
|
||||||
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
|
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
|
||||||
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
|
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
|
||||||
{ label: "Gizi Baik", value: 86, color: "teal" },
|
{ label: "Gizi Baik", value: 86, color: "teal" },
|
||||||
{ label: "Target Stunting", value: 14, color: "red" },
|
{ label: "Target Stunting", value: 14, color: "red" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sample data for posyandu schedule
|
// Sample data for posyandu schedule
|
||||||
const posyanduSchedule = [
|
const posyanduSchedule = [
|
||||||
{ nama: "Posyandu Mawar", tanggal: "Senin, 15 Feb 2026", jam: "08:00 - 11:00" },
|
{
|
||||||
{ nama: "Posyandu Melati", tanggal: "Selasa, 16 Feb 2026", jam: "08:00 - 11:00" },
|
nama: "Posyandu Mawar",
|
||||||
{ nama: "Posyandu Dahlia", tanggal: "Rabu, 17 Feb 2026", jam: "08:00 - 11:00" },
|
tanggal: "Senin, 15 Feb 2026",
|
||||||
{ nama: "Posyandu Anggrek", tanggal: "Kamis, 18 Feb 2026", jam: "08:00 - 11:00" },
|
jam: "08:00 - 11:00",
|
||||||
];
|
},
|
||||||
|
{
|
||||||
|
nama: "Posyandu Melati",
|
||||||
|
tanggal: "Selasa, 16 Feb 2026",
|
||||||
|
jam: "08:00 - 11:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Posyandu Dahlia",
|
||||||
|
tanggal: "Rabu, 17 Feb 2026",
|
||||||
|
jam: "08:00 - 11:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Posyandu Anggrek",
|
||||||
|
tanggal: "Kamis, 18 Feb 2026",
|
||||||
|
jam: "08:00 - 11:00",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Sample data for education stats
|
// Sample data for education stats
|
||||||
const educationStats = {
|
const educationStats = {
|
||||||
siswa: {
|
siswa: {
|
||||||
tk: 125,
|
tk: 125,
|
||||||
sd: 480,
|
sd: 480,
|
||||||
smp: 210,
|
smp: 210,
|
||||||
sma: 150,
|
sma: 150,
|
||||||
},
|
},
|
||||||
sekolah: {
|
sekolah: {
|
||||||
jumlah: 8,
|
jumlah: 8,
|
||||||
guru: 42,
|
guru: 42,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sample data for scholarships
|
// Sample data for scholarships
|
||||||
const scholarshipData = {
|
const scholarshipData = {
|
||||||
penerima: 45,
|
penerima: 45,
|
||||||
dana: "Rp 1.200.000.000",
|
dana: "Rp 1.200.000.000",
|
||||||
tahunAjaran: "2025/2026",
|
tahunAjaran: "2025/2026",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sample data for cultural events
|
// Sample data for cultural events
|
||||||
const culturalEvents = [
|
const culturalEvents = [
|
||||||
{ nama: "Hari Kesaktian Pancasila", tanggal: "1 Oktober 2025", lokasi: "Balai Desa" },
|
{
|
||||||
{ nama: "Festival Budaya Desa", tanggal: "20 Mei 2026", lokasi: "Lapangan Desa" },
|
nama: "Hari Kesaktian Pancasila",
|
||||||
{ nama: "Perayaan HUT Desa", tanggal: "17 Agustus 2026", lokasi: "Balai Desa" },
|
tanggal: "1 Oktober 2025",
|
||||||
];
|
lokasi: "Balai Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Festival Budaya Desa",
|
||||||
|
tanggal: "20 Mei 2026",
|
||||||
|
lokasi: "Lapangan Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Perayaan HUT Desa",
|
||||||
|
tanggal: "17 Agustus 2026",
|
||||||
|
lokasi: "Balai Desa",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* Health Statistics Cards */}
|
{/* Health Statistics Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
Ibu Hamil Aktif
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{healthStats.ibuHamil}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
</Stack>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<ThemeIcon variant="light" color="darmasaba-blue" size="xl" radius="xl">
|
Ibu Hamil Aktif
|
||||||
<IconHeartbeat size={24} />
|
</Text>
|
||||||
</ThemeIcon>
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
</Group>
|
{healthStats.ibuHamil}
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Stack>
|
||||||
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-blue"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconHeartbeat size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
Balita Terdaftar
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{healthStats.balita}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
</Stack>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl">
|
Balita Terdaftar
|
||||||
<IconBabyCarriage size={24} />
|
</Text>
|
||||||
</ThemeIcon>
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
</Group>
|
{healthStats.balita}
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Stack>
|
||||||
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-success"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconBabyCarriage size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
Alert Stunting
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="xl" fw={700} c="red">
|
>
|
||||||
{healthStats.alertStunting}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
</Stack>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<ThemeIcon variant="light" color="red" size="xl" radius="xl">
|
Alert Stunting
|
||||||
<IconStethoscope size={24} />
|
</Text>
|
||||||
</ThemeIcon>
|
<Text size="xl" fw={700} c="red">
|
||||||
</Group>
|
{healthStats.alertStunting}
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Stack>
|
||||||
|
<ThemeIcon variant="light" color="red" size="xl" radius="xl">
|
||||||
|
<IconStethoscope size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
withBorder
|
||||||
Posyandu Aktif
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{healthStats.posyanduAktif}
|
<Group justify="space-between" align="center">
|
||||||
</Text>
|
<Stack gap={0}>
|
||||||
</Stack>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<ThemeIcon variant="light" color="darmasaba-warning" size="xl" radius="xl">
|
Posyandu Aktif
|
||||||
<IconMedicalCross size={24} />
|
</Text>
|
||||||
</ThemeIcon>
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
</Group>
|
{healthStats.posyanduAktif}
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
</Stack>
|
||||||
</Grid>
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-warning"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconMedicalCross size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* Health Progress Bars */}
|
{/* Health Progress Bars */}
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Statistik Kesehatan</Title>
|
p="md"
|
||||||
<Stack gap="md">
|
radius="md"
|
||||||
{healthProgress.map((item, index) => (
|
withBorder
|
||||||
<div key={index}>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Group justify="space-between" mb={5}>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Text size="sm" fw={500} c={dark ? "dark.0" : "black"}>
|
>
|
||||||
{item.label}
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
</Text>
|
Statistik Kesehatan
|
||||||
<Text size="sm" fw={600} c={dark ? "dark.0" : "black"}>
|
</Title>
|
||||||
{item.value}%
|
<Stack gap="md">
|
||||||
</Text>
|
{healthProgress.map((item, index) => (
|
||||||
</Group>
|
<div key={index}>
|
||||||
<Progress
|
<Group justify="space-between" mb={5}>
|
||||||
value={item.value}
|
<Text size="sm" fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
size="lg"
|
{item.label}
|
||||||
radius="xl"
|
</Text>
|
||||||
color={item.color}
|
<Text size="sm" fw={600} c={dark ? "dark.0" : "black"}>
|
||||||
/>
|
{item.value}%
|
||||||
</div>
|
</Text>
|
||||||
))}
|
</Group>
|
||||||
</Stack>
|
<Progress
|
||||||
</Card>
|
value={item.value}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color={item.color}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Jadwal Posyandu */}
|
{/* Jadwal Posyandu */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Jadwal Posyandu</Title>
|
p="md"
|
||||||
<Stack gap="sm">
|
radius="md"
|
||||||
{posyanduSchedule.map((item, index) => (
|
withBorder
|
||||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }} h="100%">
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Group justify="space-between">
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<Stack gap={0}>
|
>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
<Text size="sm" c={dark ? "dark.0" : "black"}>{item.tanggal}</Text>
|
Jadwal Posyandu
|
||||||
</Stack>
|
</Title>
|
||||||
<Badge variant="light" color="darmasaba-blue">
|
<Stack gap="sm">
|
||||||
{item.jam}
|
{posyanduSchedule.map((item, index) => (
|
||||||
</Badge>
|
<Card
|
||||||
</Group>
|
key={index}
|
||||||
</Card>
|
p="md"
|
||||||
))}
|
radius="md"
|
||||||
</Stack>
|
withBorder
|
||||||
</Card>
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
</GridCol>
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.tanggal}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Badge variant="light" color="darmasaba-blue">
|
||||||
|
{item.jam}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Pendidikan */}
|
{/* Pendidikan */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Pendidikan</Title>
|
p="md"
|
||||||
<Stack gap="md">
|
radius="md"
|
||||||
<Group justify="space-between">
|
withBorder
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>TK / PAUD</Text>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.tk}</Text>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
</Group>
|
h="100%"
|
||||||
<Group justify="space-between">
|
>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SD</Text>
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sd}</Text>
|
Pendidikan
|
||||||
</Group>
|
</Title>
|
||||||
<Group justify="space-between">
|
<Stack gap="md">
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SMP</Text>
|
<Group justify="space-between">
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.smp}</Text>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
</Group>
|
TK / PAUD
|
||||||
<Group justify="space-between">
|
</Text>
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SMA</Text>
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sma}</Text>
|
{educationStats.siswa.tk}
|
||||||
</Group>
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
SD
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.siswa.sd}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
SMP
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.siswa.smp}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
SMA
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.siswa.sma}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Card withBorder radius="md" p="md" mt="md" bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
<Card
|
||||||
<Group justify="space-between">
|
withBorder
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Lembaga Pendidikan</Text>
|
radius="md"
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.jumlah}</Text>
|
p="md"
|
||||||
</Group>
|
mt="md"
|
||||||
<Group justify="space-between" mt="sm">
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Tenaga Pengajar</Text>
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.guru}</Text>
|
>
|
||||||
</Group>
|
<Group justify="space-between">
|
||||||
</Card>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
</Stack>
|
Jumlah Lembaga Pendidikan
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
</Grid>
|
{educationStats.sekolah.jumlah}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between" mt="sm">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
Jumlah Tenaga Pengajar
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.sekolah.guru}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{/* Beasiswa Desa */}
|
{/* Beasiswa Desa */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Group justify="space-between" align="center">
|
p="md"
|
||||||
<Stack gap={0}>
|
radius="md"
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Beasiswa Desa</Text>
|
withBorder
|
||||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>Penerima: {scholarshipData.penerima}</Text>
|
bg={dark ? "#141D34" : "white"}
|
||||||
</Stack>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl">
|
h="100%"
|
||||||
<IconAward size={24} />
|
>
|
||||||
</ThemeIcon>
|
<Group justify="space-between" align="center">
|
||||||
</Group>
|
<Stack gap={0}>
|
||||||
<Text mt="md" c={dark ? "dark.0" : "black"}>Dana Tersalurkan: <Text span fw={700}>{scholarshipData.dana}</Text></Text>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<Text mt="sm" c={dark ? "dark.3" : "dimmed"}>Tahun Ajaran: {scholarshipData.tahunAjaran}</Text>
|
Beasiswa Desa
|
||||||
</Card>
|
</Text>
|
||||||
</GridCol>
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
Penerima: {scholarshipData.penerima}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-success"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconAward size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
<Text mt="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Dana Tersalurkan:{" "}
|
||||||
|
<Text span fw={700}>
|
||||||
|
{scholarshipData.dana}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text mt="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Tahun Ajaran: {scholarshipData.tahunAjaran}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Kalender Event Budaya */}
|
{/* Kalender Event Budaya */}
|
||||||
<GridCol span={{ base: 12, lg: 6 }}>
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
<Card
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Kalender Event Budaya</Title>
|
p="md"
|
||||||
<List spacing="sm">
|
radius="md"
|
||||||
{culturalEvents.map((event, index) => (
|
withBorder
|
||||||
<List.Item key={index} icon={
|
bg={dark ? "#141D34" : "white"}
|
||||||
<ThemeIcon color="darmasaba-blue" size={24} radius="xl">
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<IconCalendarEvent size={12} />
|
h="100%"
|
||||||
</ThemeIcon>
|
>
|
||||||
}>
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
<Group justify="space-between">
|
Kalender Event Budaya
|
||||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{event.nama}</Text>
|
</Title>
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.lokasi}</Text>
|
<List spacing="sm">
|
||||||
</Group>
|
{culturalEvents.map((event, index) => (
|
||||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.tanggal}</Text>
|
<List.Item
|
||||||
</List.Item>
|
key={index}
|
||||||
))}
|
icon={
|
||||||
</List>
|
<ThemeIcon color="darmasaba-blue" size={24} radius="xl">
|
||||||
</Card>
|
<IconCalendarEvent size={12} />
|
||||||
</GridCol>
|
</ThemeIcon>
|
||||||
</Grid>
|
}
|
||||||
</Stack>
|
>
|
||||||
);
|
<Group justify="space-between">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{event.nama}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{event.lokasi}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{event.tanggal}
|
||||||
|
</Text>
|
||||||
|
</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SosialPage;
|
export default SosialPage;
|
||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Card as MantineCard,
|
Card as MantineCard,
|
||||||
type CardProps as MantineCardProps,
|
type CardProps as MantineCardProps,
|
||||||
|
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
|
|||||||
@@ -1,90 +1,86 @@
|
|||||||
import { Card, useMantineTheme, useComputedColorScheme } from '@mantine/core';
|
import type { CardProps } from "@mantine/core";
|
||||||
import type { CardProps } from '@mantine/core';
|
import { Card, useComputedColorScheme, useMantineTheme } from "@mantine/core";
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
interface HelpCardProps extends CardProps {
|
interface HelpCardProps extends CardProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
minHeight?: string | number; // Allow specifying a minimum height
|
minHeight?: string | number; // Allow specifying a minimum height
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HelpCard = ({
|
export const HelpCard = ({
|
||||||
children,
|
children,
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
minHeight = 'auto', // Default to auto, but allow override
|
minHeight = "auto", // Default to auto, but allow override
|
||||||
...props
|
...props
|
||||||
}: HelpCardProps) => {
|
}: HelpCardProps) => {
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const colorScheme = useComputedColorScheme('light');
|
const colorScheme = useComputedColorScheme("light");
|
||||||
const isDark = colorScheme === 'dark';
|
const isDark = colorScheme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
padding="xl"
|
padding="xl"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isDark ? theme.colors.dark[7] : theme.white,
|
backgroundColor: isDark ? theme.colors.dark[7] : theme.white,
|
||||||
borderRadius: '16px',
|
borderRadius: "16px",
|
||||||
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||||
border: `1px solid ${
|
border: `1px solid ${
|
||||||
isDark ? theme.colors.dark[4] : theme.colors.gray[3]
|
isDark ? theme.colors.dark[4] : theme.colors.gray[3]
|
||||||
}`,
|
}`,
|
||||||
minHeight, // Apply the minimum height
|
minHeight, // Apply the minimum height
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{(icon || title) && (
|
{(icon || title) && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
gap: '12px',
|
gap: "12px",
|
||||||
marginBottom: '16px',
|
marginBottom: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon && (
|
{icon && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isDark
|
backgroundColor: isDark
|
||||||
? theme.colors.blue[8]
|
? theme.colors.blue[8]
|
||||||
: theme.colors.blue[0],
|
: theme.colors.blue[0],
|
||||||
borderRadius: '8px',
|
borderRadius: "8px",
|
||||||
padding: '8px',
|
padding: "8px",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{title && (
|
{title && (
|
||||||
<h3
|
<h3
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: '16px',
|
fontSize: "16px",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: isDark
|
color: isDark ? theme.colors.dark[0] : theme.colors.dark[9],
|
||||||
? theme.colors.dark[0]
|
}}
|
||||||
: theme.colors.dark[9],
|
>
|
||||||
}}
|
{title}
|
||||||
>
|
</h3>
|
||||||
{title}
|
)}
|
||||||
</h3>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>{children}</div>
|
||||||
{children}
|
</Card>
|
||||||
</div>
|
);
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import { Inspector } from "react-dev-inspector";
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import '@mantine/charts/styles.css';
|
import "@mantine/charts/styles.css";
|
||||||
import { IS_DEV, VITE_PUBLIC_URL } from "./utils/env";
|
import { IS_DEV, VITE_PUBLIC_URL } from "./utils/env";
|
||||||
|
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
@@ -102,8 +101,6 @@ const theme = createTheme({
|
|||||||
primaryColor: "darmasaba-blue",
|
primaryColor: "darmasaba-blue",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const InspectorWrapper = IS_DEV
|
const InspectorWrapper = IS_DEV
|
||||||
? Inspector
|
? Inspector
|
||||||
: ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
: ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ function DashboardLayout() {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
shadow="md"
|
shadow="md"
|
||||||
width={200}
|
width={200}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import HelpPage from '@/components/help-page'
|
import HelpPage from "@/components/help-page";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/bantuan')({
|
|
||||||
component: HelpPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/bantuan")({
|
||||||
|
component: HelpPage,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import BumdesPage from '@/components/bumdes-page'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import BumdesPage from "@/components/bumdes-page";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/bumdes')({
|
|
||||||
component: BumdesPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/bumdes")({
|
||||||
|
component: BumdesPage,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import DemografiPekerjaan from '../../components/demografi-pekerjaan'
|
import DemografiPekerjaan from "../../components/demografi-pekerjaan";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/demografi-pekerjaan')({
|
|
||||||
component: DemografiPekerjaan,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/demografi-pekerjaan")({
|
||||||
|
component: DemografiPekerjaan,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import JennaAnalytic from '@/components/jenna-analytic'
|
import JennaAnalytic from "@/components/jenna-analytic";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/jenna-analytic')({
|
export const Route = createFileRoute("/dashboard/jenna-analytic")({
|
||||||
component: JennaAnalytic,
|
component: JennaAnalytic,
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import KeamananPage from '@/components/keamanan-page'
|
import KeamananPage from "@/components/keamanan-page";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/keamanan')({
|
|
||||||
component: KeamananPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/keamanan")({
|
||||||
|
component: KeamananPage,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import KeuanganAnggaran from '@/components/keuangan-anggaran'
|
import KeuanganAnggaran from "@/components/keuangan-anggaran";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/keuangan-anggaran')({
|
export const Route = createFileRoute("/dashboard/keuangan-anggaran")({
|
||||||
component: KeuanganAnggaran,
|
component: KeuanganAnggaran,
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import AksesDanTimSettings from '@/components/pengaturan/akses-dan-tim'
|
import AksesDanTimSettings from "@/components/pengaturan/akses-dan-tim";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/pengaturan/akses-dan-tim')({
|
export const Route = createFileRoute("/dashboard/pengaturan/akses-dan-tim")({
|
||||||
component: AksesDanTimSettings,
|
component: AksesDanTimSettings,
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import KeamananSettings from '@/components/pengaturan/keamanan'
|
import KeamananSettings from "@/components/pengaturan/keamanan";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/pengaturan/keamanan')({
|
|
||||||
component: KeamananSettings,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/pengaturan/keamanan")({
|
||||||
|
component: KeamananSettings,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import NotifikasiSettings from '@/components/pengaturan/notifikasi'
|
import NotifikasiSettings from "@/components/pengaturan/notifikasi";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/pengaturan/notifikasi')({
|
export const Route = createFileRoute("/dashboard/pengaturan/notifikasi")({
|
||||||
component: NotifikasiSettings,
|
component: NotifikasiSettings,
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/pengaturan')({
|
export const Route = createFileRoute("/dashboard/pengaturan")({
|
||||||
component: () => (
|
component: () => (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import UmumSettings from '@/components/pengaturan/umum'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import UmumSettings from "@/components/pengaturan/umum";
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/pengaturan/umum')({
|
|
||||||
component: UmumSettings,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/pengaturan/umum")({
|
||||||
|
component: UmumSettings,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,19 +1,39 @@
|
|||||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
import {
|
||||||
|
AppShell,
|
||||||
|
Burger,
|
||||||
|
Group,
|
||||||
|
useMantineColorScheme,
|
||||||
|
useMantineTheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useDisclosure, useMediaQuery } from "@mantine/hooks";
|
||||||
|
import { createFileRoute, Outlet, useRouterState } from "@tanstack/react-router";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { Header } from "@/components/header";
|
import { Header } from "@/components/header";
|
||||||
import { Sidebar } from "@/components/sidebar";
|
import { Sidebar } from "@/components/sidebar";
|
||||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard")({
|
export const Route = createFileRoute("/dashboard")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const [opened, { toggle }] = useDisclosure();
|
const [opened, { toggle, close }] = useDisclosure();
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const headerBgColor = colorScheme === 'dark' ? "#11192D" : "#19355E";
|
const theme = useMantineTheme();
|
||||||
const navbarBgColor = colorScheme === 'dark' ? "#11192D" : "white";
|
const routerState = useRouterState();
|
||||||
const mainBgColor = colorScheme === 'dark' ? "#11192D" : "#edf3f8ff";
|
|
||||||
|
const isMobile = useMediaQuery("(max-width: 48em)");
|
||||||
|
|
||||||
|
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
|
||||||
|
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
|
||||||
|
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
|
||||||
|
|
||||||
|
// ✅ AUTO CLOSE NAVBAR ON ROUTE CHANGE (MOBILE ONLY)
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMobile && opened) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}, [routerState.location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: 60 }}
|
header={{ height: 60 }}
|
||||||
@@ -25,14 +45,29 @@ function RouteComponent() {
|
|||||||
padding="md"
|
padding="md"
|
||||||
>
|
>
|
||||||
<AppShell.Header bg={headerBgColor}>
|
<AppShell.Header bg={headerBgColor}>
|
||||||
<Group h="100%" px="md">
|
<Group
|
||||||
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
|
h="100%"
|
||||||
|
px="lg"
|
||||||
|
align="center"
|
||||||
|
wrap="nowrap"
|
||||||
|
>
|
||||||
|
<Burger
|
||||||
|
opened={opened}
|
||||||
|
onClick={toggle}
|
||||||
|
hiddenFrom="sm"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
</Group>
|
</Group>
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
|
|
||||||
<AppShell.Navbar p="md" bg={navbarBgColor} style={{ display: 'flex', flexDirection: 'column' }}>
|
<AppShell.Navbar
|
||||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
p="md"
|
||||||
|
bg={navbarBgColor}
|
||||||
|
style={{ display: "flex", flexDirection: "column" }}
|
||||||
|
>
|
||||||
|
<div style={{ flex: 1, overflowY: "auto" }}>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import SocialPage from '@/components/sosial-page'
|
import SocialPage from "@/components/sosial-page";
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/dashboard/sosial')({
|
|
||||||
component: SocialPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/sosial")({
|
||||||
|
component: SocialPage,
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user