Compare commits
149 Commits
nico/11-au
...
nico/6-jan
| Author | SHA1 | Date | |
|---|---|---|---|
| 503da91ce6 | |||
| daaed8089b | |||
| f436aa2ef0 | |||
| 50bc54ceca | |||
| f0f201c853 | |||
| 29065cb3e2 | |||
| bf20cd55e8 | |||
| af60bcd6fc | |||
| dc8793e3ae | |||
| c8484357cb | |||
| 342e9bbc65 | |||
| f6f77d9e35 | |||
| a00481152c | |||
| 242ea86f77 | |||
| 99c2c9c6d7 | |||
| ac2fc1a705 | |||
| 9dbe172165 | |||
| cc318d4d54 | |||
| dcb8017594 | |||
| ec3ad12531 | |||
| dad44c0537 | |||
| 867dce42f0 | |||
| 7bb17ddf22 | |||
| a4069d3cba | |||
| ffe5e6dd9f | |||
| dcf195f54f | |||
| c03a6b3aed | |||
| 1bb9f239db | |||
| a213ff7d37 | |||
| 0018bdc251 | |||
| 83fb39a957 | |||
| 7238692dd0 | |||
| 8b50139d79 | |||
| 066180fc0e | |||
| 67f29aabef | |||
| dbf7c34228 | |||
| 036fc86fed | |||
| 2cecec733e | |||
| c64a2e5457 | |||
| 757911d7dd | |||
| 54232e4465 | |||
| 29a9a59bca | |||
| 2fb3666e57 | |||
| e30b27f7a4 | |||
| e941ed3893 | |||
| ace5aff1b6 | |||
| 716db0adca | |||
| a291bdfb51 | |||
| 0dff8f3254 | |||
| 78b8aa74cd | |||
| a0537810e8 | |||
| b3c169a2d4 | |||
| 2608a5ffdd | |||
| 6c32f3ebdb | |||
| 0feeb4de93 | |||
| 9622eb5a9a | |||
| 417a8937f5 | |||
| db8909b9ed | |||
| f66a46f645 | |||
| fb57698dc9 | |||
| d128313e71 | |||
| 7b4bb1e58e | |||
| 0befe6a3f2 | |||
| a6663bbcee | |||
| ed371bd0d9 | |||
| f82c7b86e0 | |||
| b5d6585cd5 | |||
| aa98359ef7 | |||
| 0ff0d5234a | |||
| 827c1c191a | |||
| fb596f9033 | |||
| 9055b40769 | |||
| bbf13c1cf7 | |||
| 75bf0652b1 | |||
| 0b574406e2 | |||
| ccf39bc778 | |||
| 3c21f7742c | |||
| a158241c0b | |||
| 80c5dc6361 | |||
| 8ad38fc907 | |||
| d601b2fee3 | |||
| cee0957e07 | |||
| 5c66eccf23 | |||
| f7fd9be255 | |||
| 8a6d8ed8db | |||
| 63054cedf0 | |||
| c2f1ab8179 | |||
| 295d6f7d63 | |||
| dbd56a1493 | |||
| 2a26db6e17 | |||
| 33fc472472 | |||
| d8fa56d923 | |||
| cac146471a | |||
| 3e4a7a1c0a | |||
| b5c044df6e | |||
| 0fc47c28ff | |||
| 8e25c91e85 | |||
| 068d8b1077 | |||
| 9f72e94557 | |||
| 79ad39fc55 | |||
| 39e1e7b575 | |||
| 4ceea5203f | |||
| a5d841bb6b | |||
| 6a7bd386ae | |||
| a9d98895bb | |||
| 75475dc62e | |||
| b39800a475 | |||
| 797713ef49 | |||
| 8817b937b1 | |||
| 2adf60f9eb | |||
| fa9601e126 | |||
| 7ae83788b4 | |||
| 22ec8d942d | |||
| 9f9a0fb451 | |||
| b6d6583e77 | |||
| a8fd715822 | |||
| f9530c32eb | |||
| f15ef5a275 | |||
| 3a726a3334 | |||
| b21e1f0c2e | |||
| f63249327d | |||
| bb8dab05ba | |||
| 3081e426bd | |||
| 8a275c2a32 | |||
| 8469ebd2e1 | |||
| 760ba4b6d2 | |||
| 20d4c90e60 | |||
| fafbb12a08 | |||
| 01aa0da5cc | |||
| b580978f8e | |||
| 1c01397c0d | |||
| 90a6605efd | |||
| c22d865283 | |||
| 49067f0218 | |||
| d79425d529 | |||
| 4491d23bea | |||
| 1e154ced86 | |||
| bcc51aec12 | |||
| 8d15563f15 | |||
| d7a592c635 | |||
| 5e137ba658 | |||
| c99416c7f8 | |||
| 212e2db1fb | |||
| b8a45bc451 | |||
| 0777b00a7d | |||
| a035039b2c | |||
| a6832cad40 | |||
| a1d55e2b0a | |||
| c1583c21b1 |
5
.gitignore
vendored
@@ -41,6 +41,9 @@ next-env.d.ts
|
||||
# uploads
|
||||
/uploads
|
||||
|
||||
# download
|
||||
/download
|
||||
|
||||
# cache
|
||||
/cache
|
||||
|
||||
@@ -48,3 +51,5 @@ next-env.d.ts
|
||||
|
||||
.env.*
|
||||
|
||||
*.tar.gz
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {},
|
||||
allowedDevOrigins: [
|
||||
"http://192.168.1.82:3000", // buat akses dari HP/device lain
|
||||
"http://localhost:3000", // akses lokal
|
||||
],
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
30
package.json
@@ -3,11 +3,9 @@
|
||||
"version": "0.1.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"prisma:seed": "bun run prisma/seed.ts"
|
||||
"start": "next start"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "bun run prisma/seed.ts"
|
||||
@@ -21,6 +19,7 @@
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@elysiajs/stream": "^1.1.0",
|
||||
"@elysiajs/swagger": "^1.2.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@mantine/carousel": "^7.16.2",
|
||||
"@mantine/charts": "^7.17.1",
|
||||
"@mantine/core": "^7.17.4",
|
||||
@@ -28,6 +27,7 @@
|
||||
"@mantine/dropzone": "^8.1.1",
|
||||
"@mantine/form": "^8.1.0",
|
||||
"@mantine/hooks": "^7.17.4",
|
||||
"@mantine/modals": "^8.3.6",
|
||||
"@mantine/tiptap": "^7.17.4",
|
||||
"@paljs/types": "^8.1.0",
|
||||
"@prisma/client": "^6.3.1",
|
||||
@@ -41,22 +41,32 @@
|
||||
"@tiptap/pm": "^2.11.7",
|
||||
"@tiptap/react": "^2.11.7",
|
||||
"@tiptap/starter-kit": "^2.11.7",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/bun": "^1.2.2",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/nodemailer": "^7.0.2",
|
||||
"add": "^2.0.6",
|
||||
"adm-zip": "^0.5.16",
|
||||
"animate.css": "^4.1.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bun": "^1.2.2",
|
||||
"chart.js": "^4.4.8",
|
||||
"classnames": "^2.5.1",
|
||||
"colors": "^1.4.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^17.2.3",
|
||||
"elysia": "^1.3.5",
|
||||
"embla-carousel-autoplay": "^8.5.2",
|
||||
"embla-carousel-react": "^7.1.0",
|
||||
"embla-carousel": "^8.6.0",
|
||||
"embla-carousel-autoplay": "^8.6.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"form-data": "^4.0.2",
|
||||
"framer-motion": "^12.23.5",
|
||||
"get-port": "^7.1.0",
|
||||
"iron-session": "^8.0.4",
|
||||
"jose": "^6.1.0",
|
||||
"jotai": "^2.12.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
@@ -64,24 +74,30 @@
|
||||
"lodash": "^4.17.21",
|
||||
"motion": "^12.4.1",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.1.6",
|
||||
"next": "^15.5.2",
|
||||
"next-view-transitions": "^0.3.4",
|
||||
"node-fetch": "^3.3.2",
|
||||
"nodemailer": "^7.0.10",
|
||||
"p-limit": "^6.2.0",
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.6",
|
||||
"prisma": "^6.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-exif-orientation-img": "^0.1.5",
|
||||
"react-international-phone": "^4.6.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-simple-toasts": "^6.1.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-zoom-pan-pinch": "^3.7.0",
|
||||
"readdirp": "^4.1.1",
|
||||
"recharts": "^2.15.3",
|
||||
"sharp": "^0.34.3",
|
||||
"swr": "^2.3.2",
|
||||
"uuid": "^11.1.0",
|
||||
"valtio": "^2.1.3",
|
||||
"zlib": "^1.0.5",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
/* Mobile first */
|
||||
'mantine-breakpoint-xs': '30em', // 480px → mobile kecil–normal
|
||||
'mantine-breakpoint-sm': '48em', // 768px → tablet / mobile landscape
|
||||
'mantine-breakpoint-md': '64em', // 1024px → laptop & desktop kecil
|
||||
'mantine-breakpoint-lg': '80em', // 1280px → desktop standar
|
||||
'mantine-breakpoint-xl': '90em', // 1440px+ → desktop besar
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[
|
||||
{ "name": "Semua" },
|
||||
{ "name": "Pemerintahan" },
|
||||
{ "name": "Pembangunan" },
|
||||
{ "name": "Ekonomi" },
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"name": "Pelayanan Penduduk Non-Permanent",
|
||||
"deskripsi": "<p>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</p>"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
|
||||
"deskripsi": "<p>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</p>",
|
||||
"link" : "https://oss.go.id/"
|
||||
|
||||
@@ -1,51 +1,99 @@
|
||||
[
|
||||
{
|
||||
"month": "Jan",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 160,
|
||||
"educatedUnemployment": 95,
|
||||
"uneducatedUnemployment": 65,
|
||||
"percentageChange": null
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 155,
|
||||
"educatedUnemployment": 90,
|
||||
"uneducatedUnemployment": 65,
|
||||
"percentageChange": -3.1
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 150,
|
||||
"educatedUnemployment": 88,
|
||||
"uneducatedUnemployment": 62,
|
||||
"percentageChange": -3.2
|
||||
},
|
||||
{
|
||||
"month": "Apr",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 148,
|
||||
"educatedUnemployment": 85,
|
||||
"uneducatedUnemployment": 63,
|
||||
"percentageChange": -1.3
|
||||
},
|
||||
{
|
||||
"month": "Mei",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 145,
|
||||
"educatedUnemployment": 82,
|
||||
"uneducatedUnemployment": 63,
|
||||
"percentageChange": -2.0
|
||||
},
|
||||
{
|
||||
"month": "Jun",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 140,
|
||||
"educatedUnemployment": 80,
|
||||
"uneducatedUnemployment": 60,
|
||||
"percentageChange": -3.4
|
||||
}
|
||||
]
|
||||
{
|
||||
"month": "Jan",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 160,
|
||||
"educatedUnemployment": 95,
|
||||
"uneducatedUnemployment": 65,
|
||||
"percentageChange": 0.0
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 158,
|
||||
"educatedUnemployment": 93,
|
||||
"uneducatedUnemployment": 65,
|
||||
"percentageChange": -1.25
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 155,
|
||||
"educatedUnemployment": 91,
|
||||
"uneducatedUnemployment": 64,
|
||||
"percentageChange": -1.90
|
||||
},
|
||||
{
|
||||
"month": "Apr",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 152,
|
||||
"educatedUnemployment": 89,
|
||||
"uneducatedUnemployment": 63,
|
||||
"percentageChange": -1.94
|
||||
},
|
||||
{
|
||||
"month": "Mei",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 150,
|
||||
"educatedUnemployment": 88,
|
||||
"uneducatedUnemployment": 62,
|
||||
"percentageChange": -1.32
|
||||
},
|
||||
{
|
||||
"month": "Jun",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 148,
|
||||
"educatedUnemployment": 87,
|
||||
"uneducatedUnemployment": 61,
|
||||
"percentageChange": -1.33
|
||||
},
|
||||
{
|
||||
"month": "Jul",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 145,
|
||||
"educatedUnemployment": 85,
|
||||
"uneducatedUnemployment": 60,
|
||||
"percentageChange": -2.03
|
||||
},
|
||||
{
|
||||
"month": "Agu",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 142,
|
||||
"educatedUnemployment": 84,
|
||||
"uneducatedUnemployment": 58,
|
||||
"percentageChange": -2.07
|
||||
},
|
||||
{
|
||||
"month": "Sep",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 140,
|
||||
"educatedUnemployment": 83,
|
||||
"uneducatedUnemployment": 57,
|
||||
"percentageChange": -1.41
|
||||
},
|
||||
{
|
||||
"month": "Okt",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 138,
|
||||
"educatedUnemployment": 82,
|
||||
"uneducatedUnemployment": 56,
|
||||
"percentageChange": -1.43
|
||||
},
|
||||
{
|
||||
"month": "Nov",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 135,
|
||||
"educatedUnemployment": 80,
|
||||
"uneducatedUnemployment": 55,
|
||||
"percentageChange": -2.17
|
||||
},
|
||||
{
|
||||
"month": "Des",
|
||||
"year": 2025,
|
||||
"totalUnemployment": 132,
|
||||
"educatedUnemployment": 78,
|
||||
"uneducatedUnemployment": 54,
|
||||
"percentageChange": -2.22
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "650e8400-e29b-41d4-a716-446655440001",
|
||||
"atasanId": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"bawahanId": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"tipe": "Langsung Melapor"
|
||||
}
|
||||
]
|
||||
91
prisma/data/ekonomi/struktur-organisasi/pegawai-bumdes.json
Normal file
@@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"id": "cmgewz4gt000704ib91i3f169",
|
||||
"namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.",
|
||||
"gelarAkademik": "S.H.,M.H.,NL.P.",
|
||||
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
|
||||
"email": "bagus@desa.id",
|
||||
"telepon": "081234567891",
|
||||
"alamat": "Jl. Raya Desa No. 1",
|
||||
"posisiId": "kepala_desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewxfvw000004ibee5013f4",
|
||||
"namaLengkap": "I Ketut Suwanta",
|
||||
"gelarAkademik": "S.Pt",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "suwanta@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "sekretaris_desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewxvqw000104ibgm5l8fzs",
|
||||
"namaLengkap": "Ni Wayan Supardiati",
|
||||
"gelarAkademik": "S.Pd",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "supardiati@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kaur_keuangan",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewy1g9000204ib2n7hbx0i",
|
||||
"namaLengkap": "I Wayan Agus Juni Artha Saputra",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "agus@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_menesa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewybah000304ibgqhn1gm2",
|
||||
"namaLengkap": "I Wayan Sueca",
|
||||
"gelarAkademik": "S.H.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "sueca@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_darmasaba",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewygqz000404ib20sv8nvg",
|
||||
"namaLengkap": "Si Gede Ketut Astawa",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "astawa@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_bucu",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewyos1000504ibcu8o2gyk",
|
||||
"namaLengkap": "I Kadek Arya Minarta",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "minarta@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_gulingan",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "cmgewyxk7000604ib8djs3i6c",
|
||||
"namaLengkap": "I Gede Andika Pradnya Diputra",
|
||||
"gelarAkademik": "S.E.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "diputra@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_taman",
|
||||
"isActive": true
|
||||
}
|
||||
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"namaLengkap": "Budi Santoso",
|
||||
"gelarAkademik": "S.IP",
|
||||
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
|
||||
"email": "budi@desa.id",
|
||||
"telepon": "081234567891",
|
||||
"alamat": "Jl. Raya Desa No. 1",
|
||||
"posisiId": "kepala_desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"namaLengkap": "Ani Lestari",
|
||||
"gelarAkademik": "S.Pd",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "ani@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "sekretaris_desa",
|
||||
"isActive": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,159 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"id": "kepala_desa",
|
||||
"nama": "Kepala Desa",
|
||||
"deskripsi": "Pemimpin desa Darmasaba",
|
||||
"hierarki": 1,
|
||||
"parentId": null
|
||||
},
|
||||
{
|
||||
"id": "kepala_urusan",
|
||||
"nama": "Kepala Urusan",
|
||||
"deskripsi": "Pemimpin urusan desa Darmasaba",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "sekretaris_desa",
|
||||
"nama": "Sekretaris Desa",
|
||||
"deskripsi": "Pengelola administrasi desa",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kaur_keuangan",
|
||||
"nama": "Kaur Keuangan",
|
||||
"deskripsi": "Pengelola keuangan desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "kaur_umum"
|
||||
},
|
||||
{
|
||||
"id": "kaur_perencanaan",
|
||||
"nama": "Kaur Perencanaan",
|
||||
"deskripsi": "Penyusun program kerja desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "kaur_umum"
|
||||
},
|
||||
{
|
||||
"id": "kaur_umum",
|
||||
"nama": "Kaur Umum & TU",
|
||||
"deskripsi": "Pelayanan umum dan administrasi",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_pemerintahan",
|
||||
"nama": "Kasi Pemerintahan",
|
||||
"deskripsi": "Urusan pemerintahan dan keamanan",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_pelayanan",
|
||||
"nama": "Kasi Pelayanan",
|
||||
"deskripsi": "Urusan pelayanan masyarakat",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_kesejahteraan",
|
||||
"nama": "Kasi Kesejahteraan",
|
||||
"deskripsi": "Urusan sosial dan kesejahteraan",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_cabe",
|
||||
"nama": "Kepala Dusun Banjar Dinas Cabe",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Cabe",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_menesa",
|
||||
"nama": "Kepala Dusun Banjar Dinas Menesa",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Menesa",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_penenjoan",
|
||||
"nama": "Kepala Dusun Banjar Dinas Penenjoan",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Penenjoan",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_telanga",
|
||||
"nama": "Kepala Dusun Banjar Dinas Telanga",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Telanga",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_tengah",
|
||||
"nama": "Kepala Dusun Banjar Dinas Tengah",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Tengah",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_baler_pasar",
|
||||
"nama": "Kepala Dusun Banjar Dinas Baler Pasar",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Baler Pasar",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_bucu",
|
||||
"nama": "Kepala Dusun Banjar Dinas Bucu",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Bucu",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_gulingan",
|
||||
"nama": "Kepala Dusun Banjar Dinas Gulingan",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Gulingan",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_bersih",
|
||||
"nama": "Kepala Dusun Banjar Dinas Bersih",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Bersih",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_umahanyar",
|
||||
"nama": "Kepala Dusun Banjar Dinas Umahanyar",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Umahanyar",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_taman",
|
||||
"nama": "Kepala Dusun Banjar Dinas Taman",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Taman",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_darmasaba",
|
||||
"nama": "Kepala Dusun Banjar Dinas Darmasaba",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Darmasaba",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "staf_desa",
|
||||
"nama": "Staf Desa",
|
||||
"deskripsi": "Staf Desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "kepala_desa",
|
||||
"nama": "Kepala Desa",
|
||||
"deskripsi": "Kepala Desa",
|
||||
"hierarki": 1
|
||||
},
|
||||
{
|
||||
"id": "sekretaris_desa",
|
||||
"nama": "Sekretaris Desa",
|
||||
"deskripsi": "Sekretaris Desa",
|
||||
"hierarki": 2
|
||||
},
|
||||
{
|
||||
"id": "bendahara_desa",
|
||||
"nama": "Bendahara Desa",
|
||||
"deskripsi": "Bendahara Desa",
|
||||
"hierarki": 3
|
||||
},
|
||||
{
|
||||
"id": "staff_umum",
|
||||
"nama": "Staff Umum",
|
||||
"deskripsi": "Staff Umum",
|
||||
"hierarki": 4
|
||||
}
|
||||
]
|
||||
|
||||
30
prisma/data/fetchWithRetry.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export default async function fetchWithRetry(
|
||||
url: string,
|
||||
retries = 3,
|
||||
timeoutMs = 20000
|
||||
) {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { signal: controller.signal });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ Download attempt ${attempt} failed`);
|
||||
|
||||
if (attempt === retries) {
|
||||
throw err;
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
120
prisma/data/file-storage.json
Normal file
@@ -0,0 +1,120 @@
|
||||
[
|
||||
{
|
||||
"id": "cmk27746i0000vnso2aspwf9g",
|
||||
"name": "Eqlrr1W-pK8ShMGqgPGL3-desktop.webp",
|
||||
"realName": "perbekel.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/Eqlrr1W-pK8ShMGqgPGL3-desktop.webp",
|
||||
"category": "image"
|
||||
}
|
||||
,
|
||||
{
|
||||
"id": "cmk20mg320000vnevxy0k73fr",
|
||||
"name": "thpgPSJkBxUIRajZt3AVo-desktop.webp",
|
||||
"realName": "bares.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/thpgPSJkBxUIRajZt3AVo-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20nqmu0001vnevfte29rk0",
|
||||
"name": "ubna9N6r7RgVWN5plO5mq-desktop.webp",
|
||||
"realName": "bicara-darma.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/ubna9N6r7RgVWN5plO5mq-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk228urs0007vnevi5b66bqn",
|
||||
"name": "Z4i2RRnnlHq2iWj94ldyo-desktop.webp",
|
||||
"realName": "daves.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/Z4i2RRnnlHq2iWj94ldyo-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20nyen0002vnevd0hfr3u8",
|
||||
"name": "y4yaE4XdUP1TSUGhWPW9h-desktop.webp",
|
||||
"realName": "mangan.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/y4yaE4XdUP1TSUGhWPW9h-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20o7mf0003vnevohrksm1d",
|
||||
"name": "Vr7CoaYDpk2dIkHx9PxRj-desktop.webp",
|
||||
"realName": "gelah-melah.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/Vr7CoaYDpk2dIkHx9PxRj-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20of8m0004vnev9ujy5o0l",
|
||||
"name": "ceoB_sg-HOzljN8j_2nZA-desktop.webp",
|
||||
"realName": "inovasi-desa-darmasaba.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/ceoB_sg-HOzljN8j_2nZA-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20omzq0005vnevgi6f4edu",
|
||||
"name": "vOy5YVUXfHXfiFOHylIN7-desktop.webp",
|
||||
"realName": "pdkt.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/vOy5YVUXfHXfiFOHylIN7-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk20pf3d0006vnev3mkoqpyy",
|
||||
"name": "gE_qcqIbY0mqI6FV9V4CL-desktop.webp",
|
||||
"realName": "sajjiana-dharma-raksaka.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/gE_qcqIbY0mqI6FV9V4CL-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk2cgqgm0003vn96jun52pik",
|
||||
"name": "q1G995W7cLkC_qquLTlKN-desktop.webp",
|
||||
"realName": "youtube.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/q1G995W7cLkC_qquLTlKN-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk2cmr000006vn96qepq6gvl",
|
||||
"name": "I6mlQ4nRmPX26gm79C_rM-desktop.webp",
|
||||
"realName": "facebook.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/I6mlQ4nRmPX26gm79C_rM-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk2cpeba0009vn966jcrpf3u",
|
||||
"name": "WArLC_yvU33MjoqEnQeQ1-desktop.webp",
|
||||
"realName": "instagram.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/WArLC_yvU33MjoqEnQeQ1-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmk2crcl1000cvn96j8pmgmo5",
|
||||
"name": "D3RPbNiaNSCjacLjeR_qO-desktop.webp",
|
||||
"realName": "tiktok.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/D3RPbNiaNSCjacLjeR_qO-desktop.webp",
|
||||
"category": "image"
|
||||
}
|
||||
]
|
||||
@@ -3,126 +3,108 @@
|
||||
"id": "cmds9h9ko000pvnberdjnx64b",
|
||||
"name": "1.1 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||
},
|
||||
{
|
||||
"id": "cmds9sjmz000svnbesv2133of",
|
||||
"name": "1.2 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||
},
|
||||
{
|
||||
"id": "cmds9tcpi000vvnbev3ebtlnt",
|
||||
"name": "1.3 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||
},
|
||||
{
|
||||
"id": "cmds9twvj000yvnbep0pq8dzf",
|
||||
"name": "1.4 PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA",
|
||||
"deskripsi": "<p>PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||
},
|
||||
{
|
||||
"id": "cmds9ugap0011vnbe118yv871",
|
||||
"name": "1.5 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||
},
|
||||
{
|
||||
"id": "cmdsa35310014vnbe6qy6l1rz",
|
||||
"name": "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
|
||||
"deskripsi": "<p>ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||
},
|
||||
{
|
||||
"id": "cmdsa46590017vnbepp3noso1",
|
||||
"name": "2.2 ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH",
|
||||
"deskripsi": "<p>ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||
},
|
||||
{
|
||||
"id": "cmdsa5m7z001avnbe4cvfrcz0",
|
||||
"name": "2.3 TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI",
|
||||
"deskripsi": "<p>TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||
},
|
||||
{
|
||||
"id": "cmdsa8q5q001dvnbemch8j24x",
|
||||
"name": "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
|
||||
"deskripsi": "<p>ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||
},
|
||||
{
|
||||
"id": "cmdsa9lbi001gvnbequn2ba7m",
|
||||
"name": "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
|
||||
"deskripsi": "<p>ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||
},
|
||||
{
|
||||
"id": "cmdsaa7aq001jvnbeizh04e67",
|
||||
"name": "3.3 ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA",
|
||||
"deskripsi": "<p>ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||
},
|
||||
{
|
||||
"id": "cmdsaaw8d001mvnbek3tfefrk",
|
||||
"name": "3.4 ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT",
|
||||
"deskripsi": "<p>ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||
},
|
||||
{
|
||||
"id": "cmdsabhif001pvnbepm06hry6",
|
||||
"name": "3.5 ADANYA MAKLUMAT PELAYANAN",
|
||||
"deskripsi": "<p>ADANYA MAKLUMAT PELAYANAN</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||
},
|
||||
{
|
||||
"id": "cmdsag40b001svnbe7krq9khc",
|
||||
"name": "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
|
||||
"deskripsi": "<p>ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||
},
|
||||
{
|
||||
"id": "cmdsagkaf001vvnbejo26w8sa",
|
||||
"name": "4.2 ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
||||
"deskripsi": "<p>ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||
},
|
||||
{
|
||||
"id": "cmdsah4qe001yvnbeiy3mwrvb",
|
||||
"name": "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
|
||||
"deskripsi": "<p>ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||
},
|
||||
{
|
||||
"id": "cmdsak5vn0021vnbemg86aab4",
|
||||
"name": "5.1 ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
||||
"deskripsi": "<p>ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99"
|
||||
},
|
||||
{
|
||||
"id": "cmdsalc800024vnbezgulhgrb",
|
||||
"name": "5.2 ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
||||
"deskripsi": "<p>ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
||||
"fileId": ""
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99"
|
||||
}
|
||||
]
|
||||
@@ -1,32 +1,26 @@
|
||||
[
|
||||
{
|
||||
"id": "cmds8w2q60002vnbe6i8qhkuo",
|
||||
"name": "Telephone Desa Darmasaba",
|
||||
"iconUrl": "081239580000"
|
||||
},
|
||||
{
|
||||
"id": "cmds8z7u20005vnbegyyvnbk0",
|
||||
"name": "Email Desa Darmasaba",
|
||||
"iconUrl": "desadarmasaba@badungkab.go.id"
|
||||
},
|
||||
{
|
||||
"id": "cmds9023u0008vnbe3oxmhwyf",
|
||||
"name": "Desa Darmasaba",
|
||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg"
|
||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg",
|
||||
"imageId": "cmk2cgqgm0003vn96jun52pik"
|
||||
},
|
||||
{
|
||||
"id": "cmds90oul000bvnbe2bqkptoi",
|
||||
"name": "Pemerintah Desa Darmasaba",
|
||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku"
|
||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku",
|
||||
"imageId": "cmk2cmr000006vn96qepq6gvl"
|
||||
},
|
||||
{
|
||||
"id": "cmds91i4e000evnbe8gtf1gub",
|
||||
"name": "ddarmasaba",
|
||||
"iconUrl": "https://www.instagram.com/ddarmasaba/"
|
||||
"iconUrl": "https://www.instagram.com/ddarmasaba/",
|
||||
"imageId": "cmk2cpeba0009vn966jcrpf3u"
|
||||
},
|
||||
{
|
||||
"id": "cmds92de5000hvnbemlu6sq5x",
|
||||
"name": "desa.darmasaba",
|
||||
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc"
|
||||
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc",
|
||||
"imageId": "cmk2crcl1000cvn96j8pmgmo5"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
"id": "edit",
|
||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||
"position": "Perbekel Darmasaba periode 2021-2027"
|
||||
"position": "Perbekel Darmasaba periode 2021-2027",
|
||||
"imageId": "cmk2a2dl6001nvngck1n0k8qc"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,50 +1,58 @@
|
||||
[
|
||||
{
|
||||
"id": "cmdr7039z0002vn5rttctt9hn",
|
||||
"name": "Davest",
|
||||
"description": "Darmasaba Village Festval",
|
||||
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024"
|
||||
},
|
||||
{
|
||||
"id": "cmdr755pf0005vn5rp8tyuubw",
|
||||
"name": "Dmangan",
|
||||
"description": "Darmasaba Aman Pangan",
|
||||
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024"
|
||||
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024",
|
||||
"imageId" : "cmk20nyen0002vnevd0hfr3u8"
|
||||
},
|
||||
{
|
||||
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
||||
"name": "Bicara Darmasaba",
|
||||
"description": "Bicara Darmasaba",
|
||||
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba"
|
||||
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba",
|
||||
"imageId" : "cmk20nqmu0001vnevfte29rk0"
|
||||
},
|
||||
{
|
||||
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
||||
"name": "Bares",
|
||||
"description": "Darmasaba Recycling Stock/Exchange",
|
||||
"link": "http://darmasaba.desa.id/berita/56722-bares"
|
||||
"link": "http://darmasaba.desa.id/berita/56722-bares",
|
||||
"imageId" : "cmk20mg320000vnevxy0k73fr"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7bxtp000evn5rmy85wihx",
|
||||
"name": "Sajjana Dharma Raksaka",
|
||||
"description": "Sajjana Dharma Raksaka",
|
||||
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf"
|
||||
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf",
|
||||
"imageId" : "cmk20pf3d0006vnev3mkoqpyy"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
||||
"name": "PDKT",
|
||||
"description": "Perangkat Desa Kuat Teknologi",
|
||||
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t"
|
||||
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t",
|
||||
"imageId" : "cmk20omzq0005vnevgi6f4edu"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
||||
"name": "GM",
|
||||
"description": "Galah Melah",
|
||||
"link": "https://darmasaba.desa.id/berita/52880-galah-melah"
|
||||
"link": "https://darmasaba.desa.id/berita/52880-galah-melah",
|
||||
"imageId" : "cmk20o7mf0003vnevohrksm1d"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7glue000pvn5r6onzslju",
|
||||
"name": "Inovasi Desa Darmasaba",
|
||||
"description": "Inovasi Desa Darmasaba",
|
||||
"link": "https://darmasaba.desa.id/produk-lokal-desa"
|
||||
"link": "https://darmasaba.desa.id/produk-lokal-desa",
|
||||
"imageId" : "cmk20of8m0004vnev9ujy5o0l"
|
||||
},
|
||||
{
|
||||
"id": "cmk228ust0009vnev5p8i377o",
|
||||
"name": "Davest",
|
||||
"description": "<p>DAVEST (Darmasaba Investment) merupakan program inovasi Desa Darmasaba yang bertujuan mempromosikan potensi investasi desa secara terintegrasi melalui media digital dan pendampingan langsung. Program ini menjadi sarana penghubung antara pemerintah desa, pelaku usaha, dan investor dalam rangka mendorong pertumbuhan ekonomi desa yang berkelanjutan.</p><p>DAVEST menyajikan informasi potensi unggulan desa seperti sektor UMKM, pariwisata, ekonomi kreatif, serta peluang investasi berbasis sumber daya lokal dengan prinsip transparansi dan kemudahan akses informasi.</p><p>Di tahun 2024 ini Davest (Darmasaba Village Festival) akan diadakan lagi, dengan berbagai kegiatan pemerdayaan, edukasi dan hiburan yang tentunya lebih waahhhh dari dua tahun lalu. Untuk memantapkan hal tersebut, Pemdes Darmasaba melakukan rapat koordinasi (rakor) Davest 2024 yang dipimpin langsung oleh Perbekel Darmasaba I. B. Surya Prabhawa Manuaba, S.H.,M.H. pada hari Senin (22/1/2024) bertempat di Ruang Shanti Gosana Kantor Perbekel Darmasaba.</p><hr><h3>Tujuan Program</h3><ul><li><p>Meningkatkan daya tarik investasi di Desa Darmasaba</p></li><li><p>Mempromosikan potensi unggulan desa secara profesional</p></li><li><p>Mendorong pertumbuhan ekonomi dan penciptaan lapangan kerja</p></li><li><p>Mendukung visi Desa Darmasaba sebagai desa inovatif dan berdaya saing</p></li></ul><hr><h3>Sasaran Program</h3><ul><li><p>Calon investor lokal dan regional</p></li><li><p>Pelaku UMKM dan kelompok usaha desa</p></li><li><p>Masyarakat Desa Darmasaba</p></li></ul><hr><h3>Bentuk Inovasi</h3><ul><li><p>Inovasi ekonomi desa</p></li><li><p>Inovasi digital</p></li><li><p>Inovasi tata kelola pelayanan investasi</p></li></ul><hr><h3>Ruang Lingkup Kegiatan</h3><ul><li><p>Penyusunan profil potensi investasi desa</p></li><li><p>Digitalisasi informasi investasi desa</p></li><li><p>Promosi peluang investasi melalui media online</p></li><li><p>Fasilitasi komunikasi antara investor dan desa</p></li><li><p>Pendampingan awal investasi berbasis desa</p></li></ul>",
|
||||
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024",
|
||||
"imageId" : "cmk228urs0007vnevi5b66bqn"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{ "nama": "Kebersihan" },
|
||||
{ "nama": "Infrastruktur" },
|
||||
{ "nama": "Sosial" },
|
||||
{ "nama": "Lingkungan" }
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{ "id": "cmghqwjs4000404l8c5uvc300", "nama": "PAUD" },
|
||||
{ "id": "cmghqwjs4000404l8c5uvc301", "nama": "TK" },
|
||||
{ "id": "cmghqwjs4000404l8c5uvc302", "nama": "SD" },
|
||||
{ "id": "cmghqwjs4000404l8c5uvc303", "nama": "SMP" },
|
||||
{ "id": "cmghqwjs4000404l8c5uvc304", "nama": "SMA" },
|
||||
{ "id": "cmghqwjs4000404l8c5uvc305", "nama": "SMK" }
|
||||
]
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"jenisInformasi": "Peraturan Desa",
|
||||
"deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa",
|
||||
"tanggal": "15 Januari 2024"
|
||||
"id": "cmeppcwzk0000vn5exmudcipd",
|
||||
"jenisInformasi": "Potensi Desa",
|
||||
"deskripsi": "<p>“Potensi desa adalah segenap sumber daya alam dan sumber daya manusia yang dimiliki desa sebagai modal dasar yang perlu dikelola dan dikembangkan bagi kelangsungan dan perkembangan desa. Adapun potensi yang dimiliki Desa Darmasaba yaitu:</p><ol><li><p>TPS3R Pudak Mesari</p></li><li><p>Bumdes Pudak Mesari</p></li><li><p>Pertanian</p></li><li><p>Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa</p></li><li><p>Taman Beji Cengana</p></li><li><p>Dam Tanah Putih</p></li><li><p>Gumuh Sari Water Park</p></li><li><p>UMKM</p></li><li><p>Kawasan Kuliner</p></li><li><p>IKM berbasis Pengolahan Pangan</p></li><li><p>Genteng</p></li><li><p>Peternakan Ikan Lele</p></li><li><p>Pemotongan Daging”</p></li></ol>",
|
||||
"tanggal": "2021-05-25"
|
||||
},
|
||||
{
|
||||
"id": "cmeppieay0001vn5e8qe658ub",
|
||||
"jenisInformasi": "Layanan Surat Keterangan Desa",
|
||||
"deskripsi": "<p>“Desa Darmasaba menyediakan berbagai jenis layanan surat keterangan untuk kebutuhan administratif, antara lain:</p><ul><li><p>Surat Keterangan Domisili Organisasi</p></li><li><p>Surat Keterangan Penghasilan</p></li><li><p>Surat Keterangan Tidak Mampu</p></li><li><p>Surat Keterangan Kelahiran</p></li><li><p>Surat Keterangan Usaha</p></li><li><p>Surat Keterangan Tempat Usaha</p></li><li><p>Surat Keterangan Belum Kawin</p></li><li><p>Surat Keterangan Kelakuan Baik (Pengantar SKCK)</p></li><li><p>Surat Keterangan Kematian</p></li><li><p>Surat Keterangan Perbedaan Biodata Diri</p></li><li><p>Surat Keterangan Yatim/Piatu/Yatim Piatu<br>Untuk surat keterangan lainnya, masyarakat dapat berkonsultasi langsung ke kantor Perbekel Darmasaba.”<br><em>(Sumber: Laman Layanan Desa Darmasaba)</em></p></li></ul>",
|
||||
"tanggal": "2025-02-21"
|
||||
}
|
||||
]
|
||||
10
prisma/data/ppid/ikm/jenis-kelamin/jenis-kelamin.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8bt5o5000007lb9xp11unb",
|
||||
"name": "Laki-laki"
|
||||
},
|
||||
{
|
||||
"id": "cme8btctl000107lbh2hocgg8",
|
||||
"name": "Perempuan"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8buup6000207lb54q9b0az",
|
||||
"name": "Sangat Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bv15o000307lbft9b0vzy",
|
||||
"name": "Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bvjvu000507lbgfsveog6",
|
||||
"name": "Kurang Baik"
|
||||
},
|
||||
{
|
||||
"id": "cme8bvvm6000607lbh6rn2ubm",
|
||||
"name": "Sangat Kurang Baik"
|
||||
}
|
||||
]
|
||||
14
prisma/data/ppid/ikm/umur-responden/umur-responden.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"id": "cme8bwgwu000707lbawc6fz3a",
|
||||
"name": "Muda"
|
||||
},
|
||||
{
|
||||
"id": "cme8hnx09000b07jl3ipifb1k",
|
||||
"name": "Dewasa"
|
||||
},
|
||||
{
|
||||
"id": "cme8ho7dv000c07jlc7lr4b4w",
|
||||
"name": "Lansia"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"id": "cmgewz4gt000704ib91i3f169",
|
||||
"namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.",
|
||||
"gelarAkademik": "S.H.,M.H.,NL.P.",
|
||||
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
|
||||
@@ -11,7 +11,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"id": "cmgewxfvw000004ibee5013f4",
|
||||
"namaLengkap": "I Ketut Suwanta",
|
||||
"gelarAkademik": "S.Pt",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -22,7 +22,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440006",
|
||||
"id": "cmgewxvqw000104ibgm5l8fzs",
|
||||
"namaLengkap": "Ni Wayan Supardiati",
|
||||
"gelarAkademik": "S.Pd",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -33,7 +33,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440011",
|
||||
"id": "cmgewy1g9000204ib2n7hbx0i",
|
||||
"namaLengkap": "I Wayan Agus Juni Artha Saputra",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -44,7 +44,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440012",
|
||||
"id": "cmgewybah000304ibgqhn1gm2",
|
||||
"namaLengkap": "I Wayan Sueca",
|
||||
"gelarAkademik": "S.H.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -55,7 +55,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440017",
|
||||
"id": "cmgewygqz000404ib20sv8nvg",
|
||||
"namaLengkap": "Si Gede Ketut Astawa",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -66,7 +66,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440018",
|
||||
"id": "cmgewyos1000504ibcu8o2gyk",
|
||||
"namaLengkap": "I Kadek Arya Minarta",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
@@ -77,7 +77,7 @@
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440021",
|
||||
"id": "cmgewyxk7000604ib8djs3i6c",
|
||||
"namaLengkap": "I Gede Andika Pradnya Diputra",
|
||||
"gelarAkademik": "S.E.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
|
||||
11
prisma/data/resolveImageId.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import safeImageId from "./safeImageId";
|
||||
|
||||
export default async function resolveImageIdForSeed(
|
||||
existingImageId: string | null | undefined,
|
||||
seedImageId: string | null | undefined
|
||||
) {
|
||||
if (existingImageId) return existingImageId;
|
||||
|
||||
// ✅ Skip validasi saat seed
|
||||
return await safeImageId(seedImageId, true);
|
||||
}
|
||||
24
prisma/data/safeImageId.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function safeImageId(
|
||||
imageId?: string | null,
|
||||
skipValidation = false // ✅ tambah param
|
||||
) {
|
||||
if (!imageId) return null;
|
||||
|
||||
if (skipValidation) {
|
||||
console.log(`⚠️ Skipping validation for ${imageId} (seed mode)`);
|
||||
return imageId; // langsung return tanpa cek DB
|
||||
}
|
||||
|
||||
const exists = await prisma.fileStorage.findUnique({
|
||||
where: { id: imageId },
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
console.warn(`⚠️ imageId ${imageId} not found in FileStorage`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return imageId;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "cmdpm429r0000vnndkcwslt0h",
|
||||
"name": "warga"
|
||||
}
|
||||
]
|
||||
32
prisma/data/user/roles.json
Normal file
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"id": "0",
|
||||
"name": "DEVELOPER",
|
||||
"description": "Developer",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "1",
|
||||
"name": "SUPER ADMIN",
|
||||
"description": "Administrator",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "ADMIN DESA",
|
||||
"description": "Administrator Desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "ADMIN KESEHATAN",
|
||||
"description": "Administrator Bidang Kesehatan",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"name": "ADMIN PENDIDIKAN",
|
||||
"description": "Administrator Bidang Pendidikan",
|
||||
"isActive": true
|
||||
}
|
||||
]
|
||||
10
prisma/data/user/users.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"id": "cmie1o0zh0002vn132vtzg7hh",
|
||||
"username": "SuperAdmin-Nico",
|
||||
"nomor": "6289647037426",
|
||||
"roleId": 0,
|
||||
"isActive": true,
|
||||
"sessionInvalid": false
|
||||
}
|
||||
]
|
||||
1127
prisma/migrations/20251119062255_add_unique_username/migration.sql
Normal file
142
prisma/migrations/20260106072549_nico_6_jan2025/migration.sql
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `dokterdanTenagaMedisId` on the `FasilitasKesehatan` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `tarifDanLayananId` on the `FasilitasKesehatan` table. All the data in the column will be lost.
|
||||
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `UserSession` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `permissions` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "FasilitasKesehatan" DROP CONSTRAINT "FasilitasKesehatan_dokterdanTenagaMedisId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "FasilitasKesehatan" DROP CONSTRAINT "FasilitasKesehatan_tarifDanLayananId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "User" DROP CONSTRAINT "User_roleId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "UserSession" DROP CONSTRAINT "UserSession_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "DokterdanTenagaMedis" ADD COLUMN "jadwalLibur" TEXT,
|
||||
ADD COLUMN "jamBukaLibur" TEXT,
|
||||
ADD COLUMN "jamBukaOperasional" TEXT,
|
||||
ADD COLUMN "jamTutupLibur" TEXT,
|
||||
ADD COLUMN "jamTutupOperasional" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "FasilitasKesehatan" DROP COLUMN "dokterdanTenagaMedisId",
|
||||
DROP COLUMN "tarifDanLayananId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "MediaSosial" ADD COLUMN "icon" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "roles" ALTER COLUMN "permissions" DROP NOT NULL;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "User";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "UserSession";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "permissions";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"nomor" TEXT NOT NULL,
|
||||
"roleId" TEXT NOT NULL DEFAULT '2',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sessionInvalid" BOOLEAN NOT NULL DEFAULT false,
|
||||
"lastLogin" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"permissions" JSONB,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_sessions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "user_sessions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserMenuAccess" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"menuId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "UserMenuAccess_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_Tarif" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_Tarif_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_Dokter" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_Dokter_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_nomor_key" ON "users"("nomor");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_sessions_userId_idx" ON "user_sessions"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_sessions_token_idx" ON "user_sessions"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserMenuAccess_userId_menuId_key" ON "UserMenuAccess"("userId", "menuId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_Tarif_B_index" ON "_Tarif"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_Dokter_B_index" ON "_Dokter"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_sessions" ADD CONSTRAINT "user_sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserMenuAccess" ADD CONSTRAINT "UserMenuAccess_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_Tarif" ADD CONSTRAINT "_Tarif_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_Tarif" ADD CONSTRAINT "_Tarif_B_fkey" FOREIGN KEY ("B") REFERENCES "TarifDanLayanan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_Dokter" ADD CONSTRAINT "_Dokter_A_fkey" FOREIGN KEY ("A") REFERENCES "DokterdanTenagaMedis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_Dokter" ADD CONSTRAINT "_Dokter_B_fkey" FOREIGN KEY ("B") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
63
prisma/safeseedUnique.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
type SafeSeedOptions = {
|
||||
skipUpdate?: boolean;
|
||||
};
|
||||
|
||||
// prisma/safeseedUnique.ts
|
||||
export async function safeSeedUnique<T extends keyof PrismaClient>(
|
||||
model: T,
|
||||
where: Record<string, any>,
|
||||
data: Record<string, any>,
|
||||
options: SafeSeedOptions = {}
|
||||
) {
|
||||
const m = prisma[model] as any;
|
||||
if (!m) throw new Error(`Model ${String(model)} tidak ditemukan`);
|
||||
|
||||
try {
|
||||
// Pastikan `where` berisi field yang benar-benar unique (misal: `id`)
|
||||
const result = await m.upsert({
|
||||
where,
|
||||
update: options.skipUpdate ? {} : data,
|
||||
create: data, // ✅ Jangan duplikasi `where` ke `create`
|
||||
});
|
||||
console.log(`✅ Seed ${String(model)}:`, where);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error(`❌ Gagal seed ${String(model)}:`, where, err);
|
||||
throw err; // ✅ Rethrow agar seeding berhenti jika kritis
|
||||
}
|
||||
}
|
||||
|
||||
// /* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// import { PrismaClient } from "@prisma/client";
|
||||
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
// type SafeSeedOptions = {
|
||||
// skipUpdate?: boolean;
|
||||
// };
|
||||
|
||||
// export async function safeSeedUnique<T extends keyof PrismaClient>(
|
||||
// model: T,
|
||||
// where: Record<string, any>,
|
||||
// data: Record<string, any>,
|
||||
// options: SafeSeedOptions = {}
|
||||
// ) {
|
||||
// const m = prisma[model] as any;
|
||||
// if (!m) throw new Error(`Model ${String(model)} tidak ditemukan`);
|
||||
|
||||
// try {
|
||||
// await m.upsert({
|
||||
// where,
|
||||
// update: options.skipUpdate ? {} : data,
|
||||
// create: { ...where, ...data },
|
||||
// });
|
||||
|
||||
// console.log(`✅ Seed ${String(model)}:`, where);
|
||||
// } catch (err) {
|
||||
// console.error(`❌ Gagal seed ${String(model)}:`, where, err);
|
||||
// }
|
||||
// }
|
||||
745
prisma/seed.ts
@@ -1,107 +1,363 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import prisma from "@/lib/prisma";
|
||||
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
||||
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
|
||||
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
||||
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
|
||||
import desaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/desaantiKorpusi.json";
|
||||
import kategoriDesaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json";
|
||||
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
|
||||
import apbdes from "./data/landing-page/apbdes/apbdes.json";
|
||||
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
|
||||
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||
import kategoriBerita from "./data/kategori-berita.json";
|
||||
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
||||
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
|
||||
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
||||
import layanan from "./data/list-layanan.json";
|
||||
import potensi from "./data/list-potensi.json";
|
||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||
import kategoriPrestasiDesa from "./data/landing-page/prestasi-desa/kategori-prestasi.json";
|
||||
import prestasiDesa from "./data/landing-page/prestasi-desa/prestasi-desa.json";
|
||||
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
|
||||
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
|
||||
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
|
||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
||||
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json";
|
||||
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
||||
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
||||
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
|
||||
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
|
||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
|
||||
import lambangDesa from "./data/desa/profile/lambang_desa.json";
|
||||
import maskotDesa from "./data/desa/profile/maskot_desa.json";
|
||||
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
|
||||
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
||||
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
|
||||
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
|
||||
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
|
||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
|
||||
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
|
||||
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
||||
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
|
||||
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
||||
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai-bumdes.json";
|
||||
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json";
|
||||
import kategoriBerita from "./data/desa/berita/kategori-berita.json";
|
||||
import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
|
||||
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
|
||||
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
|
||||
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
||||
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
|
||||
import kategoriKegiatanData from "./data/lingkungan/gotong-royong/kategori-gotong-royong.json";
|
||||
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
|
||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
|
||||
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
||||
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
|
||||
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
||||
import potensi from "./data/list-potensi.json";
|
||||
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
||||
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
||||
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
|
||||
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
||||
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
||||
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
|
||||
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
|
||||
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
|
||||
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
||||
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
||||
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
||||
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
||||
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
|
||||
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
|
||||
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
|
||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||
import roles from "./data/user/roles.json";
|
||||
import fileStorage from "./data/file-storage.json";
|
||||
import jenjangPendidikan from "./data/pendidikan/info-sekolah/jenjang-pendidikan.json";
|
||||
import seedAssets from "./seed_assets";
|
||||
import users from "./data/user/users.json";
|
||||
import { safeSeedUnique } from "./safeseedUnique";
|
||||
import safeImageId from "./data/safeImageId";
|
||||
import resolveImageIdForSeed from "./data/resolveImageId";
|
||||
|
||||
(async () => {
|
||||
// =========== LANDING PAGE ===========
|
||||
// =========== PROFILE ===========
|
||||
for (const p of profilePejabatDesa) {
|
||||
await prisma.pejabatDesa.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
});
|
||||
// seed assets
|
||||
await prisma.fileStorage.deleteMany();
|
||||
console.log("🗑️ Cleared existing fileStorage records");
|
||||
await seedAssets();
|
||||
|
||||
// // =========== FILE STORAGE ===========
|
||||
console.log("🔄 Seeding file storage...");
|
||||
for (const f of fileStorage) {
|
||||
await safeSeedUnique(
|
||||
"fileStorage",
|
||||
{ name: f.name },
|
||||
{
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
realName: f.realName,
|
||||
path: f.path,
|
||||
mimeType: f.mimeType,
|
||||
link: f.link,
|
||||
category: f.category,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
"✅ profilePejabatDesa seeded without imageId (editable later via UI)"
|
||||
);
|
||||
|
||||
console.log("✅ File storage seeded");
|
||||
|
||||
console.log("🔄 Seeding roles...");
|
||||
|
||||
for (const r of roles) {
|
||||
try {
|
||||
// ✅ Destructure to remove permissions if exists
|
||||
const { permissions, ...roleData } = r as any;
|
||||
|
||||
await safeSeedUnique(
|
||||
"role",
|
||||
{ name: roleData.name },
|
||||
{
|
||||
id: roleData.id,
|
||||
name: roleData.name,
|
||||
description: roleData.description,
|
||||
permissions: roleData.permissions || {}, // ✅ Include permissions
|
||||
isActive: roleData.isActive,
|
||||
}
|
||||
);
|
||||
console.log(`✅ Seeded role -> ${roleData.name}`);
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2002") {
|
||||
console.warn(`⚠️ Role already exists (skipping): ${r.name}`);
|
||||
} else {
|
||||
console.error(`❌ Failed to seed role ${r.name}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("✅ Roles seeding completed");
|
||||
|
||||
// =========== USER ===========
|
||||
console.log("🔄 Seeding users...");
|
||||
for (const u of users) {
|
||||
try {
|
||||
// Verify role exists first
|
||||
const roleExists = await prisma.role.findUnique({
|
||||
where: { id: u.roleId.toString() },
|
||||
select: { id: true }, // Only select id to minimize query
|
||||
});
|
||||
|
||||
if (!roleExists) {
|
||||
console.error(
|
||||
`❌ Role with id ${u.roleId} not found for user ${u.username}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
await safeSeedUnique(
|
||||
"user",
|
||||
{ id: u.id },
|
||||
{
|
||||
username: u.username,
|
||||
nomor: u.nomor,
|
||||
roleId: u.roleId.toString(),
|
||||
isActive: u.isActive,
|
||||
sessionInvalid: false,
|
||||
}
|
||||
);
|
||||
console.log(`✅ Seeded user -> ${u.username}`);
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2003") {
|
||||
console.error(
|
||||
`❌ Foreign key constraint failed for user ${u.username}: Role ${u.roleId} does not exist`
|
||||
);
|
||||
} else {
|
||||
console.error(`❌ Failed to seed user ${u.username}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("✅ Users seeding completed");
|
||||
// =========== LANDING PAGE ===========
|
||||
// =========== SUBMENU PROFILE ===========
|
||||
// =========== PROFILE PEJABAT DESA ===========
|
||||
// In your seed.ts file, update the PejabatDesa seeding section to:
|
||||
console.log("🔄 Seeding Pejabat Desa...");
|
||||
for (const p of profilePejabatDesa) {
|
||||
try {
|
||||
// First, verify the image exists
|
||||
if (p.imageId) {
|
||||
const imageExists = await prisma.fileStorage.findUnique({
|
||||
where: { id: p.imageId },
|
||||
});
|
||||
|
||||
if (!imageExists) {
|
||||
console.warn(
|
||||
`⚠️ Image not found for PejabatDesa ${p.name}, skipping...`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await safeSeedUnique(
|
||||
"pejabatDesa",
|
||||
{ id: p.id },
|
||||
{
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
imageId: p.imageId,
|
||||
}
|
||||
);
|
||||
console.log(`✅ Seeded Pejabat Desa -> ${p.name}`);
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Failed to seed Pejabat Desa ${p.name}:`, error.message);
|
||||
}
|
||||
}
|
||||
console.log("✅ Pejabat Desa seeding completed");
|
||||
|
||||
// =========== PROGRAM INOVASI ===========
|
||||
// Add this section after the other seed operations in seed.ts
|
||||
console.log("🔄 Seeding Program Inovasi...");
|
||||
|
||||
for (const p of programInovasi) {
|
||||
const existing = await prisma.programInovasi.findUnique({
|
||||
where: { id: p.id },
|
||||
select: { imageId: true },
|
||||
});
|
||||
|
||||
let imageId = existing?.imageId; // Pertahankan existing
|
||||
|
||||
// Kalau belum ada imageId, cari berdasarkan name/realName
|
||||
if (!imageId && p.imageId) {
|
||||
// ✅ Cari langsung berdasarkan ID yang ada di p.imageId
|
||||
const fileRecord = await prisma.fileStorage.findUnique({
|
||||
where: { id: p.imageId },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
if (fileRecord) {
|
||||
imageId = fileRecord.id;
|
||||
console.log(
|
||||
`✅ Found file by ID: ${fileRecord.name} (${fileRecord.id})`
|
||||
);
|
||||
} else {
|
||||
console.warn(`⚠️ File with ID ${p.imageId} not found for ${p.name}`);
|
||||
imageId = null;
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.programInovasi.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
imageId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
imageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("program inovasi success ...");
|
||||
|
||||
// =========== MEDIA SOSIAL ===========
|
||||
for (const p of mediaSosial) {
|
||||
for (const m of mediaSosial) {
|
||||
const existing = await prisma.mediaSosial.findUnique({
|
||||
where: { id: m.id },
|
||||
select: { imageId: true },
|
||||
});
|
||||
|
||||
const imageId = await resolveImageIdForSeed(existing?.imageId, m.imageId);
|
||||
|
||||
await prisma.mediaSosial.upsert({
|
||||
where: { id: m.id },
|
||||
update: {
|
||||
name: m.name,
|
||||
iconUrl: m.iconUrl,
|
||||
// ⛔ JANGAN overwrite imageId sembarangan
|
||||
imageId,
|
||||
},
|
||||
create: {
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
iconUrl: m.iconUrl,
|
||||
imageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("media sosial success ...");
|
||||
|
||||
// =========== SUBMENU DESA ANTI KORUPSI ===========
|
||||
// =========== KATEGORI DESA ANTI KORUPSI ===========
|
||||
for (const k of kategoriDesaAntiKorupsi) {
|
||||
await prisma.kategoriDesaAntiKorupsi.upsert({
|
||||
where: { id: k.id },
|
||||
update: {
|
||||
name: k.name,
|
||||
},
|
||||
create: {
|
||||
id: k.id,
|
||||
name: k.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("kategori desa anti korupsi success ...");
|
||||
|
||||
// =========== DESA ANTI KORUPSI ===========
|
||||
for (const p of desaAntiKorupsi) {
|
||||
await prisma.desaAntiKorupsi.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
deskripsi: p.deskripsi,
|
||||
kategoriId: p.kategoriId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
deskripsi: p.deskripsi,
|
||||
kategoriId: p.kategoriId,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("media sosial success ...");
|
||||
console.log("desa anti korupsi success ...");
|
||||
|
||||
// =========== KATEGORI DESA ANTI KORUPSI ===========
|
||||
for (const p of kategoriDesaAntiKorupsi) {
|
||||
await prisma.kategoriDesaAntiKorupsi.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("desa anti korupsi success ...");
|
||||
|
||||
// =========== KATEGORI PRESTASI DESA===========
|
||||
for (const c of kategoriPrestasiDesa) {
|
||||
await prisma.kategoriPrestasiDesa.upsert({
|
||||
where: { id: c.id },
|
||||
update: {
|
||||
name: c.name,
|
||||
},
|
||||
create: {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("kategori prestasi desa success ...");
|
||||
|
||||
// =========== PRESTASI DESA===========
|
||||
for (const p of prestasiDesa) {
|
||||
await prisma.prestasiDesa.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
deskripsi: p.deskripsi,
|
||||
kategoriId: p.kategoriId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
deskripsi: p.deskripsi,
|
||||
kategoriId: p.kategoriId,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("prestasi desa success ...");
|
||||
|
||||
// =========== PENGHARGAAN ===========
|
||||
for (const p of penghargaan) {
|
||||
@@ -122,8 +378,8 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("penghargaan success ...");
|
||||
|
||||
// =========== LAYANAN DESA ===========
|
||||
for (const p of pelayananSuratKeterangan) {
|
||||
// =========== LAYANAN DESA ===========
|
||||
for (const p of pelayananSuratKeterangan) {
|
||||
await prisma.pelayananSuratKeterangan.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
@@ -157,35 +413,16 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("pelayanan surat keterangan success ...");
|
||||
|
||||
// =========== LAYANAN ===========
|
||||
for (const l of layanan) {
|
||||
await prisma.layanan.upsert({
|
||||
where: {
|
||||
name: l.name,
|
||||
},
|
||||
update: {
|
||||
name: l.name,
|
||||
},
|
||||
create: {
|
||||
name: l.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("layanan success ...");
|
||||
|
||||
// =========== SDGSDesa ===========
|
||||
for (const l of sdgsDesa) {
|
||||
await prisma.sDGSDesa.upsert({
|
||||
where: {
|
||||
name: l.name,
|
||||
jumlah: l.jumlah,
|
||||
},
|
||||
await prisma.sdgsDesa.upsert({
|
||||
where: { id: l.id },
|
||||
update: {
|
||||
name: l.name,
|
||||
jumlah: l.jumlah,
|
||||
},
|
||||
create: {
|
||||
id: l.id,
|
||||
name: l.name,
|
||||
jumlah: l.jumlah,
|
||||
},
|
||||
@@ -198,8 +435,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
for (const l of apbdes) {
|
||||
await prisma.aPBDes.upsert({
|
||||
where: {
|
||||
name: l.name,
|
||||
jumlah: l.jumlah,
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
name: l.name,
|
||||
@@ -214,6 +450,9 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
|
||||
console.log("sdgs desa success ...");
|
||||
|
||||
// =========== MENU DESA ===========
|
||||
// =========== SUBMENU PROFILE ===========
|
||||
// =========== SEJARAH DESA ===========
|
||||
for (const l of sejarahDesa) {
|
||||
await prisma.sejarahDesa.upsert({
|
||||
where: {
|
||||
@@ -233,6 +472,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
|
||||
console.log("sejarah desa success ...");
|
||||
|
||||
// =========== MASKOT DESA ===========
|
||||
for (const l of maskotDesa) {
|
||||
await prisma.maskotDesa.upsert({
|
||||
where: {
|
||||
@@ -252,6 +492,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
|
||||
console.log("maskot desa success ...");
|
||||
|
||||
// =========== LAMBANG DESA ===========
|
||||
for (const l of lambangDesa) {
|
||||
await prisma.lambangDesa.upsert({
|
||||
where: {
|
||||
@@ -271,6 +512,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
|
||||
console.log("lambang desa success ...");
|
||||
|
||||
// =========== PROFIL PERBEKEL ===========
|
||||
for (const c of profilPerbekel) {
|
||||
await prisma.profilPerbekel.upsert({
|
||||
where: { id: c.id },
|
||||
@@ -295,6 +537,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
"✅ profilePerbekel seeded without imageId (editable later via UI)"
|
||||
);
|
||||
|
||||
// =========== VISI MISI DESA ===========
|
||||
for (const l of visiMisiDesa) {
|
||||
await prisma.visiMisiDesa.upsert({
|
||||
where: {
|
||||
@@ -314,63 +557,159 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
|
||||
console.log("visi misi desa success ...");
|
||||
|
||||
// Flatten the nested array structure for posisiOrganisasiPPID
|
||||
const flattenedPosisiOrganisasiPPID = posisiOrganisasiPPID.flat();
|
||||
// =========== MENU PPID ===========
|
||||
// =========== SUBMENU PROFILE PPID ===========
|
||||
for (const c of profilePPID) {
|
||||
await prisma.profilePPID.upsert({
|
||||
where: { id: c.id },
|
||||
update: {
|
||||
name: c.name,
|
||||
biodata: c.biodata,
|
||||
riwayat: c.riwayat,
|
||||
pengalaman: c.pengalaman,
|
||||
unggulan: c.unggulan,
|
||||
// imageId tidak di-update
|
||||
},
|
||||
create: {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
biodata: c.biodata,
|
||||
riwayat: c.riwayat,
|
||||
pengalaman: c.pengalaman,
|
||||
unggulan: c.unggulan,
|
||||
// imageId tidak di-create
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
|
||||
|
||||
// =========== SUBMENU STRUKTUR PPID ===========
|
||||
// =========== POSISI ORGANISASI PPID ===========
|
||||
|
||||
const flattenedPosisi = posisiOrganisasiPPID.flat();
|
||||
|
||||
// ✅ Urutkan berdasarkan hierarki
|
||||
const sortedPosisi = flattenedPosisi.sort((a, b) => a.hierarki - b.hierarki);
|
||||
|
||||
for (const p of sortedPosisi) {
|
||||
console.log(`Seeding: ${p.nama} (id: ${p.id}, parent: ${p.parentId})`);
|
||||
|
||||
if (p.parentId) {
|
||||
const parentExists = flattenedPosisi.some((pos) => pos.id === p.parentId);
|
||||
if (!parentExists) {
|
||||
console.warn(
|
||||
`⚠️ Parent tidak ditemukan: ${p.parentId} untuk ${p.nama}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const p of flattenedPosisiOrganisasiPPID) {
|
||||
await prisma.posisiOrganisasiPPID.upsert({
|
||||
where: { id: p.id },
|
||||
update: p,
|
||||
create: p,
|
||||
});
|
||||
}
|
||||
console.log("posisi organisasi berhasil");
|
||||
|
||||
// =========== PEGAWAI PPID ===========
|
||||
console.log("🔄 Seeding pegawai PPID...");
|
||||
const flattenedPegawai = pegawaiPPID.flat();
|
||||
|
||||
// Check for duplicate emails
|
||||
const emails = new Set();
|
||||
for (const p of flattenedPegawai) {
|
||||
if (emails.has(p.email)) {
|
||||
console.warn(`⚠️ Duplicate email found in pegawaiPPID: ${p.email}`);
|
||||
}
|
||||
emails.add(p.email);
|
||||
}
|
||||
|
||||
for (const p of flattenedPegawai) {
|
||||
try {
|
||||
await prisma.pegawaiPPID.upsert({
|
||||
where: { id: p.id },
|
||||
update: p,
|
||||
create: p,
|
||||
});
|
||||
console.log(`✅ Seeded pegawai PPID -> ${p.namaLengkap}`);
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2002") {
|
||||
console.warn(
|
||||
`⚠️ Pegawai PPID with duplicate email (skipping): ${p.email}`
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
`❌ Failed to seed pegawai PPID ${p.namaLengkap}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("✅ pegawai PPID seeding completed");
|
||||
|
||||
// =========== SUBMENU VISI MISI PPID ===========
|
||||
|
||||
for (const v of visiMisiPPID) {
|
||||
await prisma.visiMisiPPID.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
id: v.id,
|
||||
},
|
||||
update: {
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
parentId: p.parentId,
|
||||
misi: v.misi,
|
||||
visi: v.visi,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
parentId: p.parentId,
|
||||
id: v.id,
|
||||
misi: v.misi,
|
||||
visi: v.visi,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("posisi organisasi success ...");
|
||||
console.log("visi misi PPID success ...");
|
||||
|
||||
// Flatten the nested array structure for pegawaiPPID
|
||||
const flattenedPegawaiPPID = pegawaiPPID.flat();
|
||||
|
||||
for (const p of flattenedPegawaiPPID) {
|
||||
await prisma.pegawaiPPID.upsert({
|
||||
// =========== SUBMENU DASAR HUKUM PPID ===========
|
||||
for (const v of dasarHukumPPID) {
|
||||
await prisma.dasarHukumPPID.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
id: v.id,
|
||||
},
|
||||
update: {
|
||||
namaLengkap: p.namaLengkap,
|
||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
||||
email: p.email,
|
||||
gelarAkademik: p.gelarAkademik,
|
||||
telepon: p.telepon,
|
||||
alamat: p.alamat,
|
||||
posisiId: p.posisiId,
|
||||
isActive: p.isActive,
|
||||
judul: v.judul,
|
||||
content: v.content,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
namaLengkap: p.namaLengkap,
|
||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
||||
email: p.email,
|
||||
gelarAkademik: p.gelarAkademik,
|
||||
telepon: p.telepon,
|
||||
alamat: p.alamat,
|
||||
posisiId: p.posisiId,
|
||||
isActive: p.isActive,
|
||||
id: v.id,
|
||||
judul: v.judul,
|
||||
content: v.content,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("pegawai success ...");
|
||||
console.log("dasar hukum PPID success ...");
|
||||
|
||||
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
|
||||
for (const v of daftarInformasiPublik) {
|
||||
// Convert string date to Date object
|
||||
const tanggal = new Date(v.tanggal);
|
||||
|
||||
await prisma.daftarInformasiPublik.upsert({
|
||||
where: {
|
||||
id: v.id,
|
||||
},
|
||||
update: {
|
||||
jenisInformasi: v.jenisInformasi,
|
||||
deskripsi: v.deskripsi,
|
||||
tanggal: tanggal,
|
||||
},
|
||||
create: {
|
||||
id: v.id,
|
||||
jenisInformasi: v.jenisInformasi,
|
||||
deskripsi: v.deskripsi,
|
||||
tanggal: tanggal,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("daftar informasi publik PPID success ...");
|
||||
|
||||
for (const l of pelayananPerizinanBerusaha) {
|
||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||
@@ -504,65 +843,53 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("cara memperoleh salinan informasi success ...");
|
||||
|
||||
for (const c of profilePPID) {
|
||||
await prisma.profilePPID.upsert({
|
||||
where: { id: c.id },
|
||||
update: {
|
||||
name: c.name,
|
||||
biodata: c.biodata,
|
||||
riwayat: c.riwayat,
|
||||
pengalaman: c.pengalaman,
|
||||
unggulan: c.unggulan,
|
||||
// imageId tidak di-update
|
||||
},
|
||||
create: {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
biodata: c.biodata,
|
||||
riwayat: c.riwayat,
|
||||
pengalaman: c.pengalaman,
|
||||
unggulan: c.unggulan,
|
||||
// imageId tidak di-create
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
|
||||
|
||||
for (const v of visiMisiPPID) {
|
||||
await prisma.visiMisiPPID.upsert({
|
||||
for (const j of jenisKelamin) {
|
||||
await prisma.jenisKelaminResponden.upsert({
|
||||
where: {
|
||||
id: v.id,
|
||||
id: j.id,
|
||||
},
|
||||
update: {
|
||||
misi: v.misi,
|
||||
visi: v.visi,
|
||||
name: j.name,
|
||||
},
|
||||
create: {
|
||||
id: v.id,
|
||||
misi: v.misi,
|
||||
visi: v.visi,
|
||||
id: j.id,
|
||||
name: j.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("visi misi PPID success ...");
|
||||
console.log("jenis kelamin responden success ...");
|
||||
|
||||
for (const v of dasarHukumPPID) {
|
||||
await prisma.dasarHukumPPID.upsert({
|
||||
for (const r of pilihanRatingResponden) {
|
||||
await prisma.pilihanRatingResponden.upsert({
|
||||
where: {
|
||||
id: v.id,
|
||||
id: r.id,
|
||||
},
|
||||
update: {
|
||||
judul: v.judul,
|
||||
content: v.content,
|
||||
name: r.name,
|
||||
},
|
||||
create: {
|
||||
id: v.id,
|
||||
judul: v.judul,
|
||||
content: v.content,
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("dasar hukum PPID success ...");
|
||||
console.log("pilihan rating responden success ...");
|
||||
|
||||
for (const u of umurResponden) {
|
||||
await prisma.umurResponden.upsert({
|
||||
where: {
|
||||
id: u.id,
|
||||
},
|
||||
update: {
|
||||
name: u.name,
|
||||
},
|
||||
create: {
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("umur responden success ...");
|
||||
|
||||
for (const k of kategoriProduk) {
|
||||
await prisma.kategoriProduk.upsert({
|
||||
@@ -580,28 +907,36 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("kategori produk success ...");
|
||||
|
||||
for (const p of posisiOrganisasi) {
|
||||
await prisma.posisiOrganisasi.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
update: {
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
},
|
||||
const flattenedPosisiBumdes = posisiOrganisasi.flat();
|
||||
|
||||
// ✅ Urutkan berdasarkan hierarki
|
||||
const sortedPosisiBumdes = flattenedPosisiBumdes.sort(
|
||||
(a, b) => a.hierarki - b.hierarki
|
||||
);
|
||||
|
||||
for (const p of sortedPosisiBumdes) {
|
||||
console.log(`Seeding: ${p.nama} (id: ${p.id}, parent: ${p.parentId})`);
|
||||
|
||||
if (p.parentId) {
|
||||
const parentExists = flattenedPosisi.some((pos) => pos.id === p.parentId);
|
||||
if (!parentExists) {
|
||||
console.warn(
|
||||
`⚠️ Parent tidak ditemukan: ${p.parentId} untuk ${p.nama}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.posisiOrganisasiBumDes.upsert({
|
||||
where: { id: p.id },
|
||||
update: p,
|
||||
create: p,
|
||||
});
|
||||
}
|
||||
console.log("posisi organisasi success ...");
|
||||
console.log("posisi organisasi berhasil");
|
||||
|
||||
for (const p of pegawai) {
|
||||
await prisma.pegawai.upsert({
|
||||
await prisma.pegawaiBumDes.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
@@ -630,26 +965,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("pegawai success ...");
|
||||
|
||||
for (const p of hubunganOrganisasi) {
|
||||
await prisma.hubunganOrganisasi.upsert({
|
||||
where: {
|
||||
atasanId_bawahanId: {
|
||||
atasanId: p.atasanId,
|
||||
bawahanId: p.bawahanId,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
tipe: p.tipe,
|
||||
},
|
||||
create: {
|
||||
atasanId: p.atasanId,
|
||||
bawahanId: p.bawahanId,
|
||||
tipe: p.tipe,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("hubungan organisasi success ...");
|
||||
|
||||
for (const d of detailDataPengangguran) {
|
||||
await prisma.detailDataPengangguran.upsert({
|
||||
where: {
|
||||
@@ -673,6 +988,30 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
}
|
||||
console.log("📊 detailDataPengangguran success ...");
|
||||
|
||||
// =========== KATEGORI GOTONG ROYONG ===========
|
||||
// Add IDs to the kategoriKegiatan data
|
||||
const kategoriKegiatan = kategoriKegiatanData.map((k, index) => ({
|
||||
...k,
|
||||
id: `kategori-${index + 1}`,
|
||||
}));
|
||||
|
||||
for (const k of kategoriKegiatan) {
|
||||
await prisma.kategoriKegiatan.upsert({
|
||||
where: {
|
||||
id: k.id,
|
||||
},
|
||||
update: {
|
||||
nama: k.nama,
|
||||
},
|
||||
create: {
|
||||
id: k.id,
|
||||
nama: k.nama,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("kategori kegiatan success ...");
|
||||
|
||||
for (const e of tujuanEdukasiLingkungan) {
|
||||
await prisma.tujuanEdukasiLingkungan.upsert({
|
||||
where: {
|
||||
@@ -926,6 +1265,22 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
||||
console.log(
|
||||
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const j of jenjangPendidikan) {
|
||||
await prisma.jenjangPendidikan.upsert({
|
||||
where: {
|
||||
id: j.id || undefined,
|
||||
},
|
||||
update: {
|
||||
nama: j.nama,
|
||||
},
|
||||
create: {
|
||||
nama: j.nama,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("✅ Jenjang Pendidikan seeded successfully");
|
||||
})()
|
||||
.then(() => prisma.$disconnect())
|
||||
.catch((e) => {
|
||||
|
||||
174
prisma/seed_assets.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// prisma/seedAssets.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import AdmZip from "adm-zip";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
import fetchWithRetry from "./data/fetchWithRetry";
|
||||
|
||||
const UPLOADS_DIR =
|
||||
process.env.WIBU_UPLOAD_DIR || path.join(process.cwd(), "uploads");
|
||||
|
||||
// --- Helper: deteksi kategori file ---
|
||||
function detectCategory(filename: string): "image" | "document" | "other" {
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) return "image";
|
||||
if ([".pdf", ".doc", ".docx"].includes(ext)) return "document";
|
||||
return "other";
|
||||
}
|
||||
|
||||
// --- Helper: recursive walk dir ---
|
||||
async function walkDir(
|
||||
dir: string,
|
||||
fileList: string[] = []
|
||||
): Promise<string[]> {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "__MACOSX") continue; // skip folder sampah
|
||||
await walkDir(fullPath, fileList);
|
||||
} else {
|
||||
if (entry.name.startsWith(".") || entry.name === ".DS_Store") continue; // skip file sampah
|
||||
fileList.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
export default async function seedAssets() {
|
||||
console.log("🚀 Seeding assets...");
|
||||
|
||||
// 1. Download zip
|
||||
const url =
|
||||
"https://cld-dkr-makuro-seafile.wibudev.com/f/90dd12c9713e42379fcd/?dl=1";
|
||||
const res = await fetchWithRetry(url, 3, 20000);
|
||||
|
||||
// Validasi content-type
|
||||
const contentType = res.headers.get("content-type");
|
||||
if (!contentType?.includes("zip")) {
|
||||
throw new Error(`Invalid content-type (${contentType}). Expected ZIP file`);
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await res.arrayBuffer());
|
||||
|
||||
// Validasi ukuran file
|
||||
if (buffer.length < 100) {
|
||||
throw new Error("Downloaded ZIP is empty or corrupted");
|
||||
}
|
||||
|
||||
// Validasi signature ZIP ("PK")
|
||||
if (buffer.toString("utf8", 0, 2) !== "PK") {
|
||||
throw new Error("Invalid ZIP signature (PK not found)");
|
||||
}
|
||||
|
||||
// 2. Extract zip ke folder tmp
|
||||
const extractDir = path.join(process.cwd(), "tmp_assets");
|
||||
await fs.rm(extractDir, { recursive: true, force: true });
|
||||
await fs.mkdir(extractDir, { recursive: true });
|
||||
|
||||
let zip: AdmZip;
|
||||
|
||||
try {
|
||||
zip = new AdmZip(buffer);
|
||||
} catch (err) {
|
||||
throw new Error("Failed to parse ZIP file (corrupted or invalid)");
|
||||
}
|
||||
|
||||
try {
|
||||
zip.extractAllTo(extractDir, true);
|
||||
} catch (err) {
|
||||
throw new Error("Failed to extract ZIP contents");
|
||||
}
|
||||
|
||||
// 3. Cari semua file valid (recursive)
|
||||
const files = await walkDir(extractDir);
|
||||
|
||||
// 4. Loop tiap file & simpan
|
||||
for (const filePath of files) {
|
||||
const entryName = path.basename(filePath);
|
||||
const category = detectCategory(entryName);
|
||||
|
||||
let finalName = entryName;
|
||||
let mimeType = "application/octet-stream";
|
||||
let targetPath = "";
|
||||
|
||||
if (category === "image") {
|
||||
const fileBaseName = path.parse(entryName).name;
|
||||
finalName = `${fileBaseName}.webp`;
|
||||
targetPath = path.join(UPLOADS_DIR, "images", finalName);
|
||||
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
||||
await sharp(filePath).webp({ quality: 80 }).toFile(targetPath);
|
||||
mimeType = "image/webp";
|
||||
} else if (category === "document") {
|
||||
targetPath = path.join(UPLOADS_DIR, "documents", entryName);
|
||||
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
||||
await fs.copyFile(filePath, targetPath);
|
||||
mimeType = "application/pdf";
|
||||
} else {
|
||||
targetPath = path.join(UPLOADS_DIR, "other", entryName);
|
||||
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
||||
await fs.copyFile(filePath, targetPath);
|
||||
}
|
||||
|
||||
const existing = await prisma.fileStorage.findUnique({
|
||||
where: { name: finalName },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// Restore kalau soft deleted
|
||||
await prisma.fileStorage.update({
|
||||
where: { name: finalName },
|
||||
data: {
|
||||
path: targetPath,
|
||||
realName: entryName,
|
||||
mimeType,
|
||||
link: `/uploads/${category}/${finalName}`,
|
||||
category,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`♻️ restored: ${category}/${finalName}`);
|
||||
} else {
|
||||
await prisma.fileStorage.create({
|
||||
data: {
|
||||
name: finalName,
|
||||
realName: entryName,
|
||||
path: targetPath,
|
||||
mimeType,
|
||||
link: `/uploads/${category}/${finalName}`,
|
||||
category,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📂 created: ${category}/${finalName}`);
|
||||
}
|
||||
|
||||
console.log(`📂 saved: ${category}/${finalName}`);
|
||||
}
|
||||
|
||||
// 6. Cleanup
|
||||
await fs.rm(extractDir, { recursive: true, force: true });
|
||||
|
||||
console.log("✅ Selesai seed assets!");
|
||||
console.log("DB URL (asset):", process.env.DATABASE_URL);
|
||||
|
||||
}
|
||||
|
||||
// --- Auto run kalau dipanggil langsung ---
|
||||
if (import.meta.main) {
|
||||
seedAssets()
|
||||
.catch((err) => {
|
||||
console.error("❌ Error seeding assets:", err);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
}
|
||||
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
BIN
public/beasiswa-siswa.png
Normal file
|
After Width: | Height: | Size: 446 KiB |
BIN
public/chatbot-removebg-preview.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
public/mangupuraaward.jpeg
Normal file
|
After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
@@ -23,6 +23,7 @@ export default function SpashScreen() {
|
||||
<Paper p={"md"} miw={320}>
|
||||
<Flex>
|
||||
<Image
|
||||
loading="lazy"
|
||||
src={images["darmasaba-icon"]}
|
||||
alt="darmasaba"
|
||||
w={100}
|
||||
|
||||
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client';
|
||||
|
||||
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
||||
import { LatLngExpression } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import L from 'leaflet';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type MarkerData = {
|
||||
position: [number, number];
|
||||
popup: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
center: [number, number];
|
||||
markers: MarkerData[];
|
||||
zoom?: number;
|
||||
scrollWheelZoom?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export default function LeafletMultiMarkerMap({
|
||||
center,
|
||||
markers,
|
||||
zoom = 13,
|
||||
scrollWheelZoom = true,
|
||||
className = '',
|
||||
style = { height: '100%', width: '100%', zIndex: 0 },
|
||||
}: Props) {
|
||||
// Fix for default marker icons in Next.js
|
||||
useEffect(() => {
|
||||
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
|
||||
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<MapContainer
|
||||
center={center as LatLngExpression}
|
||||
zoom={zoom}
|
||||
scrollWheelZoom={scrollWheelZoom}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{markers.map((marker, index) => (
|
||||
<Marker key={index} position={marker.position as LatLngExpression}>
|
||||
<Popup>{marker.popup}</Popup>
|
||||
</Marker>
|
||||
))}
|
||||
</MapContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Underline from '@tiptap/extension-underline';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import Superscript from '@tiptap/extension-superscript';
|
||||
import SubScript from '@tiptap/extension-subscript';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type CreateEditorProps = {
|
||||
value: string;
|
||||
@@ -32,6 +33,13 @@ export default function CreateEditor({ value, onChange }: CreateEditorProps) {
|
||||
},
|
||||
});
|
||||
|
||||
// 👇 Tambahkan efek untuk sinkronisasi value dari luar (resetForm)
|
||||
useEffect(() => {
|
||||
if (editor && value !== editor.getHTML()) {
|
||||
editor.commands.setContent(value || '');
|
||||
}
|
||||
}, [value, editor]);
|
||||
|
||||
return (
|
||||
<RichTextEditor editor={editor}>
|
||||
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function EditEditor({ value, onChange }: EditEditorProps) {
|
||||
editor.off('update', updateHandler);
|
||||
};
|
||||
}, [editor, onChange]);
|
||||
|
||||
|
||||
return (
|
||||
<RichTextEditor editor={editor}>
|
||||
|
||||
100
src/app/admin/(dashboard)/_com/iconMap.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
IconLeaf,
|
||||
IconTrophy,
|
||||
IconTent,
|
||||
IconChartLine,
|
||||
IconRecycle,
|
||||
IconTruck,
|
||||
IconScale,
|
||||
IconClipboard,
|
||||
IconTrash,
|
||||
IconHomeEco,
|
||||
IconChristmasTreeFilled,
|
||||
IconTrendingUp,
|
||||
IconShieldFilled,
|
||||
IconHome,
|
||||
IconTree,
|
||||
IconDroplet,
|
||||
IconCash,
|
||||
IconSchool,
|
||||
IconShoppingCart,
|
||||
IconHospital,
|
||||
IconAmbulance,
|
||||
IconFiretruck,
|
||||
IconBuilding,
|
||||
IconAlertTriangle,
|
||||
} from '@tabler/icons-react'
|
||||
|
||||
export type IconKey =
|
||||
| 'ekowisata'
|
||||
| 'kompetisi'
|
||||
| 'wisata'
|
||||
| 'ekonomi'
|
||||
| 'sampah'
|
||||
| 'truck'
|
||||
| 'scale'
|
||||
| 'clipboard'
|
||||
| 'trash'
|
||||
| 'lingkunganSehat'
|
||||
| 'sumberOksigen'
|
||||
| 'ekonomiBerkelanjutan'
|
||||
| 'mencegahBencana'
|
||||
| 'rumah'
|
||||
| 'pohon'
|
||||
| 'air'
|
||||
| 'bantuan'
|
||||
| 'pelatihan'
|
||||
| 'subsidi'
|
||||
| 'layananKesehatan'
|
||||
| 'polisi'
|
||||
| 'ambulans'
|
||||
| 'pemadam'
|
||||
| 'rumahSakit'
|
||||
| 'bangunan'
|
||||
| 'darurat'
|
||||
|
||||
|
||||
const iconMap: Record<IconKey, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
kompetisi: IconTrophy,
|
||||
wisata: IconTent,
|
||||
ekonomi: IconChartLine,
|
||||
sampah: IconRecycle,
|
||||
truck: IconTruck,
|
||||
scale: IconScale,
|
||||
clipboard: IconClipboard,
|
||||
trash: IconTrash,
|
||||
lingkunganSehat: IconHomeEco,
|
||||
sumberOksigen: IconChristmasTreeFilled,
|
||||
ekonomiBerkelanjutan: IconTrendingUp,
|
||||
mencegahBencana: IconShieldFilled,
|
||||
rumah: IconHome,
|
||||
pohon: IconTree,
|
||||
air: IconDroplet,
|
||||
bantuan: IconCash,
|
||||
pelatihan: IconSchool,
|
||||
subsidi: IconShoppingCart,
|
||||
layananKesehatan: IconHospital,
|
||||
polisi: IconShieldFilled,
|
||||
ambulans: IconAmbulance,
|
||||
pemadam: IconFiretruck,
|
||||
rumahSakit: IconHospital,
|
||||
bangunan: IconBuilding,
|
||||
darurat: IconAlertTriangle
|
||||
}
|
||||
|
||||
type Props = {
|
||||
name: IconKey
|
||||
size?: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const IconMapper: React.FC<Props> = ({ name, size = 24, color }) => {
|
||||
const IconComponent = iconMap[name]
|
||||
if (!IconComponent) return null
|
||||
return <IconComponent size={size} color={color} />
|
||||
}
|
||||
36
src/app/admin/(dashboard)/_com/modalNonaktif.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// components/modal/ModalKonfirmasiHapus.tsx
|
||||
import colors from "@/con/colors"
|
||||
import { Modal, Text, Button, Flex } from "@mantine/core"
|
||||
|
||||
interface ModalKonfirmasiNonAktifProps {
|
||||
opened: boolean
|
||||
loading?: boolean
|
||||
onClose: () => void
|
||||
onConfirm: () => void
|
||||
text: string
|
||||
}
|
||||
|
||||
export function ModalKonfirmasiNonAktif({
|
||||
opened,
|
||||
loading = false,
|
||||
onClose,
|
||||
onConfirm,
|
||||
text,
|
||||
}: ModalKonfirmasiNonAktifProps) {
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
title={<Text fw={"bold"} fz={"xl"}>Konfirmasi Non Aktif</Text>}
|
||||
centered
|
||||
>
|
||||
<Text mb="md">{text}</Text>
|
||||
<Flex justify="flex-end" gap="sm">
|
||||
<Button style={{color: "white"}} bg={colors['blue-button']} variant="default" onClick={onClose}>Batal</Button>
|
||||
<Button color="red" onClick={onConfirm} loading={loading}>
|
||||
Yakin Non Aktif
|
||||
</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -3,16 +3,24 @@
|
||||
|
||||
import { Box, rem, Select } from '@mantine/core';
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
IconAmbulance,
|
||||
IconBuilding,
|
||||
IconCash,
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboardTextFilled,
|
||||
IconDroplet,
|
||||
IconFiretruck,
|
||||
IconHome,
|
||||
IconHomeEco,
|
||||
IconHospital,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconSchool,
|
||||
IconShieldFilled,
|
||||
IconShoppingCart,
|
||||
IconTent,
|
||||
IconTrashFilled,
|
||||
IconTree,
|
||||
@@ -32,13 +40,23 @@ const iconMap = {
|
||||
scale: { label: 'Scale', icon: IconScale },
|
||||
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
|
||||
trash: { label: 'Trash', icon: IconTrashFilled },
|
||||
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
|
||||
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
|
||||
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
|
||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||
rumah: {label: 'Rumah', icon: IconHome},
|
||||
pohon: {label: 'Pohon', icon: IconTree},
|
||||
air: {label: 'Air', icon: IconDroplet}
|
||||
lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
|
||||
sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
|
||||
ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
|
||||
mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
|
||||
rumah: { label: 'Rumah', icon: IconHome },
|
||||
pohon: { label: 'Pohon', icon: IconTree },
|
||||
air: { label: 'Air', icon: IconDroplet },
|
||||
bantuan: { label: 'Bantuan', icon: IconCash },
|
||||
pelatihan: { label: 'Pelatihan', icon: IconSchool },
|
||||
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
|
||||
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
|
||||
polisi: { label: 'Polisi', icon: IconShieldFilled },
|
||||
ambulans: { label: 'Ambulans', icon: IconAmbulance },
|
||||
pemadam: { label: 'Pemadam', icon: IconFiretruck },
|
||||
rumahSakit: { label: 'Rumah Sakit', icon: IconHospital },
|
||||
bangunan: { label: 'Bangunan', icon: IconBuilding },
|
||||
darurat: { label: 'Darurat', icon: IconAlertTriangle },
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import { Box, rem, Select } from '@mantine/core';
|
||||
import { Box, Group, rem, Select, SelectProps } from '@mantine/core';
|
||||
import {
|
||||
IconAmbulance,
|
||||
IconCash,
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboardTextFilled,
|
||||
IconDroplet,
|
||||
IconFiretruck,
|
||||
IconHome,
|
||||
IconHomeEco,
|
||||
IconHospital,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconSchool,
|
||||
IconShieldFilled,
|
||||
IconShoppingCart,
|
||||
IconTent,
|
||||
IconTrashFilled,
|
||||
IconTree,
|
||||
IconTrendingUp,
|
||||
IconTrophy,
|
||||
IconTruckFilled,
|
||||
IconBuilding,
|
||||
IconAlertTriangle,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
const iconMap = {
|
||||
@@ -30,16 +38,26 @@ const iconMap = {
|
||||
scale: { label: 'Scale', icon: IconScale },
|
||||
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
|
||||
trash: { label: 'Trash', icon: IconTrashFilled },
|
||||
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
|
||||
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
|
||||
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
|
||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||
rumah: {label: 'Rumah', icon: IconHome},
|
||||
pohon: {label: 'Pohon', icon: IconTree},
|
||||
air: {label: 'Air', icon: IconDroplet}
|
||||
lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
|
||||
sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
|
||||
ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
|
||||
mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
|
||||
rumah: { label: 'Rumah', icon: IconHome },
|
||||
pohon: { label: 'Pohon', icon: IconTree },
|
||||
air: { label: 'Air', icon: IconDroplet },
|
||||
bantuan: { label: 'Bantuan', icon: IconCash },
|
||||
pelatihan: { label: 'Pelatihan', icon: IconSchool },
|
||||
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
|
||||
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
|
||||
polisi: { label: 'Polisi', icon: IconShieldFilled },
|
||||
ambulans: { label: 'Ambulans', icon: IconAmbulance },
|
||||
pemadam: { label: 'Pemadam', icon: IconFiretruck },
|
||||
rumahSakit: { label: 'Rumah Sakit', icon: IconHospital },
|
||||
bangunan: { label: 'Bangunan', icon: IconBuilding },
|
||||
darurat: { label: 'Darurat', icon: IconAlertTriangle },
|
||||
};
|
||||
|
||||
type IconKey = keyof typeof iconMap;
|
||||
export type IconKey = keyof typeof iconMap;
|
||||
|
||||
const iconList = Object.entries(iconMap).map(([value, data]) => ({
|
||||
value,
|
||||
@@ -49,44 +67,52 @@ const iconList = Object.entries(iconMap).map(([value, data]) => ({
|
||||
export default function SelectIconProgramEdit({
|
||||
onChange,
|
||||
value,
|
||||
...props
|
||||
}: {
|
||||
onChange: (value: IconKey) => void;
|
||||
value: IconKey;
|
||||
}) {
|
||||
const IconComponent = iconMap[value]?.icon || null;
|
||||
|
||||
onChange: (value: IconKey | '') => void;
|
||||
value: IconKey | '';
|
||||
} & Omit<SelectProps, 'onChange' | 'value' | 'data'>) {
|
||||
return (
|
||||
<Box maw={300}>
|
||||
<Select
|
||||
placeholder="Pilih ikon"
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
if (value) onChange(value as IconKey);
|
||||
value={value || ''}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
onChange(val as IconKey);
|
||||
} else {
|
||||
onChange('');
|
||||
}
|
||||
}}
|
||||
data={iconList}
|
||||
renderOption={({ option }) => {
|
||||
const Icon = iconMap[option.value as IconKey]?.icon;
|
||||
return (
|
||||
<Group gap="sm">
|
||||
{Icon && <Icon size={18} stroke={1.5} />}
|
||||
{option.label}
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
leftSection={
|
||||
IconComponent && (
|
||||
<Box>
|
||||
<IconComponent size={24} stroke={1.5} />
|
||||
value && iconMap[value as IconKey] ? (
|
||||
<Box ml={-4}>
|
||||
{(() => {
|
||||
const Icon = iconMap[value as IconKey].icon;
|
||||
return <Icon size={20} stroke={1.5} />;
|
||||
})()}
|
||||
</Box>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
withCheckIcon={false}
|
||||
searchable={false}
|
||||
rightSectionWidth={0}
|
||||
searchable
|
||||
styles={{
|
||||
input: {
|
||||
textAlign: 'left',
|
||||
fontSize: rem(16),
|
||||
paddingLeft: 40,
|
||||
},
|
||||
section: {
|
||||
left: 10,
|
||||
right: 'auto',
|
||||
fontSize: rem(16),
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
76
src/app/admin/(dashboard)/_com/selectSocialMedia.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Image, Select, rem } from '@mantine/core';
|
||||
|
||||
const sosmedMap = {
|
||||
facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' },
|
||||
instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' },
|
||||
tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' },
|
||||
youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' },
|
||||
whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' },
|
||||
gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' },
|
||||
telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' },
|
||||
x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' },
|
||||
telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' },
|
||||
custom: { label: 'Custom Icon', src: null },
|
||||
};
|
||||
|
||||
type SosmedKey = keyof typeof sosmedMap;
|
||||
|
||||
const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({
|
||||
value,
|
||||
label: item.label,
|
||||
}));
|
||||
|
||||
export default function SelectSosialMedia({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: SosmedKey;
|
||||
onChange: (value: SosmedKey) => void;
|
||||
}) {
|
||||
const selected = value;
|
||||
const selectedImage = sosmedMap[selected]?.src;
|
||||
|
||||
return (
|
||||
<Box maw={300}>
|
||||
<Select
|
||||
placeholder="Pilih sosial media"
|
||||
value={selected}
|
||||
data={sosmedList}
|
||||
searchable={false}
|
||||
withCheckIcon={false}
|
||||
onChange={(val) => val && onChange(val as SosmedKey)}
|
||||
styles={{
|
||||
input: {
|
||||
textAlign: 'left',
|
||||
fontSize: rem(16),
|
||||
paddingLeft: 36,
|
||||
},
|
||||
section: {
|
||||
left: 10,
|
||||
right: 'auto',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 🔥 PREVIEW DIPISAH DI LUAR SELECT */}
|
||||
{selectedImage && (
|
||||
<Box mt="md">
|
||||
<Image
|
||||
alt=""
|
||||
src={selectedImage}
|
||||
radius="md"
|
||||
style={{
|
||||
width: 120,
|
||||
height: 120,
|
||||
objectFit: 'contain',
|
||||
border: '1px solid #eee',
|
||||
padding: 8,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
56
src/app/admin/(dashboard)/_com/selectSocialMediaEdit.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Select } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const sosmedMap = {
|
||||
facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' },
|
||||
instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' },
|
||||
tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' },
|
||||
youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' },
|
||||
whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' },
|
||||
gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' },
|
||||
telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' },
|
||||
x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' },
|
||||
telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' },
|
||||
custom: { label: 'Custom Icon', src: null },
|
||||
};
|
||||
|
||||
type SosmedKey = keyof typeof sosmedMap;
|
||||
|
||||
const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({
|
||||
value,
|
||||
label: item.label,
|
||||
}));
|
||||
|
||||
export default function SelectSocialMediaEdit({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (val: SosmedKey) => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<SosmedKey>('facebook');
|
||||
|
||||
useEffect(() => {
|
||||
if (value && sosmedMap[value as SosmedKey]) {
|
||||
setSelected(value as SosmedKey);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Select
|
||||
label="Jenis Media Sosial"
|
||||
value={selected}
|
||||
data={sosmedList}
|
||||
searchable={false}
|
||||
onChange={(val) => {
|
||||
if (!val) return;
|
||||
setSelected(val as SosmedKey);
|
||||
onChange(val as SosmedKey);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -74,11 +74,12 @@ const berita = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||
berita.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||
const startTime = Date.now();
|
||||
berita.findMany.loading = true;
|
||||
berita.findMany.page = page;
|
||||
berita.findMany.search = search;
|
||||
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
@@ -98,9 +99,16 @@ const berita = proxy({
|
||||
berita.findMany.data = [];
|
||||
berita.findMany.totalPages = 1;
|
||||
} finally {
|
||||
berita.findMany.loading = false;
|
||||
// pastikan minimal 300ms sebelum loading = false (biar UX smooth)
|
||||
const elapsed = Date.now() - startTime;
|
||||
const minDelay = 300;
|
||||
const delay = elapsed < minDelay ? minDelay - elapsed : 0;
|
||||
|
||||
setTimeout(() => {
|
||||
berita.findMany.loading = false;
|
||||
}, delay);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
@@ -368,11 +376,37 @@ const kategoriBerita = proxy({
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoriberita["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriBerita.findMany.data = res.data?.data ?? [];
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kategoriBerita.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kategoriBerita.findMany.page = page;
|
||||
kategoriBerita.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.kategoriberita[
|
||||
"findMany"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriBerita.findMany.data = res.data.data ?? [];
|
||||
kategoriBerita.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kategoriBerita.findMany.data = [];
|
||||
kategoriBerita.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori berita paginated:", err);
|
||||
kategoriBerita.findMany.data = [];
|
||||
kategoriBerita.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriBerita.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -68,10 +69,34 @@ const foto = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
foto.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
foto.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
foto.findMany.page = page;
|
||||
foto.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
foto.findMany.data = res.data.data ?? [];
|
||||
foto.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
foto.findMany.data = [];
|
||||
foto.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch foto paginated:", err);
|
||||
foto.findMany.data = [];
|
||||
foto.findMany.totalPages = 1;
|
||||
} finally {
|
||||
foto.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -215,6 +240,28 @@ const foto = proxy({
|
||||
foto.update.form = { ...defaultFormFoto };
|
||||
},
|
||||
},
|
||||
findRecent: {
|
||||
data: [] as Prisma.GalleryFotoGetPayload<{
|
||||
include: {
|
||||
imageGalleryFoto: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.desa.gallery.foto["find-recent"].get();
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
this.data = res.data.data ?? [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal fetch foto recent:", error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const video = proxy({
|
||||
@@ -257,10 +304,34 @@ const video = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
video.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
video.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
video.findMany.page = page;
|
||||
video.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.gallery.video["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
video.findMany.data = res.data.data ?? [];
|
||||
video.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
video.findMany.data = [];
|
||||
video.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch video paginated:", err);
|
||||
video.findMany.data = [];
|
||||
video.findMany.totalPages = 1;
|
||||
} finally {
|
||||
video.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,7 +30,6 @@ const templateTelunjukSaktiDesaForm = z.object({
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
});
|
||||
|
||||
|
||||
const templatePelayananPerizinanBerusaha = z.object({
|
||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
@@ -72,6 +71,21 @@ const pelayananPendudukNonPermanenForm = {
|
||||
deskripsi: "",
|
||||
};
|
||||
|
||||
const templateAjukanForm = z.object({
|
||||
nama: z.string().min(1).max(5000),
|
||||
nik: z.string().min(1).max(5000),
|
||||
alamat: z.string().min(1).max(5000),
|
||||
nomorKk: z.string().min(1).max(5000),
|
||||
kategoriId: z.string().min(1).max(5000),
|
||||
});
|
||||
|
||||
const defaultAjukanForm = {
|
||||
nama: "",
|
||||
nik: "",
|
||||
alamat: "",
|
||||
nomorKk: "",
|
||||
kategoriId: "",
|
||||
};
|
||||
|
||||
const suratKeterangan = proxy({
|
||||
create: {
|
||||
@@ -113,16 +127,21 @@ const suratKeterangan = proxy({
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
suratKeterangan.findMany.loading = true; // Use the full path to access the property
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
suratKeterangan.findMany.loading = true; // Use the full path to access the property
|
||||
suratKeterangan.findMany.page = page;
|
||||
suratKeterangan.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
query,
|
||||
});
|
||||
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
suratKeterangan.findMany.data = res.data.data || [];
|
||||
suratKeterangan.findMany.total = res.data.total || 0;
|
||||
@@ -143,6 +162,30 @@ const suratKeterangan = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
findManyAll: {
|
||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[] | null,
|
||||
loading: false,
|
||||
load: async () => {
|
||||
suratKeterangan.findManyAll.loading = true;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan["findManyAll"].get();
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
suratKeterangan.findManyAll.data = res.data.data || [];
|
||||
} else {
|
||||
suratKeterangan.findManyAll.data = [];
|
||||
console.error("Failed to load surat keterangan all:", res.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading surat keterangan all:", error);
|
||||
suratKeterangan.findManyAll.data = [];
|
||||
} finally {
|
||||
suratKeterangan.findManyAll.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||
include: {
|
||||
@@ -341,28 +384,34 @@ const pelayananTelunjukSaktiDesa = proxy({
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
|
||||
pelayananTelunjukSaktiDesa.findMany.page = page;
|
||||
pelayananTelunjukSaktiDesa.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
query,
|
||||
});
|
||||
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pelayananTelunjukSaktiDesa.findMany.data = res.data.data || [];
|
||||
pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0;
|
||||
pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1;
|
||||
pelayananTelunjukSaktiDesa.findMany.totalPages =
|
||||
res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load telunjuk sakti desa:", res.data?.message);
|
||||
console.error("Failed to load surat keterangan:", res.data?.message);
|
||||
pelayananTelunjukSaktiDesa.findMany.data = [];
|
||||
pelayananTelunjukSaktiDesa.findMany.total = 0;
|
||||
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
|
||||
suratKeterangan.findMany.total = 0;
|
||||
suratKeterangan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading telunjuk sakti desa:", error);
|
||||
console.error("Error loading surat keterangan:", error);
|
||||
pelayananTelunjukSaktiDesa.findMany.data = [];
|
||||
pelayananTelunjukSaktiDesa.findMany.total = 0;
|
||||
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
|
||||
@@ -410,7 +459,9 @@ const pelayananTelunjukSaktiDesa = proxy({
|
||||
);
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus");
|
||||
toast.success(
|
||||
result.message || "Telunjuk Sakti Desa berhasil dihapus"
|
||||
);
|
||||
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result.message || "Gagal menghapus telunjuk sakti desa");
|
||||
@@ -501,7 +552,9 @@ const pelayananTelunjukSaktiDesa = proxy({
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate");
|
||||
toast.success(
|
||||
result.message || "Telunjuk Sakti Desa berhasil diupdate"
|
||||
);
|
||||
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
@@ -522,39 +575,30 @@ const pelayananTelunjukSaktiDesa = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const pelayananPerizinanBerusaha = proxy({
|
||||
findById: {
|
||||
data: null as pelayananPerizinanBerusahaForm | null,
|
||||
loading: false,
|
||||
initialize() {
|
||||
pelayananPerizinanBerusaha.findById.data = {
|
||||
id: "",
|
||||
name: "",
|
||||
deskripsi: "",
|
||||
link: "",
|
||||
} as pelayananPerizinanBerusahaForm;
|
||||
},
|
||||
async load(id: string) {
|
||||
try {
|
||||
pelayananPerizinanBerusaha.findById.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/desa/layanan/pelayananperizinanberusaha/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
pelayananPerizinanBerusaha.findById.data = data.data ?? null;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch pelayanan perizinan berusaha:",
|
||||
res.statusText
|
||||
);
|
||||
pelayananPerizinanBerusaha.findById.data = null;
|
||||
this.loading = true;
|
||||
const response = await fetch(`/api/desa/layanan/pelayananperizinanberusaha/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
this.data = result.data; // Make sure this matches your API response structure
|
||||
}
|
||||
return result?.data || null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching pelayanan perizinan berusaha:", error);
|
||||
pelayananPerizinanBerusaha.findById.data = null;
|
||||
console.error('Error loading data:', error);
|
||||
toast.error('Gagal memuat data');
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -596,9 +640,7 @@ const pelayananPerizinanBerusaha = proxy({
|
||||
} catch (error) {
|
||||
console.error("Error fetching pelayanan perizinan berusaha:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Gagal memuat data"
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -713,9 +755,7 @@ const pelayananPendudukNonPermanen = proxy({
|
||||
} catch (error) {
|
||||
console.error("Error fetching pelayanan penduduk non permanen:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Gagal memuat data"
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -760,11 +800,250 @@ const pelayananPendudukNonPermanen = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
const ajukanPermohonan = proxy({
|
||||
create: {
|
||||
form: { ...defaultAjukanForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateAjukanForm.safeParse(
|
||||
ajukanPermohonan.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
ajukanPermohonan.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||
"create"
|
||||
].post(ajukanPermohonan.create.form);
|
||||
if (res.status === 200) {
|
||||
ajukanPermohonan.findMany.load();
|
||||
return toast.success("Ajukan permohonan berhasil disimpan!");
|
||||
}
|
||||
return toast.error("Gagal menyimpan ajukan permohonan");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
ajukanPermohonan.create.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
ajukanPermohonan.create.form = { ...defaultAjukanForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
};
|
||||
}>[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
ajukanPermohonan.findMany.loading = true; // Use the full path to access the property
|
||||
ajukanPermohonan.findMany.page = page;
|
||||
ajukanPermohonan.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
const res = await ApiFetch.api.desa.ajukanpermohonan[
|
||||
"findMany"
|
||||
].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
ajukanPermohonan.findMany.data = res.data.data || [];
|
||||
ajukanPermohonan.findMany.total = res.data.total || 0;
|
||||
ajukanPermohonan.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load ajukan permohonan:", res.data?.message);
|
||||
ajukanPermohonan.findMany.data = [];
|
||||
ajukanPermohonan.findMany.total = 0;
|
||||
ajukanPermohonan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading ajukan permohonan:", error);
|
||||
ajukanPermohonan.findMany.data = [];
|
||||
ajukanPermohonan.findMany.total = 0;
|
||||
ajukanPermohonan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
ajukanPermohonan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.AjukanPermohonanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
}
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
ajukanPermohonan.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch ajukan permohonan:", res.statusText);
|
||||
ajukanPermohonan.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching ajukan permohonan:", error);
|
||||
ajukanPermohonan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
ajukanPermohonan.delete.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Ajukan permohonan berhasil dihapus");
|
||||
await ajukanPermohonan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result.message || "Gagal menghapus ajukan permohonan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus ajukan permohonan");
|
||||
} finally {
|
||||
ajukanPermohonan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...defaultAjukanForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
nik: data.nik,
|
||||
alamat: data.alamat,
|
||||
nomorKk: data.nomorKk,
|
||||
kategoriId: data.kategoriId,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching ajukan permohonan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateAjukanForm.safeParse(
|
||||
ajukanPermohonan.edit.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
ajukanPermohonan.edit.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/desa/ajukanpermohonan/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
nik: this.form.nik,
|
||||
alamat: this.form.alamat,
|
||||
nomorKk: this.form.nomorKk,
|
||||
kategoriId: this.form.kategoriId,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Ajukan permohonan berhasil diupdate");
|
||||
await ajukanPermohonan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal mengupdate ajukan permohonan"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating ajukan permohonan:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update ajukan permohonan"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
ajukanPermohonan.edit.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateLayananDesa = proxy({
|
||||
suratKeterangan,
|
||||
pelayananPerizinanBerusaha,
|
||||
pelayananTelunjukSaktiDesa,
|
||||
pelayananPendudukNonPermanen,
|
||||
ajukanPermohonan,
|
||||
});
|
||||
|
||||
export default stateLayananDesa;
|
||||
|
||||
@@ -39,7 +39,7 @@ const penghargaanState = proxy({
|
||||
);
|
||||
if (res.status === 200) {
|
||||
penghargaanState.findMany.load();
|
||||
return toast.success("success create");
|
||||
return toast.success("Sukses menambahkan");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
@@ -56,16 +56,21 @@ const penghargaanState = proxy({
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
penghargaanState.findMany.loading = true; // Use the full path to access the property
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
penghargaanState.findMany.loading = true; // Use the full path to access the property
|
||||
penghargaanState.findMany.page = page;
|
||||
penghargaanState.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
const res = await ApiFetch.api.desa.penghargaan[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
query,
|
||||
});
|
||||
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
penghargaanState.findMany.data = res.data.data || [];
|
||||
penghargaanState.findMany.total = res.data.total || 0;
|
||||
|
||||
@@ -55,11 +55,39 @@ const category = proxy({
|
||||
pengumumans: number;
|
||||
};
|
||||
})[],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = res.data?.data ?? [];
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||
category.findMany.loading = true; // Use the full path to access the property
|
||||
category.findMany.page = page;
|
||||
category.findMany.search = search;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit, search },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
category.findMany.data = res.data.data || [];
|
||||
category.findMany.total = res.data.total || 0;
|
||||
category.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load potensi desa:", res.data?.message);
|
||||
category.findMany.data = [];
|
||||
category.findMany.total = 0;
|
||||
category.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading potensi desa:", error);
|
||||
category.findMany.data = [];
|
||||
category.findMany.total = 0;
|
||||
category.findMany.totalPages = 1;
|
||||
} finally {
|
||||
category.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -259,7 +287,7 @@ const pengumuman = proxy({
|
||||
);
|
||||
if (res.status === 200) {
|
||||
pengumuman.findMany.load();
|
||||
return toast.success("success create");
|
||||
return toast.success("Sukses menambahkan");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
|
||||
@@ -56,14 +56,16 @@ const potensiDesa = proxy({
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||
potensiDesa.findMany.loading = true; // Use the full path to access the property
|
||||
potensiDesa.findMany.page = page;
|
||||
potensiDesa.findMany.search = search;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.potensi[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
query: { page, limit, search },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
@@ -298,11 +300,34 @@ const kategoriPotensi = proxy({
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriPotensi.findMany.data = res.data?.data ?? [];
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kategoriPotensi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kategoriPotensi.findMany.page = page;
|
||||
kategoriPotensi.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriPotensi.findMany.data = res.data.data ?? [];
|
||||
kategoriPotensi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kategoriPotensi.findMany.data = [];
|
||||
kategoriPotensi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori potensi paginated:", err);
|
||||
kategoriPotensi.findMany.data = [];
|
||||
kategoriPotensi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriPotensi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { toast } from "react-toastify";
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
|
||||
// ========================================= SEJARAH DESA ========================================= //
|
||||
const sejarahDesaForm = z.object({
|
||||
@@ -106,16 +108,13 @@ const sejarahDesa = proxy({
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/profile/sejarah/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/profile/sejarah/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
@@ -409,16 +408,13 @@ const lambangDesa = proxy({
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/profile/lambang/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/profile/lambang/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
@@ -588,14 +584,11 @@ const maskotDesa = proxy({
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/desa/profile/maskot/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/desa/profile/maskot/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@@ -818,12 +811,247 @@ const profilPerbekel = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
//========================================= MANTAN PERBEKEL ========================================= //
|
||||
const mantanPerbekelForm = z.object({
|
||||
nama: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
daerah: z.string().min(3, "Daerah minimal 3 karakter"),
|
||||
periode: z.string().min(3, "Periode minimal 3 karakter"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
});
|
||||
|
||||
const mantanPerbekelDefaultForm = {
|
||||
nama: "",
|
||||
daerah: "",
|
||||
periode: "",
|
||||
imageId: "",
|
||||
};
|
||||
|
||||
const mantanPerbekel = proxy({
|
||||
create: {
|
||||
form: { ...mantanPerbekelDefaultForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = mantanPerbekelForm.safeParse(mantanPerbekel.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
mantanPerbekel.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.mantanperbekel["create"].post(
|
||||
mantanPerbekel.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
mantanPerbekel.findMany.load();
|
||||
return toast.success("Foto berhasil disimpan!");
|
||||
}
|
||||
return toast.error("Gagal menyimpan foto");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
mantanPerbekel.create.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
mantanPerbekel.create.form = { ...mantanPerbekelDefaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.PerbekelDariMasaKeMasaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
mantanPerbekel.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
mantanPerbekel.findMany.page = page;
|
||||
mantanPerbekel.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.desa.mantanperbekel["findMany"].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
mantanPerbekel.findMany.data = res.data.data ?? [];
|
||||
mantanPerbekel.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
mantanPerbekel.findMany.data = [];
|
||||
mantanPerbekel.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch mantan perbekel paginated:", err);
|
||||
mantanPerbekel.findMany.data = [];
|
||||
mantanPerbekel.findMany.totalPages = 1;
|
||||
} finally {
|
||||
mantanPerbekel.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PerbekelDariMasaKeMasaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/mantanperbekel/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
mantanPerbekel.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch mantan perbekel:", res.statusText);
|
||||
mantanPerbekel.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching mantan perbekel:", error);
|
||||
mantanPerbekel.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
mantanPerbekel.delete.loading = true;
|
||||
const response = await fetch(`/api/desa/mantanperbekel/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Mantan perbekel berhasil dihapus");
|
||||
await mantanPerbekel.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result.message || "Gagal menghapus mantan perbekel");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus mantan perbekel");
|
||||
} finally {
|
||||
mantanPerbekel.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...mantanPerbekelDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/desa/mantanperbekel/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
daerah: data.daerah,
|
||||
periode: data.periode,
|
||||
imageId: data.imageId || "",
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading foto:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = mantanPerbekelForm.safeParse(mantanPerbekel.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
mantanPerbekel.update.loading = true;
|
||||
const response = await fetch(`/api/desa/mantanperbekel/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
daerah: this.form.daerah,
|
||||
periode: this.form.periode,
|
||||
imageId: this.form.imageId,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success(result.message || "Mantan perbekel berhasil diupdate");
|
||||
await mantanPerbekel.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate mantan perbekel");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating mantan perbekel:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Gagal mengupdate mantan perbekel"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
mantanPerbekel.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
mantanPerbekel.update.id = "";
|
||||
mantanPerbekel.update.form = { ...mantanPerbekelDefaultForm };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateProfileDesa = proxy({
|
||||
lambangDesa,
|
||||
maskotDesa,
|
||||
profilPerbekel,
|
||||
visiMisiDesa,
|
||||
sejarahDesa,
|
||||
mantanPerbekel,
|
||||
});
|
||||
|
||||
export default stateProfileDesa;
|
||||
|
||||
@@ -7,9 +7,13 @@ import { z } from "zod";
|
||||
|
||||
const templateApbDesa = z.object({
|
||||
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
|
||||
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"),
|
||||
pembiayaanIds: z
|
||||
.array(z.string().uuid())
|
||||
.nonempty("Pilih minimal 1 pembiayaan"),
|
||||
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
|
||||
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"),
|
||||
pendapatanIds: z
|
||||
.array(z.string().uuid())
|
||||
.nonempty("Pilih minimal 1 pendapatan"),
|
||||
});
|
||||
|
||||
const ApbDesaDefaultForm = {
|
||||
@@ -54,35 +58,81 @@ const ApbDesa = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.ApbDesaGetPayload<{
|
||||
include: {
|
||||
pendapatan: true;
|
||||
belanja: true;
|
||||
pembiayaan: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
data: null as
|
||||
| Prisma.ApbDesaGetPayload<{
|
||||
include: {
|
||||
pendapatan: true;
|
||||
belanja: true;
|
||||
pembiayaan: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
ApbDesa.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
ApbDesa.findMany.page = page;
|
||||
ApbDesa.findMany.search = search;
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
ApbDesa.findMany.data = res.data.data ?? [];
|
||||
ApbDesa.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil APB Desa");
|
||||
ApbDesa.findMany.data = [];
|
||||
ApbDesa.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch APB Desa paginated:", err);
|
||||
ApbDesa.findMany.data = [];
|
||||
ApbDesa.findMany.totalPages = 1;
|
||||
} finally {
|
||||
ApbDesa.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findFirst: {
|
||||
data: null as Prisma.ApbDesaGetPayload<{
|
||||
include: { pendapatan: true; belanja: true; pembiayaan: true };
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(params?: Record<string, any>) {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
// ✅ request ke endpoint find-first
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
||||
"find-first"
|
||||
].get({ query: params || {} });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
this.data = res.data.data ?? null;
|
||||
} else {
|
||||
this.data = null;
|
||||
toast.error(res.data?.message || "Gagal memuat data pertama APB Desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil APB Desa");
|
||||
console.error("Error findFirst APB Desa:", error);
|
||||
toast.error("Gagal memuat data APB Desa pertama");
|
||||
this.data = null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
reset() {
|
||||
this.data = null;
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...ApbDesaDefaultForm },
|
||||
@@ -106,13 +156,13 @@ const ApbDesa = proxy({
|
||||
throw new Error("Gagal mengambil APB Desa");
|
||||
}
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || "Gagal memuat APB Desa");
|
||||
}
|
||||
|
||||
|
||||
const data = result.data;
|
||||
|
||||
|
||||
this.id = id;
|
||||
this.form = {
|
||||
tahun: data.tahun || 0,
|
||||
@@ -120,7 +170,7 @@ const ApbDesa = proxy({
|
||||
belanjaIds: data.belanja?.map((b: any) => b.id) || [],
|
||||
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
|
||||
};
|
||||
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error loading APB Desa:", error);
|
||||
@@ -189,7 +239,7 @@ const ApbDesa = proxy({
|
||||
data: null as Prisma.ApbDesaGetPayload<{
|
||||
include: { pendapatan: true; belanja: true; pembiayaan: true };
|
||||
}> | null,
|
||||
|
||||
|
||||
async load(id: string) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
@@ -199,11 +249,11 @@ const ApbDesa = proxy({
|
||||
throw new Error("Gagal mengambil detail APB Desa");
|
||||
}
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || "Gagal mengambil data");
|
||||
}
|
||||
|
||||
|
||||
this.data = result.data; // ✅ fix utama di sini
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
@@ -264,34 +314,32 @@ const pendapatan = proxy({
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
pendapatan.findMany.loading = true; // Use the full path to access the property
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
pendapatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pendapatan.findMany.page = page;
|
||||
pendapatan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const res =
|
||||
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pendapatan.findMany.data = res.data.data || [];
|
||||
pendapatan.findMany.total = res.data.total || 0;
|
||||
pendapatan.findMany.totalPages = res.data.totalPages || 1;
|
||||
pendapatan.findMany.data = res.data.data ?? [];
|
||||
pendapatan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
console.error("Failed to load pendapatan:", res.data?.message);
|
||||
pendapatan.findMany.data = [];
|
||||
pendapatan.findMany.total = 0;
|
||||
pendapatan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pendapatan:", error);
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch pendapatan asli desa paginated:", err);
|
||||
pendapatan.findMany.data = [];
|
||||
pendapatan.findMany.total = 0;
|
||||
pendapatan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pendapatan.findMany.loading = false;
|
||||
@@ -308,12 +356,15 @@ const pendapatan = proxy({
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -349,16 +400,19 @@ const pendapatan = proxy({
|
||||
|
||||
try {
|
||||
pendapatan.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
@@ -495,23 +549,37 @@ const belanja = proxy({
|
||||
name: string;
|
||||
value: number;
|
||||
}>,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
belanja.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
belanja.findMany.page = page;
|
||||
belanja.findMany.search = search;
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
belanja.findMany.data = res.data.data ?? [];
|
||||
belanja.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil Belanja");
|
||||
belanja.findMany.data = [];
|
||||
belanja.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil Belanja");
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch Belanja paginated:", err);
|
||||
belanja.findMany.data = [];
|
||||
belanja.findMany.totalPages = 1;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
belanja.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -525,12 +593,15 @@ const belanja = proxy({
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/belanja/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -566,16 +637,19 @@ const belanja = proxy({
|
||||
|
||||
try {
|
||||
belanja.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
@@ -710,23 +784,37 @@ const pembiayaan = proxy({
|
||||
name: string;
|
||||
value: number;
|
||||
}>,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
pembiayaan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pembiayaan.findMany.page = page;
|
||||
pembiayaan.findMany.search = search;
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pembiayaan.findMany.data = res.data.data ?? [];
|
||||
pembiayaan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil Pembiayaan");
|
||||
pembiayaan.findMany.data = [];
|
||||
pembiayaan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil Pembiayaan");
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch Pembiayaan paginated:", err);
|
||||
pembiayaan.findMany.data = [];
|
||||
pembiayaan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
pembiayaan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -740,12 +828,15 @@ const pembiayaan = proxy({
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -781,16 +872,19 @@ const pembiayaan = proxy({
|
||||
|
||||
try {
|
||||
pembiayaan.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -48,7 +49,7 @@ const demografiPekerjaan = proxy({
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
demografiPekerjaan.create.form = { ...defaultForm };
|
||||
demografiPekerjaan.findMany.load();
|
||||
return id;
|
||||
@@ -71,12 +72,37 @@ const demografiPekerjaan = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
demografiPekerjaan.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
demografiPekerjaan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
demografiPekerjaan.findMany.page = page;
|
||||
demografiPekerjaan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
demografiPekerjaan.findMany.data = res.data.data ?? [];
|
||||
demografiPekerjaan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
demografiPekerjaan.findMany.data = [];
|
||||
demografiPekerjaan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch demografi pekerjaan paginated:", err);
|
||||
demografiPekerjaan.findMany.data = [];
|
||||
demografiPekerjaan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
demografiPekerjaan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -194,4 +220,4 @@ const demografiPekerjaan = proxy({
|
||||
},
|
||||
},
|
||||
});
|
||||
export default demografiPekerjaan
|
||||
export default demografiPekerjaan;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -46,7 +47,7 @@ const jumlahPendudukMiskin = proxy({
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
jumlahPendudukMiskin.create.form = {
|
||||
year: 0,
|
||||
totalPoorPopulation: 0,
|
||||
@@ -69,16 +70,37 @@ const jumlahPendudukMiskin = proxy({
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
jumlahPendudukMiskin.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
jumlahPendudukMiskin.findMany.page = page;
|
||||
jumlahPendudukMiskin.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data.data ?? [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
jumlahPendudukMiskin.findMany.data = [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch jumlah penduduk miskin paginated:", err);
|
||||
jumlahPendudukMiskin.findMany.data = [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jumlahPendudukMiskin.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
|
||||
@@ -15,7 +15,8 @@ const templateJumlahPengngguran = z.object({
|
||||
uneducatedUnemployment: z
|
||||
.number()
|
||||
.min(1, "Pengangguran tidak pendidikan harus diisi"),
|
||||
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"),
|
||||
percentageChange: z.number({ invalid_type_error: "Persentase perubahan harus angka" }),
|
||||
|
||||
});
|
||||
|
||||
type JumlahPengangguran = {
|
||||
@@ -29,7 +30,7 @@ type JumlahPengangguran = {
|
||||
|
||||
const jumlahPengangguranForm: JumlahPengangguran = {
|
||||
month: "",
|
||||
year: 0,
|
||||
year: new Date().getFullYear(), // Default to current year
|
||||
totalUnemployment: 0,
|
||||
educatedUnemployment: 0,
|
||||
uneducatedUnemployment: 0,
|
||||
@@ -60,13 +61,21 @@ const jumlahPengangguran = proxy({
|
||||
form: jumlahPengangguranForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateJumlahPengngguran.safeParse(
|
||||
jumlahPengangguran.create.form
|
||||
);
|
||||
// Ensure all number fields are actual numbers
|
||||
const formData = {
|
||||
...jumlahPengangguran.create.form,
|
||||
year: Number(jumlahPengangguran.create.form.year) || new Date().getFullYear(),
|
||||
totalUnemployment: Number(jumlahPengangguran.create.form.totalUnemployment) || 0,
|
||||
educatedUnemployment: Number(jumlahPengangguran.create.form.educatedUnemployment) || 0,
|
||||
uneducatedUnemployment: Number(jumlahPengangguran.create.form.uneducatedUnemployment) || 0,
|
||||
percentageChange: Number(jumlahPengangguran.create.form.percentageChange) || 0,
|
||||
};
|
||||
|
||||
const cek = templateJumlahPengngguran.safeParse(formData);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
.map((v) => `${v.path.join(".")} (${v.message})`)
|
||||
.join("\n")}]`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
@@ -78,9 +87,9 @@ const jumlahPengangguran = proxy({
|
||||
].post(jumlahPengangguran.create.form);
|
||||
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
const id = res.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
|
||||
jumlahPengangguran.findMany.load();
|
||||
return id;
|
||||
@@ -103,16 +112,40 @@ const jumlahPengangguran = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jumlahPengangguran.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
jumlahPengangguran.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
jumlahPengangguran.findMany.page = page;
|
||||
jumlahPengangguran.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jumlahPengangguran.findMany.data = res.data.data ?? [];
|
||||
jumlahPengangguran.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
jumlahPengangguran.findMany.data = [];
|
||||
jumlahPengangguran.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch jumlah pengangguran paginated:", err);
|
||||
jumlahPengangguran.findMany.data = [];
|
||||
jumlahPengangguran.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jumlahPengangguran.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
data: null as Prisma.DetailDataPengangguranGetPayload<{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -12,6 +13,7 @@ const templateForm = z.object({
|
||||
gaji: z.string(),
|
||||
deskripsi: z.string(),
|
||||
kualifikasi: z.string(),
|
||||
notelp: z.string(),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
@@ -22,6 +24,7 @@ const defaultForm = {
|
||||
gaji: "",
|
||||
deskripsi: "",
|
||||
kualifikasi: "",
|
||||
notelp: "",
|
||||
};
|
||||
|
||||
const lowonganKerjaState = proxy({
|
||||
@@ -44,7 +47,7 @@ const lowonganKerjaState = proxy({
|
||||
);
|
||||
if (res.status === 200) {
|
||||
lowonganKerjaState.create.loading = false;
|
||||
return toast.success("success create");
|
||||
return toast.success("Sukses menambahkan");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
@@ -61,13 +64,39 @@ const lowonganKerjaState = proxy({
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.LowonganPekerjaanGetPayload<{
|
||||
omit: { isActive: true };
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
lowonganKerjaState.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
lowonganKerjaState.findMany.page = page;
|
||||
lowonganKerjaState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
lowonganKerjaState.findMany.data = res.data.data ?? [];
|
||||
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
lowonganKerjaState.findMany.data = [];
|
||||
lowonganKerjaState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch lowongan kerja paginated:", err);
|
||||
lowonganKerjaState.findMany.data = [];
|
||||
lowonganKerjaState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
lowonganKerjaState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -152,6 +181,7 @@ const lowonganKerjaState = proxy({
|
||||
gaji: data.gaji,
|
||||
deskripsi: data.deskripsi,
|
||||
kualifikasi: data.kualifikasi,
|
||||
notelp: data.notelp,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -191,6 +221,7 @@ const lowonganKerjaState = proxy({
|
||||
gaji: this.form.gaji,
|
||||
deskripsi: this.form.deskripsi,
|
||||
kualifikasi: this.form.kualifikasi,
|
||||
notelp: this.form.notelp,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -11,6 +12,7 @@ const templatePasarDesaForm = z.object({
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
rating: z.number().min(1, "Rating minimal 1"),
|
||||
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
|
||||
kontak: z.string().min(1, "Kontak wajib diisi"),
|
||||
});
|
||||
|
||||
const defaultPasarDesaForm = {
|
||||
@@ -20,6 +22,7 @@ const defaultPasarDesaForm = {
|
||||
imageId: "",
|
||||
rating: 0,
|
||||
kategoriId: [] as string[],
|
||||
kontak: "",
|
||||
};
|
||||
|
||||
const pasarDesa = proxy({
|
||||
@@ -53,22 +56,47 @@ const pasarDesa = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<
|
||||
Prisma.PasarDesaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
KategoriToPasar: {
|
||||
include: {
|
||||
kategori: true;
|
||||
data: null as
|
||||
| Prisma.PasarDesaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
KategoriToPasar: {
|
||||
include: {
|
||||
kategori: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
pasarDesa.findMany.data = res.data?.data ?? [];
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
|
||||
pasarDesa.findMany.loading = true;
|
||||
pasarDesa.findMany.page = page;
|
||||
pasarDesa.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
if (categoryId) query.categoryId = categoryId;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pasarDesa.findMany.data = res.data.data ?? [];
|
||||
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
pasarDesa.findMany.data = [];
|
||||
pasarDesa.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch keamanan lingkungan paginated:", err);
|
||||
pasarDesa.findMany.data = [];
|
||||
pasarDesa.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pasarDesa.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -162,6 +190,7 @@ const pasarDesa = proxy({
|
||||
imageId: data.imageId,
|
||||
rating: data.rating,
|
||||
kategoriId: data.kategoriId,
|
||||
kontak: data.kontak,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -199,6 +228,7 @@ const pasarDesa = proxy({
|
||||
imageId: this.form.imageId,
|
||||
rating: this.form.rating,
|
||||
kategoriId: this.form.kategoriId,
|
||||
kontak: this.form.kontak,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -272,14 +302,75 @@ const kategoriProduk = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<{
|
||||
id: string;
|
||||
nama: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriProduk.findMany.data = res.data?.data ?? [];
|
||||
data: null as
|
||||
| Prisma.KategoriProdukGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kategoriProduk.findMany.page = page;
|
||||
kategoriProduk.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriProduk.findMany.data = res.data.data ?? [];
|
||||
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kategoriProduk.findMany.data = [];
|
||||
kategoriProduk.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori produk paginated:", err);
|
||||
kategoriProduk.findMany.data = [];
|
||||
kategoriProduk.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriProduk.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
// ✅ Versi findManyAll (ambil semua tanpa pagination)
|
||||
findManyAll: {
|
||||
data: null as
|
||||
| Prisma.KategoriProdukGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (search = "") => {
|
||||
kategoriProduk.findManyAll.loading = true;
|
||||
kategoriProduk.findManyAll.search = search;
|
||||
|
||||
try {
|
||||
const query: any = {};
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many-all"].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriProduk.findManyAll.data = res.data.data ?? [];
|
||||
} else {
|
||||
kategoriProduk.findManyAll.data = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori produk (all):", err);
|
||||
kategoriProduk.findManyAll.data = [];
|
||||
} finally {
|
||||
kategoriProduk.findManyAll.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -7,22 +8,21 @@ import { z } from "zod";
|
||||
const templateForm = z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
ikonUrl: z.string().optional(),
|
||||
icon: z.string().min(1, "Icon minimal 1 karakter"),
|
||||
statistik: z.object({
|
||||
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
||||
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
||||
})
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
ikonUrl: "",
|
||||
icon: "",
|
||||
statistik: {
|
||||
tahun: "",
|
||||
jumlah: ""
|
||||
}
|
||||
jumlah: "",
|
||||
},
|
||||
};
|
||||
|
||||
const programKemiskinanState = proxy({
|
||||
@@ -45,7 +45,7 @@ const programKemiskinanState = proxy({
|
||||
);
|
||||
if (res.status === 200) {
|
||||
programKemiskinanState.findMany.load();
|
||||
return toast.success("success create");
|
||||
return toast.success("Sukses menambahkan");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
@@ -64,12 +64,35 @@ const programKemiskinanState = proxy({
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
programKemiskinanState.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
programKemiskinanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
programKemiskinanState.findMany.page = page;
|
||||
programKemiskinanState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
programKemiskinanState.findMany.data = res.data.data ?? [];
|
||||
programKemiskinanState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
programKemiskinanState.findMany.data = [];
|
||||
programKemiskinanState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch program kemiskinan paginated:", err);
|
||||
programKemiskinanState.findMany.data = [];
|
||||
programKemiskinanState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
programKemiskinanState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -125,7 +148,7 @@ const programKemiskinanState = proxy({
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
deskripsi: data.deskripsi,
|
||||
ikonUrl: data.ikonUrl || "",
|
||||
icon: data.icon,
|
||||
statistik: {
|
||||
tahun: data.statistik.tahun,
|
||||
jumlah: data.statistik.jumlah,
|
||||
@@ -166,7 +189,7 @@ const programKemiskinanState = proxy({
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
deskripsi: this.form.deskripsi,
|
||||
ikonUrl: this.form.ikonUrl,
|
||||
icon: this.form.icon,
|
||||
statistik: {
|
||||
tahun: this.form.statistik.tahun,
|
||||
jumlah: this.form.statistik.jumlah,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -45,7 +46,7 @@ const grafikSektorUnggulan = proxy({
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
grafikSektorUnggulan.create.form = {
|
||||
name: "",
|
||||
description: "",
|
||||
@@ -76,13 +77,37 @@ const grafikSektorUnggulan = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikSektorUnggulan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikSektorUnggulan.findMany.page = page;
|
||||
grafikSektorUnggulan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikSektorUnggulan.findMany.data = res.data.data ?? [];
|
||||
grafikSektorUnggulan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikSektorUnggulan.findMany.data = [];
|
||||
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch sektor unggulan desa paginated:", err);
|
||||
grafikSektorUnggulan.findMany.data = [];
|
||||
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikSektorUnggulan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,9 +1,173 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
import { toast } from "react-toastify";
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateForm = z.object({
|
||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
name: "",
|
||||
imageId: "",
|
||||
};
|
||||
|
||||
type StrukturBumDesForm = Prisma.StrukturBumDesGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
imageId: true;
|
||||
image?: {
|
||||
select: {
|
||||
link: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
const stateStruktur = proxy({
|
||||
struktur: {
|
||||
data: null as StrukturBumDesForm | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
this.data = result.data;
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengambil data struktur");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Load struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat mengambil data struktur");
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.data = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
|
||||
editStruktur: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
isReadOnly: false,
|
||||
|
||||
initialize(strukturData: StrukturBumDesForm) {
|
||||
this.id = strukturData.id;
|
||||
this.isReadOnly = false;
|
||||
this.form = {
|
||||
name: strukturData.name || "",
|
||||
imageId: strukturData.imageId || "",
|
||||
};
|
||||
},
|
||||
|
||||
updateField(field: keyof typeof defaultForm, value: string) {
|
||||
this.form[field] = value;
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const validation = templateForm.safeParse(this.form);
|
||||
|
||||
if (!validation.success) {
|
||||
const errors = validation.error.issues
|
||||
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||
.join(", ");
|
||||
toast.error(`Form tidak valid: ${errors}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update struktur");
|
||||
await stateStruktur.struktur.load(this.id);
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update struktur");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Update struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat update struktur");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.id = "";
|
||||
this.form = { ...defaultForm };
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.isReadOnly = false;
|
||||
},
|
||||
},
|
||||
|
||||
async loadForEdit(id: string) {
|
||||
const strukturData = await this.struktur.load(id);
|
||||
if (strukturData) {
|
||||
this.editStruktur.initialize(strukturData);
|
||||
}
|
||||
return strukturData;
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.struktur.reset();
|
||||
this.editStruktur.reset();
|
||||
},
|
||||
});
|
||||
|
||||
const templatePosisiOrganisasi = z.object({
|
||||
nama: z.string().min(1, "Nama harus diisi"),
|
||||
@@ -30,9 +194,7 @@ const posisiOrganisasi = proxy({
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"posisi-organisasi"
|
||||
]["create"].post(this.form);
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["create"].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan posisi organisasi");
|
||||
posisiOrganisasi.findMany.load();
|
||||
@@ -52,6 +214,29 @@ const posisiOrganisasi = proxy({
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
data: null as Prisma.StrukturOrganisasiBumDesGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
posisiOrganisasi.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch posisiOrganisasi:", res.statusText);
|
||||
posisiOrganisasi.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching posisiOrganisasi:", error);
|
||||
posisiOrganisasi.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...posisiOrganisasiDefaultForm },
|
||||
@@ -161,22 +346,73 @@ const posisiOrganisasi = proxy({
|
||||
deskripsi: string | null;
|
||||
hierarki: number;
|
||||
}>,
|
||||
async load() {
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit?: number, search = "") => {
|
||||
const appliedLimit = limit ?? 10;
|
||||
posisiOrganisasi.findMany.page = page;
|
||||
posisiOrganisasi.findMany.search = search;
|
||||
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"posisi-organisasi"
|
||||
]["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
// The API now returns the id field, so we can use it directly
|
||||
this.data = res.data?.data ?? [];
|
||||
const query: any = { page, limit: appliedLimit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['find-many'].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
posisiOrganisasi.findMany.data = res.data.data ?? [];
|
||||
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
posisiOrganisasi.findMany.data = [];
|
||||
posisiOrganisasi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
this.data = [];
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch posisi organisasi paginated:", err);
|
||||
posisiOrganisasi.findMany.data = [];
|
||||
posisiOrganisasi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
posisiOrganisasi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findManyAll: {
|
||||
data: [] as Array<{
|
||||
id: string;
|
||||
nama: string;
|
||||
deskripsi: string | null;
|
||||
hierarki: number;
|
||||
}>,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (search = "") => {
|
||||
// Change to arrow function
|
||||
posisiOrganisasi.findManyAll.loading = true; // Use the full path to access the property
|
||||
posisiOrganisasi.findManyAll.search = search;
|
||||
try {
|
||||
const query: any = { search };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['find-many-all'].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
posisiOrganisasi.findManyAll.data = res.data.data || [];
|
||||
|
||||
} else {
|
||||
console.error("Failed to load posisiOrganisasi:", res.data?.message);
|
||||
posisiOrganisasi.findManyAll.data = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading posisiOrganisasi:", error);
|
||||
posisiOrganisasi.findManyAll.data = [];
|
||||
} finally {
|
||||
posisiOrganisasi.findManyAll.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
@@ -215,12 +451,12 @@ const posisiOrganisasi = proxy({
|
||||
|
||||
const templatePegawai = z.object({
|
||||
namaLengkap: z.string().min(1, "Nama wajib diisi"),
|
||||
gelarAkademik: z.string().optional(),
|
||||
imageId: z.string().nullable().optional(),
|
||||
tanggalMasuk: z.string().optional(), // ISO format
|
||||
gelarAkademik: z.string().min(1, "Gelar Akademik wajib diisi"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
tanggalMasuk: z.string().min(1, "Tanggal masuk wajib diisi"), // ISO format
|
||||
email: z.string().email("Email tidak valid").optional(),
|
||||
telepon: z.string().optional(),
|
||||
alamat: z.string().optional(),
|
||||
telepon: z.string().min(1, "Telepom wajib diisi"),
|
||||
alamat: z.string().min(1, "Alamat wajib diisi"),
|
||||
posisiId: z.string().min(1, "Posisi wajib diisi"),
|
||||
isActive: z.boolean().default(true),
|
||||
});
|
||||
@@ -251,9 +487,9 @@ const pegawai = proxy({
|
||||
|
||||
try {
|
||||
pegawai.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"pegawai"
|
||||
]["create"].post(pegawai.create.form);
|
||||
const res = await ApiFetch.api.ekonomi['struktur-organisasi'].pegawai['create'].post(
|
||||
pegawai.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
toast.success("Pegawai berhasil ditambahkan");
|
||||
await pegawai.findMany.load();
|
||||
@@ -270,45 +506,56 @@ const pegawai = proxy({
|
||||
},
|
||||
|
||||
// In struktur-organisasi.ts
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
pegawai.findMany.loading = true; // Use the full path to access the property
|
||||
pegawai.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"pegawai"
|
||||
]["find-many"].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.PegawaiBumDesGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
posisi: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
// Change to arrow function
|
||||
pegawai.findMany.loading = true; // Use the full path to access the property
|
||||
pegawai.findMany.page = page;
|
||||
pegawai.findMany.search = search;
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pegawai.findMany.data = res.data.data || [];
|
||||
pegawai.findMany.total = res.data.total || 0;
|
||||
pegawai.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load pegawai:", res.data?.message);
|
||||
const res = await ApiFetch.api.ekonomi['struktur-organisasi'].pegawai['find-many'].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pegawai.findMany.data = res.data.data || [];
|
||||
pegawai.findMany.total = res.data.total || 0;
|
||||
pegawai.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load pegawai:", res.data?.message);
|
||||
pegawai.findMany.data = [];
|
||||
pegawai.findMany.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pegawai:", error);
|
||||
pegawai.findMany.data = [];
|
||||
pegawai.findMany.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pegawai.findMany.loading = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pegawai:", error);
|
||||
pegawai.findMany.data = [];
|
||||
pegawai.findMany.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pegawai.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as
|
||||
| (Prisma.PegawaiGetPayload<{
|
||||
| (Prisma.PegawaiBumDesGetPayload<{
|
||||
include: { posisi: true; image: true };
|
||||
}> & { isActive: boolean })
|
||||
| null,
|
||||
@@ -334,12 +581,9 @@ findMany: {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
pegawai.delete.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(json.message ?? "Berhasil hapus pegawai");
|
||||
@@ -356,6 +600,31 @@ findMany: {
|
||||
},
|
||||
},
|
||||
|
||||
nonActive: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
pegawai.nonActive.loading = true;
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/non-active/${id}`, {
|
||||
method: "DELETE", // biasanya nonActive pakai PATCH
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(json.message ?? "Pegawai berhasil dinonaktifkan");
|
||||
await pegawai.findMany.load(); // refresh data
|
||||
} else {
|
||||
toast.error(json.message ?? "Gagal menonaktifkan pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal nonActive:", error);
|
||||
toast.error("Terjadi kesalahan saat menonaktifkan pegawai");
|
||||
} finally {
|
||||
pegawai.nonActive.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...pegawaiDefaultForm },
|
||||
@@ -368,15 +637,12 @@ findMany: {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/pegawai/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
@@ -487,299 +753,10 @@ findMany: {
|
||||
},
|
||||
});
|
||||
|
||||
// Schema Zod untuk form validasi
|
||||
const templateHubunganOrganisasiForm = z.object({
|
||||
atasanId: z.string().min(1, "Atasan wajib dipilih"),
|
||||
bawahanId: z.string().min(1, "Bawahan wajib dipilih"),
|
||||
tipe: z.string().optional(),
|
||||
});
|
||||
|
||||
// Default form state
|
||||
const defaultHubunganOrganisasiForm = {
|
||||
atasanId: "",
|
||||
bawahanId: "",
|
||||
tipe: "",
|
||||
};
|
||||
|
||||
// ====================== STATE ===========================
|
||||
const hubunganOrganisasi = proxy({
|
||||
create: {
|
||||
form: { ...defaultHubunganOrganisasiForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateHubunganOrganisasiForm.safeParse(
|
||||
hubunganOrganisasi.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}: ${v.message}`)
|
||||
.join("\n")}]`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
hubunganOrganisasi.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"hubungan-organisasi"
|
||||
]["create"].post(hubunganOrganisasi.create.form);
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
hubunganOrganisasi.findMany.load();
|
||||
return toast.success("Berhasil menambahkan hubungan organisasi");
|
||||
} else {
|
||||
return toast.error(res.data?.message || "Gagal menambahkan data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan");
|
||||
} finally {
|
||||
hubunganOrganisasi.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<{
|
||||
id: string;
|
||||
atasanId: string;
|
||||
bawahanId: string;
|
||||
tipe?: string | null;
|
||||
atasan: {
|
||||
id: string;
|
||||
namaLengkap: string;
|
||||
gelarAkademik: string | null;
|
||||
imageId: string | null;
|
||||
tanggalMasuk: Date | null;
|
||||
email: string | null;
|
||||
telepon: string | null;
|
||||
alamat: string | null;
|
||||
posisiId: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
bawahan: {
|
||||
id: string;
|
||||
namaLengkap: string;
|
||||
gelarAkademik: string | null;
|
||||
imageId: string | null;
|
||||
tanggalMasuk: Date | null;
|
||||
email: string | null;
|
||||
telepon: string | null;
|
||||
alamat: string | null;
|
||||
posisiId: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
}> | null,
|
||||
|
||||
async load() {
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"hubungan-organisasi"
|
||||
]["find-many"].get();
|
||||
|
||||
if (res.status === 200) {
|
||||
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map(
|
||||
(item: any) => ({
|
||||
...item,
|
||||
atasan: item.atasan
|
||||
? {
|
||||
...item.atasan,
|
||||
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true,
|
||||
}
|
||||
: null,
|
||||
bawahan: item.bawahan
|
||||
? {
|
||||
...item.bawahan,
|
||||
isActive:
|
||||
item.bawahan.isActive ?? item.bawahan.aktif ?? true,
|
||||
}
|
||||
: null,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
hubunganOrganisasi.findMany.data = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fetch list error:", error);
|
||||
toast.error("Gagal memuat data hubungan organisasi");
|
||||
hubunganOrganisasi.findMany.data = [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
data: null as {
|
||||
id: string;
|
||||
atasanId: string;
|
||||
bawahanId: string;
|
||||
tipe?: string | null;
|
||||
atasan?: {
|
||||
id: string;
|
||||
namaLengkap: string;
|
||||
gelarAkademik: string | null;
|
||||
imageId: string;
|
||||
tanggalMasuk: Date | null;
|
||||
email: string | null;
|
||||
telepon: string | null;
|
||||
alamat: string | null;
|
||||
posisiId: string;
|
||||
aktif: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
bawahan?: {
|
||||
id: string;
|
||||
namaLengkap: string;
|
||||
gelarAkademik: string | null;
|
||||
imageId: string;
|
||||
tanggalMasuk: Date | null;
|
||||
email: string | null;
|
||||
telepon: string | null;
|
||||
alamat: string | null;
|
||||
posisiId: string;
|
||||
aktif: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
} | null,
|
||||
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
|
||||
);
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result?.success) {
|
||||
hubunganOrganisasi.findUnique.data = result.data;
|
||||
} else {
|
||||
hubunganOrganisasi.findUnique.data = null;
|
||||
toast.error(result?.message || "Gagal mengambil data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find unique error:", error);
|
||||
hubunganOrganisasi.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...defaultHubunganOrganisasiForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
|
||||
);
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
atasanId: data.atasanId,
|
||||
bawahanId: data.bawahanId,
|
||||
tipe: data.tipe || "",
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateHubunganOrganisasiForm.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}: ${v.message}`)
|
||||
.join("\n")}]`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
if (res.ok && result.success) {
|
||||
await hubunganOrganisasi.findMany.load();
|
||||
toast.success("Berhasil mengupdate hubungan organisasi");
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal mengupdate");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal update");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
hubunganOrganisasi.edit.id = "";
|
||||
hubunganOrganisasi.edit.form = { ...defaultHubunganOrganisasiForm };
|
||||
},
|
||||
},
|
||||
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
hubunganOrganisasi.delete.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
if (res.ok && result?.success) {
|
||||
toast.success("Hubungan organisasi berhasil dihapus");
|
||||
hubunganOrganisasi.findMany.load();
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus hubungan organisasi");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Delete error:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus");
|
||||
} finally {
|
||||
hubunganOrganisasi.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const strukturorganisasiState = proxy({
|
||||
const stateStrukturBumDes = proxy({
|
||||
stateStruktur,
|
||||
posisiOrganisasi,
|
||||
pegawai,
|
||||
hubunganOrganisasi,
|
||||
});
|
||||
|
||||
export default strukturorganisasiState;
|
||||
export default stateStrukturBumDes;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -50,7 +51,7 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
grafikBerdasarkanUsiaKerjaNganggur.create.form = {
|
||||
usia18_25: "",
|
||||
usia26_35: "",
|
||||
@@ -75,16 +76,37 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.page = page;
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data.data ?? [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch grafik berdasarkan usia kerja yang menganggur paginated:", err);
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
|
||||
omit: { isActive: true };
|
||||
@@ -233,7 +255,7 @@ const grafikBerdasarkanPendidikan = proxy({
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
toast.success("Sukses menambahkan");
|
||||
grafikBerdasarkanPendidikan.create.form = {
|
||||
SD: "",
|
||||
SMP: "",
|
||||
@@ -259,15 +281,36 @@ const grafikBerdasarkanPendidikan = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikBerdasarkanPendidikan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikBerdasarkanPendidikan.findMany.page = page;
|
||||
grafikBerdasarkanPendidikan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikBerdasarkanPendidikan.findMany.data = res.data.data ?? [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch grafik berdasarkan pendidikan paginated:", err);
|
||||
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikBerdasarkanPendidikan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
|
||||
|
||||