diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0c0e5bd --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# Database +DATABASE_URL="postgresql://user:password@localhost:5432/dashboard_desa?schema=public" + +# Authentication +BETTER_AUTH_SECRET="your-secret-key-here-min-32-characters" +ADMIN_EMAIL="admin@example.com" +ADMIN_PASSWORD="admin123" + +# GitHub OAuth (Optional) +GITHUB_CLIENT_ID="" +GITHUB_CLIENT_SECRET="" + +# Application +PORT=3000 +NODE_ENV=development +LOG_LEVEL=info + +# Public URL +VITE_PUBLIC_URL="http://localhost:3000" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..088f877 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,106 @@ +name: Publish Docker to GHCR + +on: + workflow_dispatch: + inputs: + stack_env: + description: "stack env" + required: true + type: choice + default: "dev" + options: + - dev + - prod + - stg + tag: + description: "Image tag (e.g. 1.0.0)" + required: true + default: "1.0.0" + + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + publish: + name: Build & Push to GHCR ${{ github.repository }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }} + runs-on: ubuntu-latest + environment: ${{ vars.PORTAINER_ENV || 'portainer' }} + permissions: + contents: read + packages: write + steps: + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + df -h + + - name: Checkout branch ${{ github.event.inputs.stack_env }} + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.stack_env }} + + - name: Checkout scripts from main + uses: actions/checkout@v4 + with: + ref: main + path: .ci + sparse-checkout: .github/workflows/script + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }} + type=raw,value=${{ github.event.inputs.stack_env }}-latest + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + no-cache: true + + - name: Notify success + if: success() + run: bash ./.ci/.github/workflows/script/notify.sh + env: + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + NOTIFY_STATUS: success + NOTIFY_WORKFLOW: "Publish Docker" + NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}" + + - name: Notify failure + if: failure() + run: bash ./.ci/.github/workflows/script/notify.sh + env: + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + NOTIFY_STATUS: failure + NOTIFY_WORKFLOW: "Publish Docker" + NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}" diff --git a/.github/workflows/re-pull.yml b/.github/workflows/re-pull.yml new file mode 100644 index 0000000..e2110e7 --- /dev/null +++ b/.github/workflows/re-pull.yml @@ -0,0 +1,60 @@ +name: Re-Pull Docker +on: + workflow_dispatch: + inputs: + stack_name: + description: "stack name" + required: true + type: string + stack_env: + description: "stack env" + required: true + type: choice + default: "dev" + options: + - dev + - stg + - prod + +jobs: + publish: + name: Re-Pull Docker ${{ github.event.inputs.stack_name }} + runs-on: ubuntu-latest + environment: ${{ vars.PORTAINER_ENV || 'portainer' }} + permissions: + contents: read + packages: write + steps: + - name: Checkout scripts from main + uses: actions/checkout@v4 + with: + ref: main + sparse-checkout: .github/workflows/script + + - name: Deploy ke Portainer + run: bash ./.github/workflows/script/re-pull.sh + env: + PORTAINER_USERNAME: ${{ secrets.PORTAINER_USERNAME }} + PORTAINER_PASSWORD: ${{ secrets.PORTAINER_PASSWORD }} + PORTAINER_URL: ${{ secrets.PORTAINER_URL }} + STACK_NAME: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }} + + - name: Notify success + if: success() + run: bash ./.github/workflows/script/notify.sh + env: + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + NOTIFY_STATUS: success + NOTIFY_WORKFLOW: "Re-Pull Docker" + NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}" + + - name: Notify failure + if: failure() + run: bash ./.github/workflows/script/notify.sh + env: + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + NOTIFY_STATUS: failure + NOTIFY_WORKFLOW: "Re-Pull Docker" + NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}" diff --git a/.github/workflows/script/notify.sh b/.github/workflows/script/notify.sh new file mode 100644 index 0000000..22944bf --- /dev/null +++ b/.github/workflows/script/notify.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +: "${TELEGRAM_TOKEN:?TELEGRAM_TOKEN tidak di-set}" +: "${TELEGRAM_CHAT_ID:?TELEGRAM_CHAT_ID tidak di-set}" +: "${NOTIFY_STATUS:?NOTIFY_STATUS tidak di-set}" +: "${NOTIFY_WORKFLOW:?NOTIFY_WORKFLOW tidak di-set}" + +if [ "$NOTIFY_STATUS" = "success" ]; then + ICON="✅" + TEXT="${ICON} *${NOTIFY_WORKFLOW}* berhasil!" +else + ICON="❌" + TEXT="${ICON} *${NOTIFY_WORKFLOW}* gagal!" +fi + +if [ -n "$NOTIFY_DETAIL" ]; then + TEXT="${TEXT} +${NOTIFY_DETAIL}" +fi + +curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg chat_id "$TELEGRAM_CHAT_ID" \ + --arg text "$TEXT" \ + '{chat_id: $chat_id, text: $text, parse_mode: "Markdown"}')" diff --git a/.github/workflows/script/re-pull.sh b/.github/workflows/script/re-pull.sh new file mode 100644 index 0000000..8097813 --- /dev/null +++ b/.github/workflows/script/re-pull.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +: "${PORTAINER_URL:?PORTAINER_URL tidak di-set}" +: "${PORTAINER_USERNAME:?PORTAINER_USERNAME tidak di-set}" +: "${PORTAINER_PASSWORD:?PORTAINER_PASSWORD tidak di-set}" +: "${STACK_NAME:?STACK_NAME tidak di-set}" + +echo "🔐 Autentikasi ke Portainer..." +TOKEN=$(curl -s -X POST https://${PORTAINER_URL}/api/auth \ + -H "Content-Type: application/json" \ + -d "{\"username\": \"${PORTAINER_USERNAME}\", \"password\": \"${PORTAINER_PASSWORD}\"}" \ + | jq -r .jwt) + +if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then + echo "❌ Autentikasi gagal! Cek PORTAINER_URL, USERNAME, dan PASSWORD." + exit 1 +fi + +echo "🔍 Mencari stack: $STACK_NAME..." +STACK=$(curl -s -X GET https://${PORTAINER_URL}/api/stacks \ + -H "Authorization: Bearer ${TOKEN}" \ + | jq ".[] | select(.Name == \"$STACK_NAME\")") + +if [ -z "$STACK" ]; then + echo "❌ Stack '$STACK_NAME' tidak ditemukan di Portainer!" + echo " Pastikan nama stack sudah benar." + exit 1 +fi + +STACK_ID=$(echo "$STACK" | jq -r .Id) +ENDPOINT_ID=$(echo "$STACK" | jq -r .EndpointId) +ENV=$(echo "$STACK" | jq '.Env // []') + +echo "📄 Mengambil compose file..." +STACK_FILE=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks/${STACK_ID}/file" \ + -H "Authorization: Bearer ${TOKEN}" \ + | jq -r .StackFileContent) + +PAYLOAD=$(jq -n \ + --arg content "$STACK_FILE" \ + --argjson env "$ENV" \ + '{stackFileContent: $content, env: $env, pullImage: true}') + +echo "🚀 Redeploying $STACK_NAME (pull latest image)..." +HTTP_STATUS=$(curl -s -o /tmp/portainer_response.json -w "%{http_code}" \ + -X PUT "https://${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${ENDPOINT_ID}" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + +if [ "$HTTP_STATUS" != "200" ]; then + echo "❌ Redeploy gagal! HTTP Status: $HTTP_STATUS" + cat /tmp/portainer_response.json | jq . + exit 1 +fi + +echo "⏳ Menunggu container running..." + +MAX_RETRY=15 +COUNT=0 + +while [ $COUNT -lt $MAX_RETRY ]; do + sleep 5 + COUNT=$((COUNT + 1)) + + CONTAINERS=$(curl -s -X GET \ + "https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \ + -H "Authorization: Bearer ${TOKEN}") + + TOTAL=$(echo "$CONTAINERS" | jq 'length') + RUNNING=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "running")] | length') + FAILED=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not))] | length') + + echo "🔄 [${COUNT}/${MAX_RETRY}] Running: ${RUNNING} | Failed: ${FAILED} | Total: ${TOTAL}" + echo "$CONTAINERS" | jq -r '.[] | " → \(.Names[0]) | \(.State) | \(.Status)"' + + if [ "$FAILED" -gt "0" ]; then + echo "" + echo "❌ Ada container yang crash!" + echo "$CONTAINERS" | jq -r '.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not)) | " → \(.Names[0]) | \(.Status)"' + exit 1 + fi + + if [ "$RUNNING" -gt "0" ]; then + echo "" + echo "✅ Stack $STACK_NAME berhasil di-redeploy dan running!" + exit 0 + fi +done + +echo "" +echo "❌ Timeout! Stack tidak kunjung running setelah $((MAX_RETRY * 5)) detik." +exit 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 02abb20..3e6f1b5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ _.log report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # dotenv environment variable files +# Only .env.example is allowed to be committed .env .env.development.local .env.test.local @@ -33,6 +34,9 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store +# Dashboard-MD +Dashboard-MD + # Playwright artifacts test-results/ playwright-report/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6743868 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +# Stage 1: Build +FROM oven/bun:1.3 AS build + +# Install build dependencies for native modules +RUN apt-get update && apt-get install -y \ + python3 \ + make \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy package files +COPY package.json bun.lock* ./ + +# Install dependencies +RUN bun install --frozen-lockfile + +# Copy the rest of the application code +COPY . . + +# Use .env.example as default env for build +RUN cp .env.example .env + +# Generate Prisma client +RUN bun x prisma generate + +# Generate API types +RUN bun run gen:api + +# Build the application frontend using our custom build script +RUN bun run build + +# Stage 2: Runtime +FROM oven/bun:1.3-slim AS runtime + +# Set environment variables +ENV NODE_ENV=production + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy necessary files from build stage +COPY --from=build /app/package.json ./ +COPY --from=build /app/tsconfig.json ./ +COPY --from=build /app/dist ./dist +COPY --from=build /app/generated ./generated +COPY --from=build /app/src ./src +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/prisma ./prisma + +# Expose the port +EXPOSE 3000 + +# Start the application +CMD ["bun", "start"] diff --git a/Screenshot 2026-03-10 at 16.48.25.png b/Screenshot 2026-03-10 at 16.48.25.png new file mode 100644 index 0000000..018aa7a Binary files /dev/null and b/Screenshot 2026-03-10 at 16.48.25.png differ diff --git a/public/SDGS-1.png b/public/SDGS-1.png new file mode 100644 index 0000000..2a0437e Binary files /dev/null and b/public/SDGS-1.png differ diff --git a/public/SDGS-16.png b/public/SDGS-16.png new file mode 100644 index 0000000..09821a1 Binary files /dev/null and b/public/SDGS-16.png differ diff --git a/public/SDGS-3.png b/public/SDGS-3.png new file mode 100644 index 0000000..b65cd01 Binary files /dev/null and b/public/SDGS-3.png differ diff --git a/public/SDGS-7.png b/public/SDGS-7.png new file mode 100644 index 0000000..c57ea96 Binary files /dev/null and b/public/SDGS-7.png differ diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..e80eade --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env bun + +/** + * Build script for production + * 1. Build CSS with PostCSS/Tailwind + * 2. Bundle JS with Bun (without CSS) + * 3. Replace CSS reference in HTML + */ + +import { $ } from "bun"; +import fs from "node:fs"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import autoprefixer from "autoprefixer"; + +console.log("🔨 Starting production build..."); + +// Ensure dist directory exists +if (!fs.existsSync("./dist")) { + fs.mkdirSync("./dist", { recursive: true }); +} + +// Step 1: Build CSS with PostCSS +console.log("🎨 Building CSS..."); +const cssInput = fs.readFileSync("./src/index.css", "utf-8"); +const cssResult = await postcss([tailwindcss(), autoprefixer()]).process( + cssInput, + { + from: "./src/index.css", + to: "./dist/index.css", + }, +); + +fs.writeFileSync("./dist/index.css", cssResult.css); +console.log("✅ CSS built successfully!"); + +// Step 2: Build JS with Bun (build HTML too, we'll fix CSS link later) +console.log("📦 Bundling JavaScript..."); +await $`bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='"production"' --env='VITE_*'`; + +// Step 3: Copy public assets +console.log("📁 Copying public assets..."); +if (fs.existsSync("./public")) { + await $`cp -r public/* dist/ 2>/dev/null || true`; +} + +// Step 4: Ensure HTML references the correct CSS +// Bun build might have renamed the CSS, we want to use our own index.css +console.log("🔧 Fixing HTML CSS reference..."); +const htmlPath = "./dist/index.html"; +if (fs.existsSync(htmlPath)) { + let html = fs.readFileSync(htmlPath, "utf-8"); + // Replace any bundled CSS reference with our index.css + html = html.replace(/href="[^"]*\.css"/g, 'href="/index.css"'); + fs.writeFileSync(htmlPath, html); +} + +console.log("✅ Build completed successfully!"); diff --git a/src/components/bumdes-page.tsx b/src/components/bumdes-page.tsx index 4b7e1b2..605a8e8 100644 --- a/src/components/bumdes-page.tsx +++ b/src/components/bumdes-page.tsx @@ -1,23 +1,13 @@ -import { - Badge, - Button, - Card, - Grid, - GridCol, - Group, - Select, - Stack, - Table, - Text, - Title, - useMantineColorScheme, -} from "@mantine/core"; import { IconBuildingStore, IconCategory, IconCurrency, IconUsers, + IconTrendingUp, + IconTrendingDown, + IconChevronDown, } from "@tabler/icons-react"; +import { useMantineColorScheme } from "@mantine/core"; import { useState } from "react"; const BumdesPage = () => { @@ -25,69 +15,98 @@ const BumdesPage = () => { const dark = colorScheme === "dark"; const [timeFilter, setTimeFilter] = useState("bulan"); + const [categoryFilter, setCategoryFilter] = useState("semua"); - // Sample data for KPI cards + // KPI Data const kpiData = [ { title: "UMKM Aktif", - value: 45, - icon: , - color: "darmasaba-blue", + value: "45", + subtitle: "Beroperasi", + icon: IconUsers, }, { title: "UMKM Terdaftar", - value: 68, - icon: , - color: "darmasaba-success", + value: "68", + subtitle: "Total terdaftar", + icon: IconBuildingStore, }, { title: "Omzet", - value: "Rp 48.000.000", - icon: , - color: "darmasaba-warning", + value: "48 JT", + subtitle: "Bulan ini", + icon: IconCurrency, }, { title: "Kategori UMKM", - value: 34, - icon: , - color: "darmasaba-danger", + value: "34", + subtitle: "Jenis produk", + icon: IconCategory, }, ]; - // Sample data for top products + // Mini stats data + const miniStats = [ + { + title: "Total Penjualan", + value: "Rp 30.900.000", + subtitle: "+18% vs bulan lalu", + isPositive: true, + }, + { + title: "Produk Aktif", + value: "7", + subtitle: "Kategori produk", + }, + { + title: "Total Transaksi", + value: "500", + subtitle: "Transaksi bulan ini", + }, + ]; + + // Top 3 products data const topProducts = [ { rank: 1, name: "Beras Premium Organik", - umkmOwner: "Warung Pak Joko", - growth: "+12%", + umkmOwner: "Kelompok Tani Subak", + sales: "Rp 8.500.000", + volume: "650 Kg Terjual", + growth: "+15%", }, { rank: 2, name: "Keripik Singkong", umkmOwner: "Ibu Sari Snack", + sales: "Rp 4.200.000", + volume: "320 Kg Terjual", growth: "+8%", }, { rank: 3, name: "Madu Alami", umkmOwner: "Peternakan Lebah", + sales: "Rp 3.750.000", + volume: "150 Liter Terjual", growth: "+5%", }, ]; - // Sample data for product sales + // Product sales data const productSales = [ { produk: "Beras Premium Organik", + umkm: "Kelompok Tani Subak", penjualanBulanIni: "Rp 8.500.000", - bulanLalu: "Rp 8.500.000", - trend: 10, + bulanLalu: "Rp 7.400.000", + trend: 15, volume: "650 Kg", stok: "850 Kg", }, { produk: "Keripik Singkong", + umkm: "Ibu Sari Snack", penjualanBulanIni: "Rp 4.200.000", bulanLalu: "Rp 3.800.000", trend: 10, @@ -96,6 +115,7 @@ const BumdesPage = () => { }, { produk: "Madu Alami", + umkm: "Peternakan Lebah", penjualanBulanIni: "Rp 3.750.000", bulanLalu: "Rp 4.100.000", trend: -8, @@ -104,6 +124,7 @@ const BumdesPage = () => { }, { produk: "Kecap Tradisional", + umkm: "Bu Darmi", penjualanBulanIni: "Rp 2.800.000", bulanLalu: "Rp 2.500.000", trend: 12, @@ -112,277 +133,456 @@ const BumdesPage = () => { }, ]; + const cardStyle = { + backgroundColor: dark ? "#1E293B" : "white", + border: `1px solid ${dark ? "#1E293B" : "white"}`, + }; + + const textStyle = { + color: dark ? "white" : "#1F2937", + }; + + const subtitleStyle = { + color: dark ? "#9CA3AF" : "#6B7280", + }; + return ( - - {/* KPI Cards */} - - {kpiData.map((kpi, index) => ( - - - - - - {kpi.title} - - - {typeof kpi.value === "number" - ? kpi.value.toLocaleString() - : kpi.value} - - - - {kpi.icon} - - - - - ))} - - - {/* Update Penjualan Produk Header */} - +
- - - Update Penjualan Produk - - - - - - - - - - {/* Produk Unggulan (Left Column) */} - - - {/* Total Penjualan, Produk Aktif, Total Transaksi */} - - - - - Total Penjualan - - - Rp 28.500.000 - - - - - Produk Aktif - - - 124 Produk - - - - - Total Transaksi - - - 1.240 Transaksi - - - - - - {/* Top 3 Produk Terlaris */} - - - Top 3 Produk Terlaris - - - {topProducts.map((product) => ( - +
+

- - - {product.rank} - - - - {product.name} - - - {product.umkmOwner} - - - - - {product.growth} - - - ))} - - - - + {kpi.title} +

+

+ {kpi.value} +

+

+ {kpi.subtitle} +

+
+
+
+ +
+
+
+ + ))} + - {/* Detail Penjualan Produk (Right Column) */} - - +
- - - Detail Penjualan Produk - - setCategoryFilter(e.target.value)} + className="appearance-none px-4 py-2 pr-8 rounded-lg text-sm font-medium border-0 focus:ring-2 focus:ring-[#1F3A5F] cursor-pointer" + style={{ + backgroundColor: dark ? "#334155" : "#F9FAFB", + color: dark ? "white" : "#1F2937", + }} + > + + + + + + +
+ + + {/* Data Table */} +
+ + + + + + + + + + + + + + {productSales.map((product, index) => ( + + + + + + + + + + ))} + +
+ Produk + + Penjualan Bulan Ini + + Bulan Lalu + + Trend + + Volume + + Stok + + Aksi +
+

+ {product.produk} +

+

+ {product.umkm} +

+
+

+ {product.penjualanBulanIni} +

+
+

+ {product.bulanLalu} +

+
+
= 0 ? "#22C55E" : "#EF4444", + }} + > + {product.trend >= 0 ? ( + + ) : ( + + )} + {product.trend >= 0 ? "+" : ""} + {product.trend}% +
+
+

+ {product.volume} +

+
+ 200 ? "#DCFCE7" : "#FEE2E2", + color: parseInt(product.stok) > 200 ? "#166534" : "#991B1B", + }} + > + {product.stok} + + + +
+
+ + + + + ); }; diff --git a/src/components/dashboard-content.tsx b/src/components/dashboard-content.tsx index d0c8b8e..888caf4 100644 --- a/src/components/dashboard-content.tsx +++ b/src/components/dashboard-content.tsx @@ -1,4 +1,5 @@ import { + Briefcase, Calendar, CheckCircle, FileText, @@ -27,7 +28,9 @@ import { Card, // Added for icon containers Grid, Group, + Image, Progress, + SimpleGrid, Stack, Text, ThemeIcon, @@ -67,9 +70,16 @@ const eventData = [ ]; const apbdesData = [ - { name: "Belanja", value: 70, color: "blue" }, - { name: "Pendapatan", value: 90, color: "green" }, - { name: "Pembangunan", value: 50, color: "orange" }, + { name: "Belanja", value: 390, label: "390M" }, + { name: "Pendapatan", value: 470, label: "470M" }, + { name: "Pembiayaan", value: 290, label: "290M" }, +]; + +const sdgsData = [ + { label: "Desa Berenergi Bersih Dan Terbarukan", value: 99.64, image: "/SDGS-7.png" }, + { label: "Desa Damai Berkeadilan", value: 78.65, image: "/SDGS-16.png" }, + { label: "Desa Sehat Dan Sejahtera", value: 77.37, image: "/SDGS-3.png" }, + { label: "Desa Tanpa Kemiskinan", value: 52.62, image: "/SDGS-1.png" } ]; export function DashboardContent() { @@ -109,7 +119,7 @@ export function DashboardContent() { variant="filled" size="xl" radius="xl" - color={dark ? "gray" : "darmasaba-blue"} + color={dark ? "gray" : "darmasaba-navy"} > @@ -143,7 +153,7 @@ export function DashboardContent() { variant="filled" size="xl" radius="xl" - color={dark ? "gray" : "darmasaba-blue"} + color={dark ? "gray" : "darmasaba-navy"} > @@ -180,7 +190,7 @@ export function DashboardContent() { variant="filled" size="xl" radius="xl" - color={dark ? "gray" : "darmasaba-blue"} + color={dark ? "gray" : "darmasaba-navy"} > @@ -214,7 +224,7 @@ export function DashboardContent() { variant="filled" size="xl" radius="xl" - color={dark ? "gray" : "darmasaba-blue"} + color={dark ? "gray" : "darmasaba-navy"} > @@ -282,7 +292,7 @@ export function DashboardContent() { @@ -375,46 +385,7 @@ export function DashboardContent() { {/* Original SVG icon */} - - - - - - + Divisi Teraktif @@ -433,7 +404,7 @@ export function DashboardContent() { value={(divisi.value / 37) * 100} size="sm" radius="xl" - color="blue" + color="#1E3A5F" /> ))} @@ -459,7 +430,7 @@ export function DashboardContent() { @@ -493,18 +464,53 @@ export function DashboardContent() { {data.name} - + + + + {data.label} + + ))}
+ + {/* SDGS Desa */} + + + SDGS Desa + + + {sdgsData.map((data, index) => ( + + + + + + {data.label} + + + {data.value} + + + + + ))} + + ); } diff --git a/src/components/demografi-pekerjaan.tsx b/src/components/demografi-pekerjaan.tsx index cf189d2..81776ef 100644 --- a/src/components/demografi-pekerjaan.tsx +++ b/src/components/demografi-pekerjaan.tsx @@ -1,410 +1,543 @@ -import { BarChart, PieChart } from "@mantine/charts"; -import { - Box, - Card, - Grid, - Group, - Stack, - Table, - Text, - Title, - useMantineColorScheme, -} from "@mantine/core"; import { IconArrowDown, IconArrowUp, IconBabyCarriage, IconSkull, + IconUsers, + IconHome, + IconExclamationCircle, } from "@tabler/icons-react"; -import React from "react"; - -// Sample Data -const kpiData = [ - { - id: 1, - title: "Total Penduduk", - value: "5.634", - sub: "Aktif terdaftar", - icon: ( - - Total Penduduk - - - ), - }, - { - id: 2, - title: "Kepala Keluarga", - value: "1.354", - sub: "Total KK", - icon: ( - - Kepala Keluarga - - - ), - }, - { - id: 3, - title: "Kelahiran", - value: "23", - sub: "Tahun ini", - icon: ( - - ), - }, - { - id: 4, - title: "Kemiskinan", - value: "324", - delta: "-10% dari tahun lalu", - deltaType: "positive", - icon: ( - - Kemiskinan - - - ), - }, -]; - -const ageDistributionData = [ - { ageRange: "17-25", total: 850 }, - { ageRange: "26-35", total: 1200 }, - { ageRange: "36-45", total: 1100 }, - { ageRange: "46-55", total: 950 }, - { ageRange: "56-65", total: 750 }, - { ageRange: "65+", total: 484 }, -]; - -const jobDistributionData = [ - { job: "Sipil", total: 1200 }, - { job: "Guru", total: 850 }, - { job: "Petani", total: 950 }, - { job: "Pedagang", total: 750 }, - { job: "Wiraswasta", total: 984 }, -]; - -const religionData = [ - { religion: "Hindu", total: 4234, color: "red" }, - { religion: "Islam", total: 856, color: "blue" }, - { religion: "Kristen", total: 412, color: "green" }, - { religion: "Buddha", total: 202, color: "yellow" }, -]; - -const banjarData = [ - { banjar: "Banjar Darmasaba", population: 1200, kk: 300, poor: 45 }, - { banjar: "Banjar Manesa", population: 950, kk: 240, poor: 32 }, - { banjar: "Banjar Cabe", population: 800, kk: 200, poor: 28 }, - { banjar: "Banjar Penenjoan", population: 1100, kk: 280, poor: 38 }, - { banjar: "Banjar Baler Pasar", population: 984, kk: 250, poor: 42 }, - { banjar: "Banjar Bucu", population: 600, kk: 184, poor: 25 }, -]; - -const dynamicStats = [ - { - title: "Kelahiran", - value: "23", - icon: , - color: "green", - }, - { - title: "Kematian", - value: "12", - icon: , - color: "red", - }, - { - title: "Pindah Masuk", - value: "45", - icon: , - color: "blue", - }, - { - title: "Pindah Keluar", - value: "32", - icon: , - color: "orange", - }, -]; +import { useMantineColorScheme } from "@mantine/core"; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; const DemografiPekerjaan = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; + + // KPI Data + const kpiData = [ + { + id: 1, + title: "Total Penduduk", + value: "5.634", + subtitle: "Aktif terdaftar", + icon: IconUsers, + }, + { + id: 2, + title: "Kepala Keluarga", + value: "1.354", + subtitle: "Total KK", + icon: IconHome, + }, + { + id: 3, + title: "Kelahiran", + value: "23", + subtitle: "Tahun ini", + icon: IconBabyCarriage, + }, + { + id: 4, + title: "Kemiskinan", + value: "324", + subtitle: "-10% dari tahun lalu", + icon: IconExclamationCircle, + }, + ]; + + // Age distribution data + const ageDistributionData = [ + { ageRange: "17-25", total: 850 }, + { ageRange: "26-35", total: 1200 }, + { ageRange: "36-45", total: 1100 }, + { ageRange: "46-55", total: 950 }, + { ageRange: "56-65", total: 750 }, + { ageRange: "65+", total: 484 }, + ]; + + // Job distribution data + const jobDistributionData = [ + { job: "Sipil", total: 1200 }, + { job: "Guru", total: 850 }, + { job: "Petani", total: 950 }, + { job: "Pedagang", total: 750 }, + { job: "Wiraswasta", total: 984 }, + ]; + + // Religion data + const religionData = [ + { religion: "Hindu", total: 4234, color: "#EF4444" }, + { religion: "Islam", total: 856, color: "#3B82F6" }, + { religion: "Kristen", total: 412, color: "#10B981" }, + { religion: "Buddha", total: 202, color: "#F59E0B" }, + ]; + + // Banjar data + const banjarData = [ + { banjar: "Banjar Darmasaba", population: 1200, kk: 300, poor: 45 }, + { banjar: "Banjar Manesa", population: 950, kk: 240, poor: 32 }, + { banjar: "Banjar Cabe", population: 800, kk: 200, poor: 28 }, + { banjar: "Banjar Penenjoan", population: 1100, kk: 280, poor: 38 }, + { banjar: "Banjar Baler Pasar", population: 984, kk: 250, poor: 42 }, + { banjar: "Banjar Bucu", population: 600, kk: 184, poor: 25 }, + ]; + + // Dynamic stats + const dynamicStats = [ + { + title: "Kelahiran", + value: "23", + icon: IconBabyCarriage, + color: "#10B981", + }, + { + title: "Kematian", + value: "12", + icon: IconSkull, + color: "#EF4444", + }, + { + title: "Pindah Masuk", + value: "45", + icon: IconArrowDown, + color: "#3B82F6", + }, + { + title: "Pindah Keluar", + value: "32", + icon: IconArrowUp, + color: "#F59E0B", + }, + ]; + + const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD", "#DBEAFE"]; + + const cardStyle = { + backgroundColor: dark ? "#141D34" : "white", + border: `1px solid ${dark ? "#141D34" : "white"}`, + }; + + const textStyle = { + color: dark ? "white" : "#1F2937", + }; + + const subtitleStyle = { + color: dark ? "#9CA3AF" : "#6B7280", + }; + return ( - - - {/* KPI Cards */} - - {kpiData.map((kpi) => ( - - - - - {kpi.title} - - {React.cloneElement(kpi.icon, { - className: "h-6 w-6", - color: dark - ? "var(--mantine-color-dark-3)" - : "var(--mantine-color-dimmed)", - })} - - - {kpi.value} - - {kpi.delta && ( - - {kpi.delta} - - )} - {kpi.sub && ( - - {kpi.sub} - - )} - - - ))} - - - {/* Charts Section */} - - {/* Grafik Pengelompokan Umur */} - - - - Grafik Pengelompokan Umur - - - - - - {/* Demografi Pekerjaan */} - - - - Demografi Pekerjaan - - - - - - - {/* Agama & Data per Banjar */} - - {/* Distribusi Agama */} - - - - Distribusi Agama - - ({ - name: item.religion, - value: item.total, - color: item.color, - }))} - withLabels - withLabelsLine - labelsPosition="outside" - labelsType="percent" - /> - - - - {/* Data per Banjar */} - - - - Data per Banjar - - - - - - Banjar - - - Penduduk - - - KK - - - Miskin - - - - - {banjarData.map((item, index) => ( - - - {item.banjar} - - - - {item.population.toLocaleString()} - - - - - {item.kk.toLocaleString()} - - - - - {item.poor.toLocaleString()} - - - - ))} - -
-
-
-
- - {/* Statistik Dinamika Penduduk */} - +
+ {/* Row 1: 4 Statistic Cards */} +
- - Statistik Dinamika Penduduk - - - {dynamicStats.map((stat, index) => ( - - ( +
+
+
+

+ {kpi.title} +

+

+ {kpi.value} +

+

+ {kpi.subtitle} +

+
+
+
+ +
+
+
+
+ ))} +
+ + {/* Row 2: 2 Chart Cards */} +
+ {/* Age Distribution Bar Chart */} +
+

+ Grafik Pengelompokan Umur +

+ + + + + + + + {ageDistributionData.map((entry, index) => ( + + ))} + + + +
+ + {/* Job Distribution Bar Chart */} +
+

+ Demografi Pekerjaan +

+ + + + + + + + {jobDistributionData.map((entry, index) => ( + + ))} + + + +
+
+ + {/* Row 3: 3 Insight Cards */} +
+ {/* Religion Distribution Pie Chart */} +
+

+ Distribusi Agama +

+ + + + `${name}: ${percent ? (percent * 100).toFixed(0) : 0}%` + } > - - - - {stat.title} - - - {stat.value} - - - {stat.icon} - - - + {religionData.map((entry, index) => ( + + ))} + + + + +
+ + {/* Population per Banjar Table */} +
+

+ Data per Banjar +

+
+ + + + + + + + + + + {banjarData.map((item, index) => ( + + + + + + + ))} + +
+ Banjar + + Penduduk + + KK + + Miskin +
+ {item.banjar} + + {item.population.toLocaleString()} + + {item.kk.toLocaleString()} + + {item.poor.toLocaleString()} +
+
+
+
+ + {/* Population Dynamics Stats */} +
+

+ Statistik Dinamika Penduduk +

+
+ {dynamicStats.map((stat, index) => ( +
+
+
+

+ {stat.title} +

+

+ {stat.value} +

+
+
+ +
+
+
))} - - - - +
+
+
+ ); }; diff --git a/src/components/help-page.tsx b/src/components/help-page.tsx index c7bab00..cbcea3c 100644 --- a/src/components/help-page.tsx +++ b/src/components/help-page.tsx @@ -142,7 +142,7 @@ const HelpPage = () => { }; return ( - + {/* Statistics Section */} {stats.map((stat, index) => ( diff --git a/src/components/jenna-analytic.tsx b/src/components/jenna-analytic.tsx index 063a56c..4fc8032 100644 --- a/src/components/jenna-analytic.tsx +++ b/src/components/jenna-analytic.tsx @@ -1,283 +1,328 @@ -import { BarChart } from "@mantine/charts"; import { - Badge, - Box, - Button, - Card, - Grid, - Group, - Progress, - Stack, - Text, - Title, - useMantineColorScheme, -} from "@mantine/core"; -import React from "react"; - -// Sample Data -const kpiData = [ - { - id: 1, - title: "Interaksi Hari Ini", - value: "61", - delta: "+15% dari kemarin", - deltaType: "positive", - icon: ( - - - - ), - }, - { - id: 2, - title: "Jawaban Otomatis", - value: "87%", - sub: "53 dari 61 interaksi", - icon: ( - - - - ), - }, - { - id: 3, - title: "Belum Ditindak", - value: "8", - sub: "Perlu respon manual", - deltaType: "negative", - icon: ( - - - - ), - }, - { - id: 4, - title: "Waktu Respon", - value: "2.3 sec", - sub: "Rata-rata", - icon: ( - - - - ), - }, -]; - -const chartData = [ - { day: "Sen", total: 100 }, - { day: "Sel", total: 120 }, - { day: "Rab", total: 90 }, - { day: "Kam", total: 150 }, - { day: "Jum", total: 110 }, - { day: "Sab", total: 80 }, - { day: "Min", total: 130 }, -]; - -const topTopics = [ - { topic: "Cara mengurus KTP", count: 89 }, - { topic: "Syarat Kartu Keluarga", count: 76 }, - { topic: "Jadwal Posyandu", count: 64 }, - { topic: "Pengaduan jalan rusak", count: 52 }, - { topic: "Info program bansos", count: 48 }, -]; - -const busyHours = [ - { period: "Pagi (08–12)", percentage: 30 }, - { period: "Siang (12–16)", percentage: 40 }, - { period: "Sore (16–20)", percentage: 20 }, - { period: "Malam (20–08)", percentage: 10 }, -]; + IconAlertTriangle, + IconClock, + IconMessageChatbot, + IconSparkles, +} from "@tabler/icons-react"; +import { useMantineColorScheme } from "@mantine/core"; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; const JennaAnalytic = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; + // KPI Data + const kpiData = [ + { + id: 1, + title: "Interaksi Hari Ini", + value: "61", + subtitle: "+15% dari kemarin", + icon: IconMessageChatbot, + }, + { + id: 2, + title: "Jawaban Otomatis", + value: "87%", + subtitle: "53 dari 61 interaksi", + icon: IconSparkles, + }, + { + id: 3, + title: "Belum Ditindak", + value: "8", + subtitle: "Perlu respon manual", + icon: IconAlertTriangle, + }, + { + id: 4, + title: "Waktu Respon", + value: "2.3s", + subtitle: "Rata-rata", + icon: IconClock, + }, + ]; + + // Weekly chatbot interaction data + const weeklyData = [ + { day: "Sen", interactions: 100 }, + { day: "Sel", interactions: 120 }, + { day: "Rab", interactions: 90 }, + { day: "Kam", interactions: 150 }, + { day: "Jum", interactions: 110 }, + { day: "Sab", interactions: 80 }, + { day: "Min", interactions: 130 }, + ]; + + // Top topics data + const topTopics = [ + { topic: "Cara mengurus KTP", count: 89 }, + { topic: "Syarat Kartu Keluarga", count: 76 }, + { topic: "Jadwal Posyandu", count: 64 }, + { topic: "Pengaduan jalan rusak", count: 52 }, + { topic: "Info program bansos", count: 48 }, + ]; + + // Busy hour distribution + const busyHours = [ + { period: "Pagi (08–12)", percentage: 30 }, + { period: "Siang (12–16)", percentage: 40 }, + { period: "Sore (16–20)", percentage: 20 }, + { period: "Malam (20–08)", percentage: 10 }, + ]; + + const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD"]; + + const cardStyle = { + backgroundColor: dark ? "#141D34" : "white", + border: `1px solid ${dark ? "#141D34" : "white"}`, + }; + + const textStyle = { + color: dark ? "white" : "#1F2937", + }; + + const subtitleStyle = { + color: dark ? "#9CA3AF" : "#6B7280", + }; + return ( - - - {/* KPI Cards */} - - {kpiData.map((kpi) => ( - - - - - {kpi.title} - - {React.cloneElement(kpi.icon, { - className: "h-6 w-6", // Keeping classes for now, can be replaced by Mantine Icon component if available or styled with sx prop - color: "var(--mantine-color-dimmed)", // Set color via prop - })} - - - {kpi.value} - - {kpi.delta && ( - - {kpi.delta} - - )} - {kpi.sub && ( - - {kpi.sub} - - )} - - - ))} - - - +
+ {/* Row 1: 4 Statistic Cards */} +
- - Interaksi Chatbot - - - - - {/* Charts and Lists Section */} - - {/* Grafik Interaksi Chatbot (now Bar Chart) */} - - ( +
- - Jam Tersibuk - - - {busyHours.map((item, index) => ( - - {item.period} - - - - {item.percentage}% - - - +
+
+

+ {kpi.title} +

+

+ {kpi.value} +

+

+ {kpi.subtitle} +

+
+
+
+ +
+
+
+
+ ))} +
+ + {/* Row 2: Full Width Weekly Bar Chart */} +
+

+ Interaksi Chatbot Mingguan +

+ + + + + + + + {weeklyData.map((entry, index) => ( + ))} - - - + + + +
- {/* Topik Pertanyaan Terbanyak & Jam Tersibuk */} - - - {/* Topik Pertanyaan Terbanyak */} - - - Topik Pertanyaan Terbanyak - - - {topTopics.map((item, index) => ( - + {/* Left: Frequently Asked Topics */} +
+

+ Topik Pertanyaan Terbanyak +

+
+ {topTopics.map((item, index) => ( +
+ + {item.topic} + + + {item.count}x + +
+ ))} +
+
+ + {/* Right: Busy Hour Distribution */} +
+

+ Distribusi Jam Tersibuk +

+
+ {busyHours.map((item, index) => ( +
+
+ - - {item.topic} - - - {item.count}x - - - ))} - - - - {/* Jam Tersibuk */} - - - - - + {item.period} + + + {item.percentage}% + +
+
+
+
+
+ ))} +
+
+
+
+ ); }; + export default JennaAnalytic; diff --git a/src/components/keamanan-page.tsx b/src/components/keamanan-page.tsx index b1dd262..0233b51 100644 --- a/src/components/keamanan-page.tsx +++ b/src/components/keamanan-page.tsx @@ -118,13 +118,6 @@ const KeamananPage = () => { return ( - {/* Page Header */} - - - Keamanan Lingkungan Desa - - - {/* KPI Cards */} {kpiData.map((kpi, index) => ( diff --git a/src/components/keuangan-anggaran.tsx b/src/components/keuangan-anggaran.tsx index 47ef4db..ff35706 100644 --- a/src/components/keuangan-anggaran.tsx +++ b/src/components/keuangan-anggaran.tsx @@ -1,356 +1,567 @@ -import { BarChart } from "@mantine/charts"; -import { - Badge, - Box, - Button, - Card, - Grid, - Group, - Progress, - Stack, - Text, - Title, - useMantineColorScheme, -} from "@mantine/core"; import { IconCurrency, IconTrendingDown, IconTrendingUp, + IconCheck, + IconClock, } from "@tabler/icons-react"; -import React from "react"; - -// Sample Data -const kpiData = [ - { - id: 1, - title: "Total APBDes", - value: "Rp 5.2M", - sub: "Tahun 2025", - icon: , - }, - { - id: 2, - title: "Realisasi", - value: "68%", - sub: "Rp 3.5M dari 5.2M", - icon: ( - - - - ), - }, - { - id: 3, - title: "Pemasukan", - value: "Rp 580jt", - sub: "Bulan ini", - delta: "+8%", - deltaType: "positive", - icon: , - }, - { - id: 4, - title: "Pengeluaran", - value: "Rp 520jt", - sub: "Bulan ini", - icon: , - }, -]; - -const incomeExpenseData = [ - { month: "Apr", income: 450, expense: 380 }, - { month: "Mei", income: 520, expense: 420 }, - { month: "Jun", income: 480, expense: 500 }, - { month: "Jul", income: 580, expense: 450 }, - { month: "Agu", income: 550, expense: 520 }, - { month: "Sep", income: 600, expense: 480 }, - { month: "Okt", income: 580, expense: 520 }, -]; - -const allocationData = [ - { sector: "Pembangunan", amount: 1200 }, - { sector: "Kesehatan", amount: 800 }, - { sector: "Pendidikan", amount: 650 }, - { sector: "Sosial", amount: 550 }, - { sector: "Kebudayaan", amount: 400 }, - { sector: "Teknologi", amount: 300 }, -]; - -const assistanceFundData = [ - { source: "Dana Desa (DD)", amount: 1800, status: "cair" }, - { source: "Alokasi Dana Desa (ADD)", amount: 950, status: "cair" }, - { source: "Bagi Hasil Pajak", amount: 450, status: "cair" }, - { source: "Hibah Provinsi", amount: 300, status: "proses" }, -]; - -const apbdReport = { - income: [ - { category: "Dana Desa", amount: 1800 }, - { category: "Alokasi Dana Desa", amount: 480 }, - { category: "Bagi Hasil Pajak & Retribusi", amount: 300 }, - { category: "Pendapatan Asli Desa", amount: 200 }, - { category: "Hibah Bantuan", amount: 300 }, - ], - expenses: [ - { category: "Penyelenggaraan Pemerintah", amount: 425 }, - { category: "Pembangunan Desa", amount: 850 }, - { category: "Pembinaan Kemasyarakatan", amount: 320 }, - { category: "Pemberdayaan Masyarakat", amount: 380 }, - { category: "Penanggulangan Bencana", amount: 180 }, - ], - totalIncome: 3080, - totalExpenses: 2155, -}; +import { useMantineColorScheme } from "@mantine/core"; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; const KeuanganAnggaran = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; + + // KPI Data + const kpiData = [ + { + id: 1, + title: "Total APBDes", + value: "Rp 5.2M", + subtitle: "Tahun 2025", + icon: IconCurrency, + }, + { + id: 2, + title: "Realisasi", + value: "68%", + subtitle: "Rp 3.5M dari 5.2M", + icon: IconCheck, + }, + { + id: 3, + title: "Pemasukan", + value: "Rp 580jt", + subtitle: "Bulan ini", + delta: "+8%", + icon: IconTrendingUp, + }, + { + id: 4, + title: "Pengeluaran", + value: "Rp 520jt", + subtitle: "Bulan ini", + icon: IconTrendingDown, + }, + ]; + + // Income vs Expense data + const incomeExpenseData = [ + { month: "Apr", income: 450, expense: 380 }, + { month: "Mei", income: 520, expense: 420 }, + { month: "Jun", income: 480, expense: 500 }, + { month: "Jul", income: 580, expense: 450 }, + { month: "Agu", income: 550, expense: 520 }, + { month: "Sep", income: 600, expense: 480 }, + { month: "Okt", income: 580, expense: 520 }, + ]; + + // Allocation data + const allocationData = [ + { sector: "Pembangunan", amount: 1200 }, + { sector: "Kesehatan", amount: 800 }, + { sector: "Pendidikan", amount: 650 }, + { sector: "Sosial", amount: 550 }, + { sector: "Kebudayaan", amount: 400 }, + { sector: "Teknologi", amount: 300 }, + ]; + + // Assistance fund data + const assistanceFundData = [ + { source: "Dana Desa (DD)", amount: 1800, status: "cair" }, + { source: "Alokasi Dana Desa (ADD)", amount: 950, status: "cair" }, + { source: "Bagi Hasil Pajak", amount: 450, status: "cair" }, + { source: "Hibah Provinsi", amount: 300, status: "proses" }, + ]; + + // APBDes Report data + const apbdReport = { + income: [ + { category: "Dana Desa", amount: 1800 }, + { category: "Alokasi Dana Desa", amount: 480 }, + { category: "Bagi Hasil Pajak & Retribusi", amount: 300 }, + { category: "Pendapatan Asli Desa", amount: 200 }, + { category: "Hibah Bantuan", amount: 300 }, + ], + expenses: [ + { category: "Penyelenggaraan Pemerintah", amount: 425 }, + { category: "Pembangunan Desa", amount: 850 }, + { category: "Pembinaan Kemasyarakatan", amount: 320 }, + { category: "Pemberdayaan Masyarakat", amount: 380 }, + { category: "Penanggulangan Bencana", amount: 180 }, + ], + totalIncome: 3080, + totalExpenses: 2155, + }; + + const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD", "#DBEAFE"]; + + const cardStyle = { + backgroundColor: dark ? "#1E293B" : "white", + border: `1px solid ${dark ? "#1E293B" : "white"}`, + }; + + const textStyle = { + color: dark ? "white" : "#1F2937", + }; + + const subtitleStyle = { + color: dark ? "#9CA3AF" : "#6B7280", + }; + return ( - - - {/* KPI Cards */} - +
+
+ {/* Row 1: 4 Summary Metrics Cards */} +
{kpiData.map((kpi) => ( - - - - - {kpi.title} - - {React.cloneElement(kpi.icon, { - className: "h-6 w-6", - color: "var(--mantine-color-dimmed)", - })} - - - {kpi.value} - - {kpi.delta && ( - +
+
+

- {kpi.delta} - - )} - {kpi.sub && ( - - {kpi.sub} - - )} - - + {kpi.title} +

+

+ {kpi.value} +

+

+ {kpi.subtitle} +

+ {kpi.delta && ( +

+ {kpi.delta} +

+ )} +
+
+
+ +
+
+
+
))} - +
- {/* Charts Section */} - - {/* Grafik Pemasukan vs Pengeluaran */} - - - - Pemasukan vs Pengeluaran - - +

+ Pemasukan vs Pengeluaran +

+ + + + -
-
- - {/* Alokasi Anggaran Per Sektor */} - - - - Alokasi Anggaran Per Sektor - - `${value}jt`} /> - - -
+ + + + + - - {/* Dana Bantuan & Hibah */} - - +
+
+ + Pemasukan + +
+
+
+ + Pengeluaran + +
+
+
+ + {/* Row 3: Analytics Section */} + + +
+ {/* Left: Horizontal Bar Chart */} +
+

- - Dana Bantuan & Hibah - - - {assistanceFundData.map((fund, index) => ( - + + + + `${value}jt`} + /> + + + + {allocationData.map((entry, index) => ( + + ))} + + + +

+ {/* Right: Assistance Funds List */} +
+

+ Dana Bantuan dan Hibah +

+
+ {assistanceFundData.map((fund, index) => ( +
+
+

+ {fund.source} +

+

+ Rp {fund.amount.toLocaleString()}jt +

+
+ - - - {fund.source} - - - Rp {fund.amount.toLocaleString()}jt - - - - {fund.status} - - - ))} - - - + {fund.status === "cair" ? ( + + ) : ( + + )} + {fund.status} + +
+ ))} +
+
- {/* Laporan APBDes */} - - - - Laporan APBDes - - - - - Pendapatan - - - {apbdReport.income.map((item, index) => ( - - {item.category} - - Rp {item.amount.toLocaleString()}jt - - - ))} - - Total Pendapatan: - - Rp {apbdReport.totalIncome.toLocaleString()}jt - - - - - - - - Belanja - - - {apbdReport.expenses.map((item, index) => ( - - {item.category} - - Rp {item.amount.toLocaleString()}jt - - - ))} - - Total Belanja: - - Rp {apbdReport.totalExpenses.toLocaleString()}jt - - - - - - + {/* Row 4: Report Section */} +
+

+ Laporan APBDes +

+
+ {/* Left: Pendapatan */} +
+

- - Saldo: - apbdReport.totalExpenses - ? "green" - : "red" - } + Pendapatan +

+
+ {apbdReport.income.map((item, index) => ( +
- Rp{" "} - {( - apbdReport.totalIncome - apbdReport.totalExpenses - ).toLocaleString()} - jt - - - - - - - - + + {item.category} + + + Rp {item.amount.toLocaleString()}jt + +
+ ))} +
+ + Total Pendapatan + + + Rp {apbdReport.totalIncome.toLocaleString()}jt + +
+
+
+ + {/* Right: Belanja */} +
+

+ Belanja +

+
+ {apbdReport.expenses.map((item, index) => ( +
+ + {item.category} + + + Rp {item.amount.toLocaleString()}jt + +
+ ))} +
+ + Total Belanja + + + Rp {apbdReport.totalExpenses.toLocaleString()}jt + +
+
+
+
+ + {/* Footer: Balance */} +
+ + Saldo + + apbdReport.totalExpenses + ? "#22C55E" + : "#EF4444", + }} + > + Rp{" "} + {( + apbdReport.totalIncome - apbdReport.totalExpenses + ).toLocaleString()} + jt + +
+
+ +
+
); }; diff --git a/src/components/kinerja-divisi.tsx b/src/components/kinerja-divisi.tsx index 05265b7..4f64037 100644 --- a/src/components/kinerja-divisi.tsx +++ b/src/components/kinerja-divisi.tsx @@ -1,21 +1,3 @@ -import { - ActionIcon, - Box, - Card, - Divider, - Grid, - GridCol, - Group, - List, - Badge as MantineBadge, - Progress as MantineProgress, - Skeleton, - Stack, - Text, - ThemeIcon, - Title, - useMantineColorScheme, -} from "@mantine/core"; import { Bar, BarChart, @@ -28,511 +10,380 @@ import { XAxis, YAxis, } from "recharts"; -import { Button } from "@/components/ui/button"; +import { useMantineColorScheme } from "@mantine/core"; +import { IconMessage } from "@tabler/icons-react"; const KinerjaDivisi = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; - // Data for division progress chart - const divisionProgressData = [ - { name: "Sekretariat", selesai: 12, berjalan: 5, tertunda: 2 }, - { name: "Keuangan", selesai: 8, berjalan: 7, tertunda: 1 }, - { name: "Sosial", selesai: 10, berjalan: 3, tertunda: 4 }, - { name: "Humas", selesai: 6, berjalan: 9, tertunda: 3 }, - ]; - - // Division task summaries - const divisionTasks = [ + // Top row - 4 activity cards + const activities = [ { - name: "Sekretariat", - tasks: [ - { title: "Laporan Bulanan", status: "selesai" }, - { title: "Arsip Dokumen", status: "berjalan" }, - { title: "Undangan Rapat", status: "tertunda" }, - ], - }, - { - name: "Keuangan", - tasks: [ - { title: "Laporan APBDes", status: "selesai" }, - { title: "Verifikasi Dana", status: "tertunda" }, - { title: "Pengeluaran Harian", status: "berjalan" }, - ], - }, - { - name: "Sosial", - tasks: [ - { title: "Program Bantuan", status: "selesai" }, - { title: "Kegiatan Posyandu", status: "berjalan" }, - { title: "Monitoring Stunting", status: "tertunda" }, - ], - }, - { - name: "Humas", - tasks: [ - { title: "Publikasi Kegiatan", status: "selesai" }, - { title: "Koordinasi Media", status: "berjalan" }, - { title: "Laporan Kegiatan", status: "tertunda" }, - ], - }, - ]; - - // Archive items - const archiveItems = [ - { name: "Surat Keputusan", count: 12 }, - { name: "Laporan Keuangan", count: 8 }, - { name: "Dokumentasi", count: 24 }, - { name: "Notulensi Rapat", count: 15 }, - ]; - - // Activity progress - const activityProgress = [ - { - name: "Pembangunan Jalan", - progress: 75, - date: "15 Feb 2026", - status: "berjalan", - }, - { - name: "Posyandu Bulanan", + title: "Rakor 2025", progress: 100, - date: "10 Feb 2026", - status: "selesai", + date: "15 Jan 2025", }, { - name: "Vaksinasi Massal", - progress: 45, - date: "20 Feb 2026", - status: "berjalan", + title: "Pemutakhiran Indeks Desa", + progress: 100, + date: "20 Feb 2025", }, { - name: "Festival Budaya", - progress: 20, - date: "5 Mar 2026", - status: "berjalan", + title: "Mengurus akta cerai warga", + progress: 100, + date: "5 Mar 2025", + }, + { + title: "Pasek 7 desa adat", + progress: 100, + date: "10 Mar 2025", }, ]; // Document statistics const documentStats = [ - { name: "Gambar", value: 42 }, - { name: "Dokumen", value: 87 }, + { name: "Gambar", value: 300, color: "#FAC858" }, + { name: "Dokumen", value: 310, color: "#92CC76" }, ]; // Activity progress statistics const activityProgressStats = [ - { name: "Selesai", value: 12, fill: "#10B981" }, - { name: "Dikerjakan", value: 8, fill: "#F59E0B" }, - { name: "Segera Dikerjakan", value: 5, fill: "#EF4444" }, - { name: "Dibatalkan", value: 2, fill: "#6B7280" }, + { name: "Selesai", value: 83.33, fill: "#92CC76" }, + { name: "Dikerjakan", value: 16.67, fill: "#FAC858" }, + { name: "Segera Dikerjakan", value: 0, fill: "#5470C6" }, + { name: "Dibatalkan", value: 0, fill: "#EE6767" }, ]; - const COLORS = ["#10B981", "#F59E0B", "#EF4444", "#6B7280"]; - const STATUS_COLORS: Record = { - selesai: "green", - berjalan: "blue", - tertunda: "red", - proses: "yellow", - }; - // Discussion data const discussions = [ { title: "Pembahasan APBDes 2026", sender: "Kepala Desa", - timestamp: "2 jam yang lalu", + date: "10 Mar 2025", }, { title: "Kegiatan Posyandu", sender: "Divisi Sosial", - timestamp: "5 jam yang lalu", + date: "9 Mar 2025", }, { title: "Festival Budaya", sender: "Divisi Humas", - timestamp: "1 hari yang lalu", + date: "8 Mar 2025", }, ]; - // Today's agenda - const todayAgenda = [ - { time: "09:00", event: "Rapat Evaluasi Bulanan" }, - { time: "14:00", event: "Koordinasi Program Bantuan" }, - ]; - return ( - - {/* Grafik Progres Tugas per Divisi */} - + {/* Top Row - 4 Activity Cards */} +
- - Grafik Progres Tugas per Divisi - - - - - - - - - - - - - - - {/* Ringkasan Tugas per Divisi */} - - {divisionTasks.map((division, index) => ( - - ( +
+ {/* Dark blue title bar */} +
- - {division.name} - - - {division.tasks.map((task, taskIndex) => ( - - - - {task.title} - - - {task.status} - - - - ))} - - - - ))} - +

{activity.title}

+
- {/* Arsip Digital Perangkat Desa */} - - - Arsip Digital Perangkat Desa - - - {archiveItems.map((item, index) => ( - - +
+
+ + {/* Date and badge */} +
+ - - - {item.name} - - - {item.count} - - - - - ))} - - - - {/* Kartu Progres Kegiatan */} - - - Progres Kegiatan / Program - - - {activityProgress.map((activity, index) => ( - - - - {activity.name} - - - {activity.status} - - - - - - {activity.progress}% - - - {activity.date} - - - ))} - - + + + Selesai + +
+
+ ))} +
- {/* Statistik Dokumen & Progres Kegiatan */} - - - + {/* Left Card - Jumlah Dokumen (Bar Chart) */} +
+

- - Jumlah Dokumen - - - - - - - - - - - - + Jumlah Dokumen +

+ + + + + + + + {documentStats.map((entry, index) => ( + + ))} + + + +
- - +

- - Progres Kegiatan - - - + + + item.value > 0)} + cx="50%" + cy="50%" + outerRadius={80} + dataKey="value" + label={({ name, percent }) => + `${name}: ${percent ? (percent * 100).toFixed(0) : 0}%` + } > - - `${name}: ${percent ? (percent * 100).toFixed(0) : "0"}%` - } - /> - - - - - - + {activityProgressStats + .filter(item => item.value > 0) + .map((entry, index) => ( + + ))} + + + + - {/* Diskusi Internal */} - - - Diskusi Internal - - - {discussions.map((discussion, index) => ( - - - - {discussion.title} - - - {discussion.timestamp} - - - - {discussion.sender} - - - ))} - - + {/* Legend */} +
+
+
+ + Segera Dikerjakan + +
+
+
+ + Dikerjakan + +
+
+
+ + Selesai + +
+
+
+ + Dibatalkan + +
+
+ + - {/* Agenda / Acara Hari Ini */} - - - Agenda / Acara Hari Ini - - {todayAgenda.length > 0 ? ( - - {todayAgenda.map((agenda, index) => ( - - - {agenda.time} - - - - {agenda.event} - - + {/* Left Card - Diskusi */} +
+

+ Diskusi +

+
+ {discussions.map((discussion, index) => ( +
+
+
+ +
+
+
+

+ {discussion.title} +

+

+ {discussion.sender} • {discussion.date} +

+
+
))} - - ) : ( - - Tidak ada acara hari ini - - )} - - +
+
+ + {/* Right Card - Acara Hari Ini */} +
+

+ Acara Hari Ini +

+
+

+ Tidak ada acara hari ini +

+
+
+ + ); }; diff --git a/src/components/pengaduan-layanan-publik.tsx b/src/components/pengaduan-layanan-publik.tsx index cafff0d..ef3ccab 100644 --- a/src/components/pengaduan-layanan-publik.tsx +++ b/src/components/pengaduan-layanan-publik.tsx @@ -1,36 +1,16 @@ import { - ActionIcon, - Badge, - Box, - Button, - Card, - Divider, - Grid, - GridCol, - Group, - List, - Select, - Stack, - Table, - Text, - Textarea, - TextInput, - Title, - useMantineColorScheme, -} from "@mantine/core"; -import { + IconMessage, IconAlertTriangle, + IconClock, IconCheck, IconChevronRight, - IconClock, - IconMessage, } from "@tabler/icons-react"; -import type React from "react"; -import { useState } from "react"; +import { useMantineColorScheme } from "@mantine/core"; import { Bar, BarChart, CartesianGrid, + Cell, Line, LineChart, ResponsiveContainer, @@ -43,800 +23,462 @@ const PengaduanLayananPublik = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; - // Summary data - const summaryData = { - total: 42, - baru: 14, - diproses: 14, - selesai: 14, - }; - - // Tren pengaduan data - const trenData = [ - { bulan: "Jan", jumlah: 30 }, - { bulan: "Feb", jumlah: 50 }, - { bulan: "Mar", jumlah: 42 }, - { bulan: "Apr", jumlah: 38 }, - { bulan: "Mei", jumlah: 45 }, - { bulan: "Jun", jumlah: 42 }, - ]; - - // Surat terbanyak data - const suratData = [ - { jenis: "KTP", jumlah: 24 }, - { jenis: "KK", jumlah: 18 }, - { jenis: "Domisili", jumlah: 15 }, - { jenis: "Usaha", jumlah: 12 }, - { jenis: "Lainnya", jumlah: 8 }, - ]; - - // Pengajuan terbaru data - const pengajuanTerbaru = [ + // Statistic cards data + const statsData = [ { - nama: "Budi Santoso", - jenis: "Ketertiban Umum", - waktu: "2 jam yang lalu", - status: "baru", + title: "Total Pengaduan", + value: 156, + subtitle: "+12% dari bulan lalu", + icon: IconMessage, }, { - nama: "Siti Rahayu", - jenis: "Pelayanan Kesehatan", - waktu: "5 jam yang lalu", - status: "diproses", + title: "Pengaduan Baru", + value: 24, + subtitle: "Perlu tindakan segera", + icon: IconAlertTriangle, }, { - nama: "Ahmad Fauzi", - jenis: "Infrastruktur", - waktu: "1 hari yang lalu", - status: "selesai", + title: "Sedang Diproses", + value: 48, + subtitle: "Dalam penanganan", + icon: IconClock, }, { - nama: "Dewi Lestari", - jenis: "Administrasi", - waktu: "1 hari yang lalu", - status: "baru", - }, - { - nama: "Joko Widodo", - jenis: "Keamanan", - waktu: "2 hari yang lalu", - status: "diproses", + title: "Selesai", + value: 84, + subtitle: "92% tingkat kepuasan", + icon: IconCheck, }, ]; - // Ide inovatif data - const ideInovatif = [ - { - nama: "Andi Prasetyo", - judul: "Penerapan Smart Village", - 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", - }, + // Line chart data for complaint trends + const trendData = [ + { month: "Jan", complaints: 32 }, + { month: "Feb", complaints: 45 }, + { month: "Mar", complaints: 38 }, + { month: "Apr", complaints: 52 }, + { month: "Mei", complaints: 48 }, + { month: "Jun", complaints: 61 }, ]; - const [activeTab, setActiveTab] = useState<"complaints" | "services">( - "complaints", - ); - const [newComplaint, setNewComplaint] = useState({ - title: "", - category: "", - description: "", - }); + // Horizontal bar chart data for most requested documents + const documentData = [ + { name: "KTP", count: 145 }, + { name: "Kartu Keluarga", count: 128 }, + { name: "Surat Domisili", count: 96 }, + { name: "Surat Usaha", count: 74 }, + { name: "SKCK", count: 52 }, + ]; - // Sample data for complaints - const complaints = [ + // Recent applications data + const recentApplications = [ { id: 1, - title: "Jalan Rusak di Jalan Raya", - category: "Infrastruktur", - status: "Pending", - priority: "High", - date: "2024-02-01", - reporter: "Bapak Ahmad", + name: "Budi Santoso", + type: "KTP Elektronik", + date: "10 Mar 2025", + status: "Selesai", + statusBg: "bg-darmasaba-success-100", + statusText: "text-darmasaba-success-800", }, { id: 2, - title: "Pemadaman Listrik Berkelanjutan", - category: "Utilitas", - status: "In Progress", - priority: "Medium", - date: "2024-02-03", - reporter: "Ibu Sari", + name: "Siti Aminah", + type: "Surat Domisili", + date: "10 Mar 2025", + status: "Diproses", + statusBg: "bg-darmasaba-warning-100", + statusText: "text-darmasaba-warning-800", }, { id: 3, - title: "Pelayanan Administrasi Lambat", - category: "Administrasi", - status: "Resolved", - priority: "Low", - date: "2024-01-28", - reporter: "Pak Joko", + name: "Ahmad Fauzi", + type: "Kartu Keluarga", + date: "9 Mar 2025", + status: "Baru", + statusBg: "bg-darmasaba-blue-100", + statusText: "text-darmasaba-blue-800", }, { id: 4, - title: "Kebersihan Lingkungan", - category: "Sanitasi", - status: "Pending", - priority: "Medium", - date: "2024-02-05", - reporter: "Bu Dewi", + name: "Dewi Lestari", + type: "Surat Usaha", + date: "9 Mar 2025", + status: "Selesai", + statusBg: "bg-darmasaba-success-100", + statusText: "text-darmasaba-success-800", + }, + { + id: 5, + name: "Joko Widodo", + type: "SKCK", + date: "8 Mar 2025", + status: "Diproses", + statusBg: "bg-darmasaba-warning-100", + statusText: "text-darmasaba-warning-800", }, ]; - // Sample data for public services - const services = [ + // Innovation ideas data + const innovationIdeas = [ { id: 1, - name: "Pembuatan KTP", - description: - "Pelayanan pembuatan Kartu Tanda Penduduk baru atau perpanjangan", - status: "Available", - category: "Administrasi", - lastUpdated: "2024-02-01", + title: "Sistem Antrian Online", + submitter: "Andi Prasetyo", + category: "Teknologi", }, { id: 2, - name: "Pembuatan Surat Keterangan Usaha", - description: "Surat keterangan untuk keperluan usaha atau perizinan", - status: "Available", - category: "Administrasi", - lastUpdated: "2024-02-02", + title: "Layanan Jemput Dokumen", + submitter: "Rina Kusuma", + category: "Pelayanan", }, { id: 3, - name: "Pelayanan Kesehatan", - description: "Pelayanan kesehatan dasar di puskesmas desa", - status: "Available", - category: "Kesehatan", - lastUpdated: "2024-01-30", + title: "Digitalisasi Arsip Desa", + submitter: "Bambang Suryono", + category: "Administrasi", }, { id: 4, - name: "Program Bantuan Sosial", - description: - "Informasi dan pendaftaran program bantuan sosial dari pemerintah", - status: "Limited", - category: "Sosial", - lastUpdated: "2024-02-04", + title: "Aplikasi Pengaduan Mobile", + submitter: "Lina Marlina", + category: "Teknologi", }, ]; - const handleInputChange = ( - e: React.ChangeEvent, - ) => { - const { name, value } = e.target; - setNewComplaint((prev) => ({ - ...prev, - [name]: value, - })); + const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD", "#DBEAFE"]; + + const cardStyle = { + backgroundColor: dark ? "#141D34" : "white", + border: `1px solid ${dark ? "#141D34" : "white"}`, }; - const handleSelectChange = (value: string | null) => { - setNewComplaint((prev) => ({ - ...prev, - category: value || "", // Ensure category is always a string - })); + const textStyle = { + color: dark ? "white" : "#1F2937", }; - const handleSubmitComplaint = (e: React.FormEvent) => { - e.preventDefault(); - console.log("Submitting complaint:", newComplaint); - // Here you would typically send the complaint to your backend - alert("Pengaduan berhasil dikirim!"); - setNewComplaint({ title: "", category: "", description: "" }); - }; - - // Render complaint table rows - const complaintRows = complaints.map((complaint) => ( - - - {complaint.title} - - - {complaint.category} - - - - {complaint.status} - - - - - {complaint.priority} - - - - {complaint.date} - - - )); - - // Status badge color mapping - const getStatusColor = (status: string) => { - switch (status) { - case "baru": - return "red"; - case "diproses": - return "yellow"; - case "selesai": - return "green"; - default: - return "gray"; - } + const subtitleStyle = { + color: dark ? "#9CA3AF" : "#6B7280", }; return ( - - {activeTab === "complaints" ? ( - <> - {/* Summary Cards */} - - - - - - - Total Pengaduan - - - {summaryData.total} - - - +
+ {/* Row 1: 4 Statistic Cards */} +
+ {statsData.map((stat, index) => ( +
+
+
+

- - - - - + {stat.title} +

+

+ {stat.value} +

+

+ {stat.subtitle} +

+
+
+
+ +
+
+
+
+ ))} +
- - - - - - Baru - - - {summaryData.baru} - - - - - - - - - - - - - - - Diproses - - - {summaryData.diproses} - - - - - - - - - - - - - - - Selesai - - - {summaryData.selesai} - - - - - - - - - - - {/* Grafik Tren Pengaduan */} - +

- - Grafik Tren Pengaduan - - - + Tren Pengaduan Warga +

+ + + + + + + + + +
+ + {/* Row 3: 3 Column Grid */} +
+ {/* Left: Most Requested Documents (Horizontal Bar Chart) */} +
+

+ Dokumen Paling Banyak Diminta +

+ + - - + + {documentData.map((entry, index) => ( + + ))} + + - +
- {/* Surat Terbanyak & Pengajuan Terbaru & Ide Inovatif */} - - {/* Surat Terbanyak */} - - - - Surat Terbanyak - - - - - - - - - - - - + {/* Middle: Recent Applications */} +
+

+ Pengajuan Terbaru +

+
+ {recentApplications.map((app) => ( +
+
+

+ {app.name} +

+

+ {app.type} +

+
+
+ + {app.status} + +

+ {app.date} +

+
+
+ ))} +
+
- {/* Pengajuan Terbaru */} - - - - Pengajuan Terbaru - - {pengajuanTerbaru.map((item, index) => ( - - - - - {item.nama} - - - {item.jenis} - - - - - {item.status} - - - {item.waktu} - - - - - - ))} - - - - {/* Ajuan Ide Inovatif */} - - - - Ajuan Ide Inovatif - - {ideInovatif.map((item, index) => ( - - - - - {item.judul} - - - {item.nama} - - - - - {item.kategori} - - - - - - - - - ))} - - -
- - {/* Complaint Submission Form and List */} - - {/* Complaint Submission Form */} - - - - - Ajukan Pengaduan - - - -
- - - -