From b673e36a45aa9597d717f7a8951cbbd3e54d39eb Mon Sep 17 00:00:00 2001 From: nico Date: Mon, 20 Apr 2026 17:15:54 +0800 Subject: [PATCH] feat(admin-ui): implement UMKM admin dashboard and CRUD pages --- MIND/PLAN/umkm-module.md | 6 + MIND/SUMMARY/umkm-module-summary.md | 8 +- package.json | 2 +- .../(dashboard)/_state/ekonomi/umkm/umkm.ts | 251 ++++++++++++++++++ .../ekonomi/umkm/_lib/layoutTabs.tsx | 168 ++++++++++++ .../ekonomi/umkm/dashboard/page.tsx | 140 ++++++++++ .../ekonomi/umkm/data-umkm/page.tsx | 105 ++++++++ .../admin/(dashboard)/ekonomi/umkm/layout.tsx | 28 ++ .../ekonomi/umkm/penjualan/page.tsx | 89 +++++++ .../(dashboard)/ekonomi/umkm/produk/page.tsx | 111 ++++++++ src/app/admin/_com/list_PageAdmin.tsx | 60 +++++ 11 files changed, 966 insertions(+), 2 deletions(-) create mode 100644 src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/_lib/layoutTabs.tsx create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/page.tsx create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/layout.tsx create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/penjualan/page.tsx create mode 100644 src/app/admin/(dashboard)/ekonomi/umkm/produk/page.tsx diff --git a/MIND/PLAN/umkm-module.md b/MIND/PLAN/umkm-module.md index d05bcb23..0afb2f03 100644 --- a/MIND/PLAN/umkm-module.md +++ b/MIND/PLAN/umkm-module.md @@ -22,3 +22,9 @@ Implement UMKM, ProdukUmkm, and PenjualanProduk module with CRUD API and Dashboa - [x] Step 6: Implement Dashboard API - [x] Step 7: Register routers - [x] Step 8: Verify changes +- [x] Step 9: Implement Admin UI Layout and Tabs +- [x] Step 10: Implement Dashboard UI Page +- [x] Step 11: Implement Data UMKM UI Page +- [x] Step 12: Implement Produk UI Page +- [x] Step 13: Implement Penjualan UI Page +- [x] Step 14: Register UI pages in Admin Menu diff --git a/MIND/SUMMARY/umkm-module-summary.md b/MIND/SUMMARY/umkm-module-summary.md index c0ea2321..41e56f9e 100644 --- a/MIND/SUMMARY/umkm-module-summary.md +++ b/MIND/SUMMARY/umkm-module-summary.md @@ -5,18 +5,24 @@ - Implemented a complete set of CRUD API endpoints for UMKM, Products, and Sales. - Developed a comprehensive Dashboard API providing KPIs, sales summaries, top products, and detailed stock analytics. - Integrated the new module into the existing `ekonomi` router. -- Verified the implementation with `tsc` to ensure type safety. +- Implemented the Admin UI with a modern tab-based layout. +- Created four main admin pages: Dashboard, Data UMKM, Produk, and Penjualan. +- Registered the new UMKM module in the Admin Navigation Menu for all roles. +- Verified the implementation with `tsc` and `bun run build`. ## Files Created/Modified ### Modified - `prisma/schema.prisma`: Added relations and models. - `src/app/api/[[...slugs]]/_lib/ekonomi/index.ts`: Registered new routers. +- `src/app/admin/_com/list_PageAdmin.tsx`: Registered new UI pages in menu. ### Created - `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/`: CRUD for UMKM. - `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/`: CRUD for Products. - `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/`: CRUD for Sales with stock management. - `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/`: Analytics endpoints. +- `src/app/admin/(dashboard)/ekonomi/umkm/`: Admin UI pages and layouts. +- `src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts`: Valtio state for the UMKM module. ## Stock Management Logic - Creating a sale decrements product stock. diff --git a/package.json b/package.json index 28f23820..93277391 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "desa-darmasaba", - "version": "0.1.12", + "version": "0.1.13", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts new file mode 100644 index 00000000..764f5dde --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts @@ -0,0 +1,251 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +// UMKM Form Validation +const umkmFormSchema = z.object({ + nama: z.string().min(1, "Nama minimal 1 karakter"), + pemilik: z.string().min(1, "Nama pemilik wajib diisi"), + kategoriId: z.string().min(1, "Kategori wajib dipilih"), + deskripsi: z.string().optional(), + alamat: z.string().optional(), + kontak: z.string().optional(), + imageId: z.string().optional(), +}); + +const defaultUmkmForm = { + nama: "", + pemilik: "", + kategoriId: "", + deskripsi: "", + alamat: "", + kontak: "", + imageId: "", + isActive: true, +}; + +// Produk Form Validation +const produkFormSchema = z.object({ + nama: z.string().min(1, "Nama produk minimal 1 karakter"), + harga: z.number().min(0, "Harga tidak boleh negatif"), + stok: z.number().min(0, "Stok tidak boleh negatif"), + umkmId: z.string().min(1, "UMKM wajib dipilih"), + deskripsi: z.string().optional(), + imageId: z.string().optional(), +}); + +const defaultProdukForm = { + nama: "", + harga: 0, + stok: 0, + umkmId: "", + deskripsi: "", + imageId: "", + isActive: true, +}; + +// Penjualan Form Validation +const penjualanFormSchema = z.object({ + produkId: z.string().min(1, "Produk wajib dipilih"), + jumlah: z.number().min(1, "Jumlah minimal 1"), + hargaSatuan: z.number().min(0, "Harga tidak boleh negatif"), + tanggal: z.string().optional(), +}); + +const defaultPenjualanForm = { + produkId: "", + jumlah: 0, + hargaSatuan: 0, + tanggal: new Date().toISOString().split('T')[0], + isActive: true, +}; + +export const umkmState = proxy({ + // UMKM Module + umkm: { + findMany: { + data: [] as any[], + page: 1, + totalPages: 1, + loading: false, + search: "", + async load(page = 1, limit = 10, search = "", kategoriId = "") { + this.loading = true; + this.page = page; + this.search = search; + try { + const params = new URLSearchParams({ + page: page.toString(), + limit: limit.toString(), + search, + kategoriId + }); + const res = await fetch(`/api/ekonomi/umkm/find-many?${params}`); + const result = await res.json(); + if (result.success) { + this.data = result.data; + this.totalPages = result.totalPages; + } + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } + }, + create: { + form: { ...defaultUmkmForm }, + loading: false, + async submit() { + const cek = umkmFormSchema.safeParse(this.form); + if (!cek.success) return toast.error("Cek kembali form anda"); + this.loading = true; + try { + const res = await fetch("/api/ekonomi/umkm/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form) + }); + const result = await res.json(); + if (result.success) { + toast.success("UMKM berhasil dibuat"); + umkmState.umkm.findMany.load(); + return true; + } + toast.error(result.message); + } catch (e) { + toast.error("Gagal membuat UMKM"); + } finally { + this.loading = false; + } + return false; + } + } + }, + + // Produk Module + produk: { + findMany: { + data: [] as any[], + page: 1, + totalPages: 1, + loading: false, + async load(page = 1, limit = 10, search = "", umkmId = "") { + this.loading = true; + try { + const params = new URLSearchParams({ page: page.toString(), limit: limit.toString(), search, umkmId }); + const res = await fetch(`/api/ekonomi/umkm/produk/find-many?${params}`); + const result = await res.json(); + if (result.success) { + this.data = result.data; + this.totalPages = result.totalPages; + } + } catch (e) { console.error(e); } finally { this.loading = false; } + } + }, + create: { + form: { ...defaultProdukForm }, + loading: false, + async submit() { + const cek = produkFormSchema.safeParse(this.form); + if (!cek.success) return toast.error("Cek kembali form anda"); + this.loading = true; + try { + const res = await fetch("/api/ekonomi/umkm/produk/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form) + }); + const result = await res.json(); + if (result.success) { + toast.success("Produk berhasil dibuat"); + umkmState.produk.findMany.load(); + return true; + } + } catch (e) { toast.error("Gagal membuat produk"); } finally { this.loading = false; } + return false; + } + } + }, + + // Penjualan Module + penjualan: { + findMany: { + data: [] as any[], + page: 1, + totalPages: 1, + loading: false, + async load(page = 1, limit = 10, produkId = "", periode = "") { + this.loading = true; + try { + const params = new URLSearchParams({ page: page.toString(), limit: limit.toString(), produkId, periode }); + const res = await fetch(`/api/ekonomi/umkm/penjualan/find-many?${params}`); + const result = await res.json(); + if (result.success) { + this.data = result.data; + this.totalPages = result.totalPages; + } + } catch (e) { console.error(e); } finally { this.loading = false; } + } + }, + create: { + form: { ...defaultPenjualanForm }, + loading: false, + async submit() { + const cek = penjualanFormSchema.safeParse(this.form); + if (!cek.success) return toast.error("Cek kembali form anda"); + this.loading = true; + try { + const res = await fetch("/api/ekonomi/umkm/penjualan/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form) + }); + const result = await res.json(); + if (result.success) { + toast.success("Penjualan berhasil dicatat"); + umkmState.penjualan.findMany.load(); + return true; + } + } catch (e) { toast.error("Gagal mencatat penjualan"); } finally { this.loading = false; } + return false; + } + } + }, + + // Dashboard Module + dashboard: { + kpi: { data: null as any, loading: false }, + summary: { data: null as any, loading: false }, + topProduk: { data: [] as any[], loading: false }, + detail: { data: [] as any[], loading: false }, + async loadAll(periode = "") { + const p = periode || `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`; + this.kpi.loading = true; + this.summary.loading = true; + this.topProduk.loading = true; + this.detail.loading = true; + try { + const [kpi, sum, top, det] = await Promise.all([ + fetch(`/api/ekonomi/umkm/dashboard/kpi?periode=${p}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/ringkasan-penjualan?periode=${p}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/top-produk?periode=${p}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/detail-penjualan?periode=${p}`).then(r => r.json()) + ]); + if (kpi.success) this.kpi.data = kpi.data; + if (sum.success) this.summary.data = sum.data; + if (top.success) this.topProduk.data = top.data; + if (det.success) this.detail.data = det.data; + } catch (e) { console.error(e); } finally { + this.kpi.loading = false; + this.summary.loading = false; + this.topProduk.loading = false; + this.detail.loading = false; + } + } + } +}); + +export default umkmState; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/_lib/layoutTabs.tsx new file mode 100644 index 00000000..26a79fe9 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/_lib/layoutTabs.tsx @@ -0,0 +1,168 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import colors from '@/con/colors'; +import { + Box, + ScrollArea, + Stack, + Tabs, + TabsList, + TabsPanel, + TabsTab, + Title +} from '@mantine/core'; +import { IconDashboard, IconBuildingStore, IconPackage, IconShoppingCart } from '@tabler/icons-react'; +import { usePathname, useRouter } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; + +function LayoutTabs({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const pathname = usePathname(); + + const tabs = [ + { + label: "Dashboard", + value: "dashboard", + href: "/admin/ekonomi/umkm/dashboard", + icon: + }, + { + label: "Data UMKM", + value: "data-umkm", + href: "/admin/ekonomi/umkm/data-umkm", + icon: + }, + { + label: "Produk", + value: "produk", + href: "/admin/ekonomi/umkm/produk", + icon: + }, + { + label: "Penjualan", + value: "penjualan", + href: "/admin/ekonomi/umkm/penjualan", + icon: + }, + ]; + + const currentTab = tabs.find((tab) => pathname.startsWith(tab.href)); + const [activeTab, setActiveTab] = useState( + currentTab?.value || tabs[0].value + ); + + const handleTabChange = (value: string | null) => { + const tab = tabs.find((t) => t.value === value); + if (tab) { + router.push(tab.href); + } + setActiveTab(value); + }; + + useEffect(() => { + const match = tabs.find((tab) => pathname.startsWith(tab.href)); + if (match) { + setActiveTab(match.value); + } + }, [pathname]); + + return ( + + + Manajemen UMKM + + + + + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + + + + + + {tabs.map((tab, i) => ( + + {tab.label} + + ))} + + + + + {tabs.map((tab, i) => ( + + {children} + + ))} + + + ); +} + +export default LayoutTabs; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx new file mode 100644 index 00000000..45f70cd4 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx @@ -0,0 +1,140 @@ +'use client' +import colors from '@/con/colors'; +import { + Box, + Card, + Grid, + Group, + SimpleGrid, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + Badge +} from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconArrowUpRight, IconArrowDownRight, IconMinus } from '@tabler/icons-react'; +import { useProxy } from 'valtio/utils'; +import umkmState from '../../../_state/ekonomi/umkm/umkm'; + +function UmkmDashboard() { + const state = useProxy(umkmState.dashboard); + + useShallowEffect(() => { + state.loadAll(); + }, []); + + if (state.kpi.loading || !state.kpi.data) { + return ; + } + + const kpi = state.kpi.data; + const summary = state.summary.data; + const topProduk = state.topProduk.data; + const detail = state.detail.data; + + return ( + + + + + + + + + + + + Top 3 Produk + + {topProduk.map((item, i) => ( + + + {item.namaProduk} + {item.namaUmkm} + + Rp {item.totalPenjualan.toLocaleString()} + + ))} + + + + + + + Detail Penjualan & Stok + + + + + Produk + Penjualan + Trend + Stok + Status + + + + {detail.map((item, i) => ( + + {item.namaProduk} + Rp {item.penjualanBulanIni.toLocaleString()} + {renderTrend(item.trend)} + {item.stok} + + + {item.statusStok} + + + + ))} + +
+
+
+
+
+
+ ); +} + +function KpiCard({ title, value, subValue, trend }: any) { + return ( + + {title} + + {value} + {trend !== undefined && ( + = 0 ? 'teal' : 'red'} fz="sm" fw={500}> + {trend >= 0 ? '+' : ''}{trend}% + + )} + + {subValue && {subValue}} + + ); +} + +function renderTrend(trend: string) { + if (trend === 'up') return ; + if (trend === 'down') return ; + return ; +} + +function getStatusColor(status: string) { + if (status === 'Aman') return 'green'; + if (status === 'Menipis') return 'yellow'; + return 'red'; +} + +export default UmkmDashboard; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/page.tsx new file mode 100644 index 00000000..6a46d4d1 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/data-umkm/page.tsx @@ -0,0 +1,105 @@ +'use client' +import colors from '@/con/colors'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + TextInput +} from '@mantine/core'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +import { IconPlus, IconSearch, IconEdit, IconTrash } from '@tabler/icons-react'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import umkmState from '../../../_state/ekonomi/umkm/umkm'; + +function DataUmkm() { + const [search, setSearch] = useState(""); + const state = useProxy(umkmState.umkm.findMany); + const [debouncedSearch] = useDebouncedValue(search, 1000); + + useShallowEffect(() => { + state.load(state.page, 10, debouncedSearch); + }, [state.page, debouncedSearch]); + + return ( + + + Data UMKM + + + + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + mb="md" + /> + + {state.loading ? ( + + ) : ( + + + + + Nama UMKM + Pemilik + Kategori + Kontak + Aksi + + + + {state.data.map((item) => ( + + {item.nama} + {item.pemilik} + {item.kategori?.nama || '-'} + {item.kontak || '-'} + + + + + + + + ))} + +
+
+ )} + +
+ state.load(p, 10, debouncedSearch)} + /> +
+
+
+ ); +} + +export default DataUmkm; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/layout.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/layout.tsx new file mode 100644 index 00000000..2afcd91e --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/layout.tsx @@ -0,0 +1,28 @@ +'use client' + +import { usePathname } from "next/navigation"; +import LayoutTabs from "./_lib/layoutTabs" +import { Box } from "@mantine/core"; + + +export default function Layout({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + + const segments = pathname.split('/').filter(Boolean); + // Path /admin/ekonomi/umkm/dashboard -> length 4 + // Path detail usually adds an ID -> length >= 5 + const isDetailPage = segments.length >= 5; + + if (isDetailPage) { + return ( + + {children} + + ); + } + return ( + + {children} + + ) +} diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/penjualan/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/penjualan/page.tsx new file mode 100644 index 00000000..2543b131 --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/penjualan/page.tsx @@ -0,0 +1,89 @@ +'use client' +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title +} from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconPlus, IconTrash } from '@tabler/icons-react'; +import { useProxy } from 'valtio/utils'; +import umkmState from '../../../_state/ekonomi/umkm/umkm'; + +function PenjualanUmkm() { + const state = useProxy(umkmState.penjualan.findMany); + + useShallowEffect(() => { + state.load(state.page, 10); + }, [state.page]); + + return ( + + + Histori Penjualan UMKM + + + + + {state.loading ? ( + + ) : ( + + + + + Tanggal + Produk + UMKM + Jumlah + Total Nilai + Aksi + + + + {state.data.map((item) => ( + + {new Date(item.tanggal).toLocaleDateString('id-ID')} + {item.produk?.nama} + {item.produk?.umkm?.nama} + {item.jumlah} + Rp {item.totalNilai.toLocaleString()} + + + + + ))} + +
+
+ )} + +
+ state.load(p, 10)} + /> +
+
+
+ ); +} + +export default PenjualanUmkm; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/produk/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/produk/page.tsx new file mode 100644 index 00000000..891b4efc --- /dev/null +++ b/src/app/admin/(dashboard)/ekonomi/umkm/produk/page.tsx @@ -0,0 +1,111 @@ +'use client' +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title, + TextInput, + Badge +} from '@mantine/core'; +import { useDebouncedValue, useShallowEffect } from '@mantine/hooks'; +import { IconPlus, IconSearch, IconEdit, IconTrash } from '@tabler/icons-react'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import umkmState from '../../../_state/ekonomi/umkm/umkm'; + +function ProdukUmkm() { + const [search, setSearch] = useState(""); + const state = useProxy(umkmState.produk.findMany); + const [debouncedSearch] = useDebouncedValue(search, 1000); + + useShallowEffect(() => { + state.load(state.page, 10, debouncedSearch); + }, [state.page, debouncedSearch]); + + return ( + + + Daftar Produk UMKM + + + + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + mb="md" + /> + + {state.loading ? ( + + ) : ( + + + + + Nama Produk + UMKM + Harga + Stok + Status Stok + Aksi + + + + {state.data.map((item) => ( + + {item.nama} + {item.umkm?.nama || '-'} + Rp {item.harga.toLocaleString()} + {item.stok} + + + {item.stok < 5 ? 'Rendah' : item.stok < 20 ? 'Menipis' : 'Aman'} + + + + + + + + + + ))} + +
+
+ )} + +
+ state.load(p, 10, debouncedSearch)} + /> +
+
+
+ ); +} + +export default ProdukUmkm; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index c514bdef..88779c9e 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -206,6 +206,26 @@ export const devBar = [ name: "Ekonomi", path: "", children: [ + { + id: "Ekonomi_UMKM_1", + name: "UMKM - Dashboard", + path: "/admin/ekonomi/umkm/dashboard" + }, + { + id: "Ekonomi_UMKM_2", + name: "UMKM - Data UMKM", + path: "/admin/ekonomi/umkm/data-umkm" + }, + { + id: "Ekonomi_UMKM_3", + name: "UMKM - Produk", + path: "/admin/ekonomi/umkm/produk" + }, + { + id: "Ekonomi_UMKM_4", + name: "UMKM - Penjualan", + path: "/admin/ekonomi/umkm/penjualan" + }, { id: "Ekonomi_1", name: "Pasar Desa", @@ -637,6 +657,26 @@ export const navBar = [ name: "Ekonomi", path: "", children: [ + { + id: "Ekonomi_UMKM_1", + name: "UMKM - Dashboard", + path: "/admin/ekonomi/umkm/dashboard" + }, + { + id: "Ekonomi_UMKM_2", + name: "UMKM - Data UMKM", + path: "/admin/ekonomi/umkm/data-umkm" + }, + { + id: "Ekonomi_UMKM_3", + name: "UMKM - Produk", + path: "/admin/ekonomi/umkm/produk" + }, + { + id: "Ekonomi_UMKM_4", + name: "UMKM - Penjualan", + path: "/admin/ekonomi/umkm/penjualan" + }, { id: "Ekonomi_1", name: "Pasar Desa", @@ -1026,6 +1066,26 @@ export const role1 = [ name: "Ekonomi", path: "", children: [ + { + id: "Ekonomi_UMKM_1", + name: "UMKM - Dashboard", + path: "/admin/ekonomi/umkm/dashboard" + }, + { + id: "Ekonomi_UMKM_2", + name: "UMKM - Data UMKM", + path: "/admin/ekonomi/umkm/data-umkm" + }, + { + id: "Ekonomi_UMKM_3", + name: "UMKM - Produk", + path: "/admin/ekonomi/umkm/produk" + }, + { + id: "Ekonomi_UMKM_4", + name: "UMKM - Penjualan", + path: "/admin/ekonomi/umkm/penjualan" + }, { id: "Ekonomi_1", name: "Pasar Desa",