Compare commits
10 Commits
nico/9-sep
...
nico/22-se
| Author | SHA1 | Date | |
|---|---|---|---|
| b5c044df6e | |||
| 0fc47c28ff | |||
| 8e25c91e85 | |||
| 068d8b1077 | |||
| 9f72e94557 | |||
| 79ad39fc55 | |||
| 39e1e7b575 | |||
| 4ceea5203f | |||
| a5d841bb6b | |||
| 6a7bd386ae |
@@ -80,9 +80,11 @@
|
||||
"react-transition-group": "^4.4.5",
|
||||
"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,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
|
||||
}
|
||||
]
|
||||
|
||||
137
prisma/data/file-storage.json
Normal file
@@ -0,0 +1,137 @@
|
||||
[
|
||||
{
|
||||
"id": "cmff0rr4z0002vn0twp333m2",
|
||||
"name": "S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
||||
"realName": "bares.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff0tnf00003vn0t3kgzi0u0",
|
||||
"name": "_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
||||
"realName": "bicara-darma.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff0uykf0004vn0trmmxpgfh",
|
||||
"name": "bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
||||
"realName": "daves.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff0z34f0005vn0tjtvq519p",
|
||||
"name": "Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
||||
"realName": "mangan.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff38cyq000bvn0t9f01cz3f",
|
||||
"name": "LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
||||
"realName": "gelah-melah.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff0zqvd0007vn0tv6o5hjcq",
|
||||
"name": "gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
||||
"realName": "inovasi-desa-darmasaba.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff1013m0008vn0th7t0d64d",
|
||||
"name": "JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
||||
"realName": "pdkt.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff10cwq0009vn0tse8dzu3j",
|
||||
"name": "bxAk4AsGbJTC705_IVdes-desktop.webp",
|
||||
"realName": "sajjiana-dharma-raksaka.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/bxAk4AsGbJTC705_IVdes-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff2w5ly000avn0telhct71k",
|
||||
"name": "Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
||||
"realName": "perbekel.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3joae0000vn6h8sgs0ilg",
|
||||
"name": "7hox9spUxj56hY_EBYLnj-desktop.webp",
|
||||
"realName": "youtube.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/7hox9spUxj56hY_EBYLnj-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3ll130001vn6hkhls3f5y",
|
||||
"name": "ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
||||
"realName": "gmail.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3mtat0002vn6hs8vyyhdd",
|
||||
"name": "z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
||||
"realName": "facebook.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3nv180003vn6h5jvedidq",
|
||||
"name": "BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
||||
"realName": "telephone-call.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3oouh0004vn6hd94brzv9",
|
||||
"name": "hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
||||
"realName": "instagram.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
||||
"category": "image"
|
||||
},
|
||||
{
|
||||
"id": "cmff3q12g0005vn6h5ojov2qa",
|
||||
"name": "6XEoZ9SFu59COpil03Gya-desktop.webp",
|
||||
"realName": "tiktok.png",
|
||||
"path": "uploads/images",
|
||||
"mimeType": "image/webp",
|
||||
"link": "/api/fileStorage/findUnique/6XEoZ9SFu59COpil03Gya-desktop.webp",
|
||||
"category": "image"
|
||||
}
|
||||
]
|
||||
@@ -2,31 +2,37 @@
|
||||
{
|
||||
"id": "cmds8w2q60002vnbe6i8qhkuo",
|
||||
"name": "Telephone Desa Darmasaba",
|
||||
"iconUrl": "081239580000"
|
||||
"iconUrl": "081239580000",
|
||||
"imageId": "cmff3nv180003vn6h5jvedidq"
|
||||
},
|
||||
{
|
||||
"id": "cmds8z7u20005vnbegyyvnbk0",
|
||||
"name": "Email Desa Darmasaba",
|
||||
"iconUrl": "desadarmasaba@badungkab.go.id"
|
||||
"iconUrl": "desadarmasaba@badungkab.go.id",
|
||||
"imageId": "cmff3ll130001vn6hkhls3f5y"
|
||||
},
|
||||
{
|
||||
"id": "cmds9023u0008vnbe3oxmhwyf",
|
||||
"name": "Desa Darmasaba",
|
||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg"
|
||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg",
|
||||
"imageId": "cmff3joae0000vn6h8sgs0ilg"
|
||||
},
|
||||
{
|
||||
"id": "cmds90oul000bvnbe2bqkptoi",
|
||||
"name": "Pemerintah Desa Darmasaba",
|
||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku"
|
||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku",
|
||||
"imageId": "cmff3mtat0002vn6hs8vyyhdd"
|
||||
},
|
||||
{
|
||||
"id": "cmds91i4e000evnbe8gtf1gub",
|
||||
"name": "ddarmasaba",
|
||||
"iconUrl": "https://www.instagram.com/ddarmasaba/"
|
||||
"iconUrl": "https://www.instagram.com/ddarmasaba/",
|
||||
"imageId": "cmff3oouh0004vn6hd94brzv9"
|
||||
},
|
||||
{
|
||||
"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": "cmff3q12g0005vn6h5ojov2qa"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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": "cmff2w5ly000avn0telhct71k"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
[
|
||||
{
|
||||
"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" : "cmff0z34f0005vn0tjtvq519p"
|
||||
},
|
||||
{
|
||||
"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" : "cmff0tnf00003vn0t3kgzi0u0"
|
||||
},
|
||||
{
|
||||
"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" : "cmff0rr4z0002vn0twp333m2"
|
||||
},
|
||||
{
|
||||
"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" : "cmff10cwq0009vn0tse8dzu3j"
|
||||
},
|
||||
{
|
||||
"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" : "cmff1013m0008vn0th7t0d64d"
|
||||
},
|
||||
{
|
||||
"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" : "cmff38cyq000bvn0t9f01cz3f"
|
||||
},
|
||||
{
|
||||
"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" : "cmff0zqvd0007vn0tv6o5hjcq"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -81,8 +81,6 @@ model FileStorage {
|
||||
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
|
||||
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
|
||||
PasarDesa PasarDesa[]
|
||||
KontakDaruratKeamanan KontakDaruratKeamanan[]
|
||||
KontakItem KontakItem[]
|
||||
Pegawai Pegawai[]
|
||||
DesaDigital DesaDigital[]
|
||||
InfoTekno InfoTekno[]
|
||||
@@ -92,7 +90,7 @@ model FileStorage {
|
||||
PejabatDesa PejabatDesa[]
|
||||
MediaSosial MediaSosial[]
|
||||
DesaAntiKorupsi DesaAntiKorupsi[]
|
||||
SDGSDesa SDGSDesa[]
|
||||
SDGSDesa SdgsDesa[]
|
||||
APBDesImage APBDes[] @relation("APBDesImage")
|
||||
APBDesFile APBDes[] @relation("APBDesFile")
|
||||
PrestasiDesa PrestasiDesa[]
|
||||
@@ -101,6 +99,8 @@ model FileStorage {
|
||||
PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[]
|
||||
|
||||
MitraKolaborasi MitraKolaborasi[]
|
||||
|
||||
ArtikelKesehatan ArtikelKesehatan[]
|
||||
}
|
||||
|
||||
//========================================= MENU LANDING PAGE ========================================= //
|
||||
@@ -168,9 +168,9 @@ model KategoriDesaAntiKorupsi {
|
||||
}
|
||||
|
||||
//========================================= SDGS Desa ========================================= //
|
||||
model SDGSDesa {
|
||||
model SdgsDesa {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
name String
|
||||
jumlah String
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
@@ -183,7 +183,7 @@ model SDGSDesa {
|
||||
//========================================= APBDes ========================================= //
|
||||
model APBDes {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
name String
|
||||
jumlah String
|
||||
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
@@ -198,7 +198,7 @@ model APBDes {
|
||||
//========================================= PRESTASI DESA ========================================= //
|
||||
model PrestasiDesa {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
name String
|
||||
deskripsi String @db.Text
|
||||
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId String
|
||||
@@ -972,8 +972,10 @@ model ArtikelKesehatan {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
content String
|
||||
introduction Introduction @relation(fields: [introductionId], references: [id])
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
introductionId String
|
||||
introduction Introduction @relation(fields: [introductionId], references: [id])
|
||||
symptom Symptom @relation(fields: [symptomId], references: [id])
|
||||
symptomId String
|
||||
prevention Prevention @relation(fields: [preventionId], references: [id])
|
||||
@@ -1216,71 +1218,51 @@ model LayananPolsek {
|
||||
|
||||
// ========================================= KONTAK DARURAT ========================================= //
|
||||
model KontakDaruratKeamanan {
|
||||
id String @id @default(uuid())
|
||||
nama String // contoh: "Layanan Darurat", "Fasilitas Kesehatan"
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
kontakItems KontakItem[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
nama String
|
||||
icon String
|
||||
kategori KontakItem @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId String
|
||||
kontakItems KontakDaruratToItem[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model KontakItem {
|
||||
id String @id @default(uuid())
|
||||
nama String // contoh: "Polisi", "Ambulans", "Puskesmas Darmasaba"
|
||||
nomorTelepon String
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
kategori KontakDaruratKeamanan @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
nama String
|
||||
nomorTelepon String
|
||||
icon String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
KontakDaruratToItem KontakDaruratToItem[]
|
||||
KontakDaruratKeamanan KontakDaruratKeamanan[]
|
||||
}
|
||||
|
||||
model KontakDaruratToItem {
|
||||
id String @id @default(uuid())
|
||||
kontakDaruratId String
|
||||
kontakItemId String
|
||||
kontakDarurat KontakDaruratKeamanan @relation(fields: [kontakDaruratId], references: [id])
|
||||
kontakItem KontakItem @relation(fields: [kontakItemId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
||||
model PencegahanKriminalitas {
|
||||
id String @id @default(cuid())
|
||||
programKeamanan ProgramKeamanan @relation(fields: [programKeamananId], references: [id])
|
||||
programKeamananId String
|
||||
tipsKeamanan TipsKeamanan @relation(fields: [tipsKeamananId], references: [id])
|
||||
tipsKeamananId String
|
||||
videoKeamanan VideoKeamanan @relation(fields: [videoKeamananId], references: [id])
|
||||
videoKeamananId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model ProgramKeamanan {
|
||||
id String @id @default(cuid())
|
||||
nama String // contoh: "Ronda Malam"
|
||||
deskripsi String? // jika mau tambahkan info detail
|
||||
slug String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
PencegahanKriminalitas PencegahanKriminalitas[]
|
||||
}
|
||||
|
||||
model TipsKeamanan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
konten String
|
||||
slug String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
PencegahanKriminalitas PencegahanKriminalitas[]
|
||||
}
|
||||
|
||||
model VideoKeamanan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String?
|
||||
videoUrl String // link youtube atau embed url
|
||||
slug String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
PencegahanKriminalitas PencegahanKriminalitas[]
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String
|
||||
deskripsiSingkat String
|
||||
linkVideo String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= LAPORAN PUBLIK ========================================= //
|
||||
@@ -1289,11 +1271,13 @@ model LaporanPublik {
|
||||
judul String
|
||||
lokasi String
|
||||
tanggalWaktu DateTime
|
||||
status StatusLaporan
|
||||
status StatusLaporan @default(Proses)
|
||||
penanganan PenangananLaporanPublik[]
|
||||
kronologi String? // Optional, bisa diisi detail kronologi
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model PenangananLaporanPublik {
|
||||
@@ -1401,6 +1385,9 @@ model PosisiOrganisasi {
|
||||
pegawai Pegawai[]
|
||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("posisi_organisasi")
|
||||
}
|
||||
@@ -1469,7 +1456,7 @@ model ProgramKemiskinan {
|
||||
id String @id @default(uuid())
|
||||
nama String
|
||||
deskripsi String
|
||||
ikonUrl String?
|
||||
icon String
|
||||
isActive Boolean @default(true)
|
||||
statistikId String? @unique
|
||||
statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id])
|
||||
@@ -1551,7 +1538,7 @@ model DataDemografiPekerjaan {
|
||||
model DetailDataPengangguran {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
month String @db.VarChar(20)
|
||||
year DateTime
|
||||
year Int
|
||||
totalUnemployment Int
|
||||
educatedUnemployment Int
|
||||
uneducatedUnemployment Int
|
||||
@@ -2106,18 +2093,18 @@ model KategoriBuku {
|
||||
// ========================================= USER ========================================= //
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
username String
|
||||
nomor String @unique
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
roleId String @default("1")
|
||||
instansi String?
|
||||
id String @id @default(cuid())
|
||||
username String
|
||||
nomor String @unique
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
roleId String @default("1")
|
||||
instansi String?
|
||||
UserSession UserSession? // Nama instansi (Puskesmas, Sekolah, dll)
|
||||
isActive Boolean @default(true)
|
||||
lastLogin DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
lastLogin DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
}
|
||||
|
||||
model Role {
|
||||
|
||||
216
prisma/seed.ts
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import prisma from "@/lib/prisma";
|
||||
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
||||
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
||||
@@ -15,7 +16,7 @@ import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PP
|
||||
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 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";
|
||||
@@ -54,63 +55,90 @@ import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-u
|
||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||
import roles from "./data/user/roles.json";
|
||||
import users from "./data/user/users.json";
|
||||
import fileStorage from "./data/file-storage.json";
|
||||
|
||||
(async () => {
|
||||
// =========== USER & ROLE ===========
|
||||
// In your seed.ts
|
||||
// =========== ROLES ===========
|
||||
console.log("🔄 Seeding roles...");
|
||||
for (const r of roles) {
|
||||
await prisma.role.upsert({
|
||||
where: { id: r.id },
|
||||
update: {
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
permissions: r.permissions,
|
||||
isActive: r.isActive,
|
||||
},
|
||||
create: {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
permissions: r.permissions,
|
||||
isActive: r.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ Roles seeded");
|
||||
|
||||
// =========== USERS ===========
|
||||
console.log("🔄 Seeding users...");
|
||||
for (const u of users) {
|
||||
// First verify the role exists
|
||||
const roleExists = await prisma.role.findUnique({
|
||||
where: { id: u.roleId }
|
||||
});
|
||||
|
||||
if (!roleExists) {
|
||||
console.error(`❌ Role with id ${u.roleId} not found for user ${u.nama}`);
|
||||
continue;
|
||||
// =========== ROLES ===========
|
||||
console.log("🔄 Seeding roles...");
|
||||
for (const r of roles) {
|
||||
await prisma.role.upsert({
|
||||
where: { id: r.id },
|
||||
update: {
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
permissions: r.permissions,
|
||||
isActive: r.isActive,
|
||||
},
|
||||
create: {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
permissions: r.permissions,
|
||||
isActive: r.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ Roles seeded");
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: { id: u.id },
|
||||
update: {
|
||||
username: u.nama,
|
||||
nomor: u.nomor,
|
||||
roleId: u.roleId,
|
||||
isActive: u.isActive,
|
||||
},
|
||||
create: {
|
||||
id: u.id,
|
||||
username: u.nama,
|
||||
nomor: u.nomor,
|
||||
roleId: u.roleId,
|
||||
isActive: u.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ Users seeded");
|
||||
// =========== USERS ===========
|
||||
console.log("🔄 Seeding users...");
|
||||
for (const u of users) {
|
||||
// First verify the role exists
|
||||
const roleExists = await prisma.role.findUnique({
|
||||
where: { id: u.roleId },
|
||||
});
|
||||
|
||||
if (!roleExists) {
|
||||
console.error(`❌ Role with id ${u.roleId} not found for user ${u.nama}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: { id: u.id },
|
||||
update: {
|
||||
username: u.nama,
|
||||
nomor: u.nomor,
|
||||
roleId: u.roleId,
|
||||
isActive: u.isActive,
|
||||
},
|
||||
create: {
|
||||
id: u.id,
|
||||
username: u.nama,
|
||||
nomor: u.nomor,
|
||||
roleId: u.roleId,
|
||||
isActive: u.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ Users seeded");
|
||||
|
||||
// =========== FILE STORAGE ===========
|
||||
console.log("🔄 Seeding file storage...");
|
||||
for (const f of fileStorage) {
|
||||
await prisma.fileStorage.upsert({
|
||||
where: { id: f.id },
|
||||
update: {
|
||||
name: f.name,
|
||||
realName: f.realName,
|
||||
path: f.path,
|
||||
mimeType: f.mimeType,
|
||||
link: f.link,
|
||||
category: f.category,
|
||||
},
|
||||
create: {
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
realName: f.realName,
|
||||
path: f.path,
|
||||
mimeType: f.mimeType,
|
||||
link: f.link,
|
||||
category: f.category,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ File storage seeded");
|
||||
// =========== LANDING PAGE ===========
|
||||
// =========== SUBMENU PROFILE ===========
|
||||
// =========== PROFILE PEJABAT DESA ===========
|
||||
@@ -120,11 +148,13 @@ console.log("✅ Users seeded");
|
||||
update: {
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -134,18 +164,35 @@ console.log("✅ Users seeded");
|
||||
|
||||
// =========== PROGRAM INOVASI ===========
|
||||
for (const p of programInovasi) {
|
||||
let imageId: string | null = null;
|
||||
|
||||
if (p.imageId) {
|
||||
const imageExists = await prisma.fileStorage.findUnique({
|
||||
where: { id: p.imageId },
|
||||
});
|
||||
|
||||
if (imageExists) {
|
||||
imageId = p.imageId;
|
||||
} else {
|
||||
console.warn(
|
||||
`⚠️ imageId ${p.imageId} tidak ditemukan untuk ProgramInovasi ${p.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
await prisma.programInovasi.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -158,11 +205,13 @@ console.log("✅ Users seeded");
|
||||
update: {
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
imageId: p.imageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -308,11 +357,8 @@ console.log("✅ Users seeded");
|
||||
|
||||
// =========== 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,
|
||||
@@ -330,8 +376,7 @@ console.log("✅ Users seeded");
|
||||
for (const l of apbdes) {
|
||||
await prisma.aPBDes.upsert({
|
||||
where: {
|
||||
name: l.name,
|
||||
jumlah: l.jumlah,
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
name: l.name,
|
||||
@@ -558,29 +603,29 @@ console.log("✅ Users seeded");
|
||||
}
|
||||
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 ...");
|
||||
// =========== 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({
|
||||
@@ -849,12 +894,9 @@ console.log("✅ Users seeded");
|
||||
console.log("hubungan organisasi success ...");
|
||||
|
||||
for (const d of detailDataPengangguran) {
|
||||
// Convert the year to a Date object (using January 1st of the year as the date)
|
||||
const yearAsDate = new Date(d.year, 0, 1);
|
||||
|
||||
await prisma.detailDataPengangguran.upsert({
|
||||
where: {
|
||||
month_year: { month: d.month, year: yearAsDate },
|
||||
month_year: { month: d.month, year: d.year },
|
||||
},
|
||||
update: {
|
||||
totalUnemployment: d.totalUnemployment,
|
||||
@@ -864,7 +906,7 @@ console.log("✅ Users seeded");
|
||||
},
|
||||
create: {
|
||||
month: d.month,
|
||||
year: yearAsDate,
|
||||
year: d.year,
|
||||
totalUnemployment: d.totalUnemployment,
|
||||
educatedUnemployment: d.educatedUnemployment,
|
||||
uneducatedUnemployment: d.uneducatedUnemployment,
|
||||
|
||||
|
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/chatbot-removebg-preview.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 266 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}
|
||||
|
||||
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} />
|
||||
}
|
||||
@@ -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 },
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -2,22 +2,30 @@
|
||||
|
||||
import { Box, rem, Select } 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 = {
|
||||
@@ -36,7 +44,17 @@ const iconMap = {
|
||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||
rumah: {label: 'Rumah', icon: IconHome},
|
||||
pohon: {label: 'Pohon', icon: IconTree},
|
||||
air: {label: 'Air', icon: IconDroplet}
|
||||
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;
|
||||
|
||||
@@ -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,32 +58,46 @@ 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 (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil APB Desa");
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch APB Desa paginated:", err);
|
||||
ApbDesa.findMany.data = [];
|
||||
ApbDesa.findMany.totalPages = 1;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
ApbDesa.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -106,13 +124,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 +138,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 +207,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 +217,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 +282,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 +324,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 +368,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 +517,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 +561,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 +605,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 +752,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 +796,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 +840,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";
|
||||
@@ -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";
|
||||
@@ -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,7 +87,7 @@ 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");
|
||||
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
|
||||
@@ -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<{
|
||||
|
||||
@@ -8,7 +8,7 @@ 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"),
|
||||
@@ -18,7 +18,7 @@ const templateForm = z.object({
|
||||
const defaultForm = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
ikonUrl: "",
|
||||
icon: "",
|
||||
statistik: {
|
||||
tahun: "",
|
||||
jumlah: "",
|
||||
@@ -148,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,
|
||||
@@ -189,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";
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -161,18 +161,34 @@ const posisiOrganisasi = proxy({
|
||||
deskripsi: string | null;
|
||||
hierarki: number;
|
||||
}>,
|
||||
async load() {
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
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 };
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
@@ -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 };
|
||||
@@ -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<{
|
||||
|
||||
@@ -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";
|
||||
@@ -61,10 +62,37 @@ const ajukanIdeInovatifState = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
ajukanIdeInovatifState.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
ajukanIdeInovatifState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
ajukanIdeInovatifState.findMany.page = page;
|
||||
ajukanIdeInovatifState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.ajukanideinovatif[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
ajukanIdeInovatifState.findMany.data = res.data.data ?? [];
|
||||
ajukanIdeInovatifState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
ajukanIdeInovatifState.findMany.data = [];
|
||||
ajukanIdeInovatifState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch ajukan ide inovatif paginated:", err);
|
||||
ajukanIdeInovatifState.findMany.data = [];
|
||||
ajukanIdeInovatifState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
ajukanIdeInovatifState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -97,16 +125,21 @@ const ajukanIdeInovatifState = proxy({
|
||||
|
||||
try {
|
||||
ajukanIdeInovatifState.delete.loading = true;
|
||||
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/inovasi/ajukanideinovatif/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus");
|
||||
toast.success(
|
||||
result.message || "Ajukan Ide Inovatif berhasil dihapus"
|
||||
);
|
||||
await ajukanIdeInovatifState.findMany.load();
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");
|
||||
|
||||
@@ -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";
|
||||
@@ -54,19 +55,20 @@ const administrasiOnline = proxy({
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<
|
||||
Prisma.AdministrasiOnlineGetPayload<{
|
||||
include: {
|
||||
jenisLayanan: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
Prisma.AdministrasiOnlineGetPayload<{
|
||||
include: {
|
||||
jenisLayanan: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
|
||||
async load(page = 1, limit = 10) {
|
||||
search: "",
|
||||
async load(page = 1, limit = 10, search = "") {
|
||||
administrasiOnline.findMany.loading = true;
|
||||
administrasiOnline.findMany.page = page;
|
||||
administrasiOnline.findMany.search = search;
|
||||
try {
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
|
||||
@@ -75,6 +77,7 @@ const administrasiOnline = proxy({
|
||||
query: {
|
||||
page,
|
||||
limit,
|
||||
search,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -91,10 +94,10 @@ const administrasiOnline = proxy({
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.AdministrasiOnlineGetPayload<{
|
||||
include: {
|
||||
jenisLayanan: true;
|
||||
};
|
||||
}> | null,
|
||||
include: {
|
||||
jenisLayanan: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
@@ -199,13 +202,37 @@ const jenisLayanan = proxy({
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jenisLayanan.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
jenisLayanan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
jenisLayanan.findMany.page = page;
|
||||
jenisLayanan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jenisLayanan.findMany.data = res.data.data ?? [];
|
||||
jenisLayanan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
jenisLayanan.findMany.data = [];
|
||||
jenisLayanan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch jenis layanan paginated:", err);
|
||||
jenisLayanan.findMany.data = [];
|
||||
jenisLayanan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jenisLayanan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -403,7 +430,9 @@ const templatePengaduanMasyarakatForm = z.object({
|
||||
nik: z.string().min(1, "NIK minimal 1 karakter"),
|
||||
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
|
||||
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
|
||||
deskripsiPengaduan: z.string().min(1, "Deskripsi pengaduan minimal 1 karakter"),
|
||||
deskripsiPengaduan: z
|
||||
.string()
|
||||
.min(1, "Deskripsi pengaduan minimal 1 karakter"),
|
||||
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
|
||||
imageId: z.string().min(1, "Image minimal 1 karakter"),
|
||||
});
|
||||
@@ -455,37 +484,42 @@ const pengaduanMasyarakat = proxy({
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<
|
||||
Prisma.PengaduanMasyarakatGetPayload<{
|
||||
include: {
|
||||
jenisPengaduan: true;
|
||||
image: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
Prisma.PengaduanMasyarakatGetPayload<{
|
||||
include: {
|
||||
jenisPengaduan: true;
|
||||
image: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
|
||||
async load(page = 1, limit = 10) {
|
||||
pengaduanMasyarakat.findMany.loading = true;
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
pengaduanMasyarakat.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pengaduanMasyarakat.findMany.page = page;
|
||||
pengaduanMasyarakat.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
|
||||
"find-many"
|
||||
].get({
|
||||
query: {
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pengaduanMasyarakat.findMany.data = res.data.data ?? [];
|
||||
pengaduanMasyarakat.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
pengaduanMasyarakat.findMany.data = [];
|
||||
pengaduanMasyarakat.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch pengaduan masyarakat paginated:", err);
|
||||
pengaduanMasyarakat.findMany.data = [];
|
||||
pengaduanMasyarakat.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pengaduanMasyarakat.findMany.loading = false;
|
||||
}
|
||||
@@ -493,11 +527,11 @@ const pengaduanMasyarakat = proxy({
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PengaduanMasyarakatGetPayload<{
|
||||
include: {
|
||||
jenisPengaduan: true;
|
||||
image: true;
|
||||
};
|
||||
}> | null,
|
||||
include: {
|
||||
jenisPengaduan: true;
|
||||
image: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
@@ -507,7 +541,10 @@ const pengaduanMasyarakat = proxy({
|
||||
const data = await res.json();
|
||||
pengaduanMasyarakat.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch pengaduan masyarakat:", res.statusText);
|
||||
console.error(
|
||||
"Failed to fetch pengaduan masyarakat:",
|
||||
res.statusText
|
||||
);
|
||||
pengaduanMasyarakat.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -542,7 +579,9 @@ const pengaduanMasyarakat = proxy({
|
||||
);
|
||||
await pengaduanMasyarakat.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus pengaduan masyarakat");
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus pengaduan masyarakat"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
@@ -567,7 +606,9 @@ const jenisPengaduan = proxy({
|
||||
form: { ...defaultJenisPengaduanForm },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.create.form);
|
||||
const cek = templateJenisPengaduanForm.safeParse(
|
||||
jenisPengaduan.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
@@ -598,13 +639,37 @@ const jenisPengaduan = proxy({
|
||||
id: string;
|
||||
nama: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jenisPengaduan.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
jenisPengaduan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
jenisPengaduan.findMany.page = page;
|
||||
jenisPengaduan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jenisPengaduan.findMany.data = res.data.data ?? [];
|
||||
jenisPengaduan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
jenisPengaduan.findMany.data = [];
|
||||
jenisPengaduan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch jenis pengaduan paginated:", err);
|
||||
jenisPengaduan.findMany.data = [];
|
||||
jenisPengaduan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jenisPengaduan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -693,7 +758,7 @@ const jenisPengaduan = proxy({
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama
|
||||
nama: data.nama,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -709,7 +774,9 @@ const jenisPengaduan = proxy({
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.edit.form);
|
||||
const cek = templateJenisPengaduanForm.safeParse(
|
||||
jenisPengaduan.edit.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
@@ -759,7 +826,9 @@ const jenisPengaduan = proxy({
|
||||
await jenisPengaduan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate jenis pengaduan");
|
||||
throw new Error(
|
||||
result.message || "Gagal mengupdate jenis pengaduan"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// If JSON parsing fails, try to get the response text for better error messages
|
||||
@@ -792,7 +861,6 @@ const jenisPengaduan = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const layananonlineDesa = proxy({
|
||||
administrasiOnline,
|
||||
jenisLayanan,
|
||||
|
||||
@@ -54,34 +54,32 @@ const programKreatifState = proxy({
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
programKreatifState.findMany.loading = true; // Use the full path to access the property
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
programKreatifState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
programKreatifState.findMany.page = page;
|
||||
programKreatifState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const res = await ApiFetch.api.inovasi.programkreatif["find-many"].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.inovasi.programkreatif[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
programKreatifState.findMany.data = res.data.data || [];
|
||||
programKreatifState.findMany.total = res.data.total || 0;
|
||||
programKreatifState.findMany.totalPages = res.data.totalPages || 1;
|
||||
programKreatifState.findMany.data = res.data.data ?? [];
|
||||
programKreatifState.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to load grafik berdasarkan jenis kelamin:",
|
||||
res.data?.message
|
||||
);
|
||||
programKreatifState.findMany.data = [];
|
||||
programKreatifState.findMany.total = 0;
|
||||
programKreatifState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch program kreatif paginated:", err);
|
||||
programKreatifState.findMany.data = [];
|
||||
programKreatifState.findMany.total = 0;
|
||||
programKreatifState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
programKreatifState.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";
|
||||
@@ -6,26 +7,14 @@ import { z } from "zod";
|
||||
|
||||
const templateForm = z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
imageId: z.string().nonempty(),
|
||||
kontakItems: z.array(
|
||||
z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
|
||||
imageId: z.string().nonempty(),
|
||||
})
|
||||
),
|
||||
icon: z.string().nonempty(),
|
||||
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
nama: "",
|
||||
imageId: "",
|
||||
kontakItems: [
|
||||
{
|
||||
nama: "",
|
||||
nomorTelepon: "",
|
||||
imageId: "",
|
||||
},
|
||||
],
|
||||
icon: "",
|
||||
kategoriId: [] as string[],
|
||||
};
|
||||
|
||||
const kontakDaruratKeamananState = proxy({
|
||||
@@ -61,20 +50,49 @@ const kontakDaruratKeamananState = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.KontakDaruratKeamananGetPayload<{
|
||||
include: {
|
||||
kontakItems: true;
|
||||
image: true;
|
||||
data: null as Array<
|
||||
Prisma.KontakDaruratKeamananGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
kontakItems: {
|
||||
include: {
|
||||
kontakItem: true;
|
||||
};
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
kontakDaruratKeamananState.findMany.data = res.data?.data ?? [];
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kontakDaruratKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kontakDaruratKeamananState.findMany.page = page;
|
||||
kontakDaruratKeamananState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
|
||||
"findMany"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kontakDaruratKeamananState.findMany.data = res.data.data ?? [];
|
||||
kontakDaruratKeamananState.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kontakDaruratKeamananState.findMany.data = [];
|
||||
kontakDaruratKeamananState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kontak darurat paginated:", err);
|
||||
kontakDaruratKeamananState.findMany.data = [];
|
||||
kontakDaruratKeamananState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kontakDaruratKeamananState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -83,10 +101,10 @@ const kontakDaruratKeamananState = proxy({
|
||||
include: {
|
||||
kontakItems: {
|
||||
include: {
|
||||
image: true;
|
||||
kontakItem: true;
|
||||
};
|
||||
};
|
||||
image: true;
|
||||
kategori: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
@@ -168,14 +186,9 @@ const kontakDaruratKeamananState = proxy({
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
imageId: data.imageId,
|
||||
kontakItems: [
|
||||
{
|
||||
nama: data.kontakItems.nama,
|
||||
nomorTelepon: data.kontakItems.nomorTelepon,
|
||||
imageId: data.kontakItems.imageId,
|
||||
},
|
||||
],
|
||||
icon: data.icon || "",
|
||||
kategoriId:
|
||||
data.kontakItems?.map((item: any) => item.kontakItemId) || [],
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -212,14 +225,8 @@ const kontakDaruratKeamananState = proxy({
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
imageId: this.form.imageId,
|
||||
kontakItems: [
|
||||
{
|
||||
nama: this.form.kontakItems[0].nama,
|
||||
nomorTelepon: this.form.kontakItems[0].nomorTelepon,
|
||||
imageId: this.form.kontakItems[0].imageId,
|
||||
},
|
||||
],
|
||||
icon: this.form.icon,
|
||||
kategoriId: this.form.kategoriId,
|
||||
}),
|
||||
}
|
||||
);
|
||||
@@ -256,4 +263,242 @@ const kontakDaruratKeamananState = proxy({
|
||||
},
|
||||
});
|
||||
|
||||
export default kontakDaruratKeamananState;
|
||||
const templateFormItem = z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
|
||||
icon: z.string().nonempty(),
|
||||
});
|
||||
|
||||
const defaultFormItem = {
|
||||
nama: "",
|
||||
nomorTelepon: "",
|
||||
icon: "",
|
||||
};
|
||||
|
||||
const kontakDaruratItem = proxy({
|
||||
create: {
|
||||
form: { ...defaultFormItem },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateFormItem.safeParse(kontakDaruratItem.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
kontakDaruratItem.create.loading = true;
|
||||
const res = await ApiFetch.api.keamanan.kontakitem["create"].post(
|
||||
kontakDaruratItem.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
kontakDaruratItem.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
kontakDaruratItem.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<
|
||||
Prisma.KontakItemGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kontakDaruratItem.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kontakDaruratItem.findMany.page = page;
|
||||
kontakDaruratItem.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.keamanan.kontakitem["find-many"].get({
|
||||
query,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kontakDaruratItem.findMany.data = res.data.data ?? [];
|
||||
kontakDaruratItem.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kontakDaruratItem.findMany.data = [];
|
||||
kontakDaruratItem.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kontak darurat paginated:", err);
|
||||
kontakDaruratItem.findMany.data = [];
|
||||
kontakDaruratItem.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kontakDaruratItem.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KontakItemGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/keamanan/kontakitem/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
kontakDaruratItem.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
kontakDaruratItem.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
kontakDaruratItem.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
kontakDaruratItem.delete.loading = true;
|
||||
const response = await fetch(`/api/keamanan/kontakitem/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Kontak item berhasil dihapus");
|
||||
await kontakDaruratItem.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus kontak item");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus kontak item");
|
||||
} finally {
|
||||
kontakDaruratItem.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultFormItem },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/keamanan/kontakitem/${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,
|
||||
nomorTelepon: data.nomorTelepon,
|
||||
icon: data.icon,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kontak darurat:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateFormItem.safeParse(kontakDaruratItem.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
kontakDaruratItem.update.loading = true;
|
||||
const response = await fetch(`/api/keamanan/kontakitem/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
nomorTelepon: this.form.nomorTelepon,
|
||||
icon: this.form.icon,
|
||||
}),
|
||||
});
|
||||
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 kontak item");
|
||||
await kontakDaruratItem.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate kontak item");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating kontak item:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Gagal mengupdate kontak item"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
kontakDaruratItem.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
kontakDaruratItem.update.id = "";
|
||||
kontakDaruratItem.update.form = { ...defaultFormItem };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const kontakDarurat = proxy({
|
||||
kontakDaruratKeamananState,
|
||||
kontakDaruratItem,
|
||||
});
|
||||
|
||||
export default kontakDarurat;
|
||||
|
||||
@@ -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";
|
||||
@@ -10,12 +11,24 @@ const templateForm = z.object({
|
||||
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
||||
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
|
||||
tanggalWaktu: z.string().min(3, "Tanggal Waktu minimal 3 karakter"),
|
||||
status: z.enum(["Selesai", "Proses", "Gagal"]),
|
||||
penanganan: z.string(),
|
||||
kronologi: z.string().optional(),
|
||||
});
|
||||
|
||||
interface FormData {
|
||||
judul: string;
|
||||
lokasi: string;
|
||||
tanggalWaktu: string;
|
||||
kronologi: string;
|
||||
}
|
||||
|
||||
const defaultForm: FormData = {
|
||||
judul: "",
|
||||
lokasi: "",
|
||||
tanggalWaktu: new Date().toISOString(),
|
||||
kronologi: "",
|
||||
};
|
||||
|
||||
interface FormEditData {
|
||||
judul: string;
|
||||
lokasi: string;
|
||||
tanggalWaktu: string;
|
||||
@@ -24,15 +37,16 @@ interface FormData {
|
||||
kronologi: string;
|
||||
}
|
||||
|
||||
const defaultForm: FormData = {
|
||||
const editForm: FormEditData = {
|
||||
judul: "",
|
||||
lokasi: "",
|
||||
tanggalWaktu: new Date().toISOString(),
|
||||
kronologi: "",
|
||||
status: "Proses",
|
||||
penanganan: "",
|
||||
kronologi: "",
|
||||
};
|
||||
|
||||
|
||||
const laporanPublikState = proxy({
|
||||
create: {
|
||||
form: { ...defaultForm },
|
||||
@@ -97,13 +111,37 @@ const laporanPublikState = proxy({
|
||||
include: { penanganan: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
laporanPublikState.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
laporanPublikState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
laporanPublikState.findMany.page = page;
|
||||
laporanPublikState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
laporanPublikState.findMany.data = res.data.data ?? [];
|
||||
laporanPublikState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
laporanPublikState.findMany.data = [];
|
||||
laporanPublikState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch laporan publik paginated:", err);
|
||||
laporanPublikState.findMany.data = [];
|
||||
laporanPublikState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
laporanPublikState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.LaporanPublikGetPayload<{
|
||||
include: { penanganan: true };
|
||||
@@ -160,7 +198,7 @@ const laporanPublikState = proxy({
|
||||
},
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
form: { ...editForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
@@ -266,7 +304,7 @@ const laporanPublikState = proxy({
|
||||
},
|
||||
reset() {
|
||||
laporanPublikState.edit.id = "";
|
||||
laporanPublikState.edit.form = { ...defaultForm };
|
||||
laporanPublikState.edit.form = { ...editForm };
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
@@ -5,45 +6,17 @@ import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateForm = z.object({
|
||||
pencegahanKriminalitas: z.object({
|
||||
programKeamanan: z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
slug: z.string().min(1, "Slug minimal 1 karakter"),
|
||||
}),
|
||||
tipsKeamanan: z.object({
|
||||
judul: z.string().min(1, "Judul minimal 1 karakter"),
|
||||
konten: z.string().min(1, "Konten minimal 1 karakter"),
|
||||
slug: z.string().min(1, "Slug minimal 1 karakter"),
|
||||
}),
|
||||
videoKeamanan: z.object({
|
||||
judul: z.string().min(1, "Judul minimal 1 karakter"),
|
||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
videoUrl: z.string().min(1, "Video URL minimal 1 karakter"),
|
||||
slug: z.string().min(1, "Slug minimal 1 karakter"),
|
||||
}),
|
||||
}),
|
||||
judul: z.string().min(1, "Judul minimal 1 karakter"),
|
||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
deskripsiSingkat: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
|
||||
linkVideo: z.string().min(1, "Link video minimal 1 karakter"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
pencegahanKriminalitas: {
|
||||
programKeamanan: {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
slug: "",
|
||||
},
|
||||
tipsKeamanan: {
|
||||
judul: "",
|
||||
konten: "",
|
||||
slug: "",
|
||||
},
|
||||
videoKeamanan: {
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
videoUrl: "",
|
||||
slug: "",
|
||||
},
|
||||
},
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
deskripsiSingkat: "",
|
||||
linkVideo: "",
|
||||
};
|
||||
|
||||
const pencegahanKriminalitasState = proxy({
|
||||
@@ -64,7 +37,7 @@ const pencegahanKriminalitasState = proxy({
|
||||
pencegahanKriminalitasState.create.loading = true;
|
||||
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
|
||||
"create"
|
||||
].post(pencegahanKriminalitasState.create.form.pencegahanKriminalitas);
|
||||
].post(pencegahanKriminalitasState.create.form);
|
||||
if (res.status === 200) {
|
||||
pencegahanKriminalitasState.findMany.load();
|
||||
return toast.success("success create");
|
||||
@@ -81,29 +54,46 @@ const pencegahanKriminalitasState = proxy({
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.PencegahanKriminalitasGetPayload<{
|
||||
include: {
|
||||
programKeamanan: true;
|
||||
tipsKeamanan: true;
|
||||
videoKeamanan: true;
|
||||
};
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
pencegahanKriminalitasState.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
pencegahanKriminalitasState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pencegahanKriminalitasState.findMany.page = page;
|
||||
pencegahanKriminalitasState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pencegahanKriminalitasState.findMany.data = res.data.data ?? [];
|
||||
pencegahanKriminalitasState.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
pencegahanKriminalitasState.findMany.data = [];
|
||||
pencegahanKriminalitasState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch pencegahan kriminalitas paginated:", err);
|
||||
pencegahanKriminalitasState.findMany.data = [];
|
||||
pencegahanKriminalitasState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pencegahanKriminalitasState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PencegahanKriminalitasGetPayload<{
|
||||
include: {
|
||||
programKeamanan: true;
|
||||
tipsKeamanan: true;
|
||||
videoKeamanan: true;
|
||||
};
|
||||
omit: { isActive: true };
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
@@ -122,6 +112,30 @@ const pencegahanKriminalitasState = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
findFirst: {
|
||||
data: null as Prisma.PencegahanKriminalitasGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}> | null,
|
||||
loading: false,
|
||||
// findFirst.load()
|
||||
async load() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await ApiFetch.api.keamanan.pencegahankriminalitas["find-first"].get();
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
this.data = res.data.data || null;
|
||||
} else {
|
||||
this.data = null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch pencegahan kriminalitas terbaru:", err);
|
||||
this.data = null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
@@ -187,24 +201,10 @@ const pencegahanKriminalitasState = proxy({
|
||||
const data = result.data;
|
||||
pencegahanKriminalitasState.update.id = data.id;
|
||||
pencegahanKriminalitasState.update.form = {
|
||||
pencegahanKriminalitas: {
|
||||
programKeamanan: {
|
||||
nama: data.programKeamanan.nama,
|
||||
deskripsi: data.programKeamanan.deskripsi,
|
||||
slug: data.programKeamanan.slug,
|
||||
},
|
||||
tipsKeamanan: {
|
||||
judul: data.tipsKeamanan.judul,
|
||||
konten: data.tipsKeamanan.konten,
|
||||
slug: data.tipsKeamanan.slug,
|
||||
},
|
||||
videoKeamanan: {
|
||||
judul: data.videoKeamanan.judul,
|
||||
deskripsi: data.videoKeamanan.deskripsi,
|
||||
videoUrl: data.videoKeamanan.videoUrl,
|
||||
slug: data.videoKeamanan.slug,
|
||||
},
|
||||
},
|
||||
judul: data.judul,
|
||||
deskripsi: data.deskripsi,
|
||||
deskripsiSingkat: data.deskripsiSingkat,
|
||||
linkVideo: data.linkVideo,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
@@ -240,40 +240,11 @@ const pencegahanKriminalitasState = proxy({
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pencegahanKriminalitas: {
|
||||
programKeamanan: {
|
||||
nama: pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.programKeamanan.nama,
|
||||
deskripsi:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.programKeamanan.deskripsi,
|
||||
slug: pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.programKeamanan.slug,
|
||||
},
|
||||
tipsKeamanan: {
|
||||
judul:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.tipsKeamanan.judul,
|
||||
konten:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.tipsKeamanan.konten,
|
||||
slug: pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.tipsKeamanan.slug,
|
||||
},
|
||||
videoKeamanan: {
|
||||
judul:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.videoKeamanan.judul,
|
||||
deskripsi:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.videoKeamanan.deskripsi,
|
||||
videoUrl:
|
||||
pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.videoKeamanan.videoUrl,
|
||||
slug: pencegahanKriminalitasState.update.form
|
||||
.pencegahanKriminalitas.videoKeamanan.slug,
|
||||
},
|
||||
},
|
||||
judul: pencegahanKriminalitasState.update.form.judul,
|
||||
deskripsi: pencegahanKriminalitasState.update.form.deskripsi,
|
||||
deskripsiSingkat:
|
||||
pencegahanKriminalitasState.update.form.deskripsiSingkat,
|
||||
linkVideo: pencegahanKriminalitasState.update.form.linkVideo,
|
||||
}),
|
||||
}
|
||||
);
|
||||
@@ -311,4 +282,4 @@ const pencegahanKriminalitasState = proxy({
|
||||
},
|
||||
},
|
||||
});
|
||||
export default pencegahanKriminalitasState;
|
||||
export default pencegahanKriminalitasState;
|
||||
|
||||
@@ -31,11 +31,13 @@ const templateForm = z.object({
|
||||
doctorSign: z.object({
|
||||
content: z.string().min(1, "Content harus diisi"),
|
||||
}),
|
||||
imageId: z.string().min(1, "Image ID harus diisi"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
title: "",
|
||||
content: "",
|
||||
imageId: "",
|
||||
introduction: {
|
||||
content: "",
|
||||
},
|
||||
@@ -59,6 +61,7 @@ const defaultForm = {
|
||||
doctorSign: {
|
||||
content: "",
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const artikelKesehatanState = proxy({
|
||||
@@ -112,6 +115,7 @@ const artikelKesehatanState = proxy({
|
||||
firstaid: true;
|
||||
mythvsfact: true;
|
||||
doctorsign: true;
|
||||
image: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
@@ -159,6 +163,7 @@ const artikelKesehatanState = proxy({
|
||||
firstaid: true;
|
||||
mythvsfact: true;
|
||||
doctorsign: true;
|
||||
image: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
@@ -213,6 +218,7 @@ const artikelKesehatanState = proxy({
|
||||
doctorSign: {
|
||||
content: data.doctorsign.content,
|
||||
},
|
||||
imageId: data.imageId,
|
||||
};
|
||||
},
|
||||
async submit() {
|
||||
@@ -253,6 +259,7 @@ const artikelKesehatanState = proxy({
|
||||
doctorSign: {
|
||||
content: artikelKesehatanState.edit.form.doctorSign.content,
|
||||
},
|
||||
imageId: artikelKesehatanState.edit.form.imageId,
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
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(1, "Name minimal 1 karakter"),
|
||||
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
|
||||
namaOrangtua: z.string().min(1, "Nama Orangtua minimal 1 karakter"),
|
||||
nomor: z.string().min(1, "Nomor minimal 1 karakter"),
|
||||
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
|
||||
catatan: z.string().min(1, "Catatan minimal 1 karakter"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
name: "",
|
||||
tanggal: "",
|
||||
namaOrangtua: "",
|
||||
nomor: "",
|
||||
alamat: "",
|
||||
catatan: "",
|
||||
};
|
||||
|
||||
const pendaftaranJadwalKegiatanState = proxy({
|
||||
create: {
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templateForm.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const errMsg = cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}: ${v.message}`)
|
||||
.join("\n");
|
||||
toast.error(errMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const payload = { ...this.form };
|
||||
|
||||
const res = await (ApiFetch.api.kesehatan as any)[
|
||||
"pendaftaran-jadwal-kegiatan"
|
||||
].create.post(payload);
|
||||
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan jadwal kegiatan");
|
||||
this.resetForm();
|
||||
await pendaftaranJadwalKegiatanState.findMany.load();
|
||||
return res.data;
|
||||
}
|
||||
} catch (err: any) {
|
||||
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
|
||||
toast.error(msg);
|
||||
console.error("SUBMIT ERROR:", err);
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.form = { ...defaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.PendaftaranJadwalKegiatanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
pendaftaranJadwalKegiatanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
pendaftaranJadwalKegiatanState.findMany.page = page;
|
||||
pendaftaranJadwalKegiatanState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.kesehatan["pendaftaran-jadwal-kegiatan"][
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pendaftaranJadwalKegiatanState.findMany.data = res.data.data ?? [];
|
||||
pendaftaranJadwalKegiatanState.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
pendaftaranJadwalKegiatanState.findMany.data = [];
|
||||
pendaftaranJadwalKegiatanState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Gagal fetch pendaftaran jadwal kegiatan paginated:",
|
||||
err
|
||||
);
|
||||
pendaftaranJadwalKegiatanState.findMany.data = [];
|
||||
pendaftaranJadwalKegiatanState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pendaftaranJadwalKegiatanState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PendaftaranJadwalKegiatanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/kesehatan/pendaftaran-jadwal-kegiatan/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
pendaftaranJadwalKegiatanState.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
pendaftaranJadwalKegiatanState.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
pendaftaranJadwalKegiatanState.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
pendaftaranJadwalKegiatanState.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/kesehatan/pendaftaran-jadwal-kegiatan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Pendaftaran jadwal kegiatan berhasil dihapus"
|
||||
);
|
||||
await pendaftaranJadwalKegiatanState.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus pendaftaran jadwal kegiatan"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menghapus pendaftaran jadwal kegiatan"
|
||||
);
|
||||
} finally {
|
||||
pendaftaranJadwalKegiatanState.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/kesehatan/pendaftaran-jadwal-kegiatan/${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 = {
|
||||
name: data.name,
|
||||
tanggal: data.tanggal,
|
||||
namaOrangtua: data.namaOrangtua,
|
||||
nomor: data.nomor,
|
||||
alamat: data.alamat,
|
||||
catatan: data.catatan,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pendaftaran jadwal kegiatan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateForm.safeParse(pendaftaranJadwalKegiatanState.edit.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
pendaftaranJadwalKegiatanState.edit.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/kesehatan/pendaftaran-jadwal-kegiatan/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
tanggal: this.form.tanggal,
|
||||
namaOrangtua: this.form.namaOrangtua,
|
||||
nomor: this.form.nomor,
|
||||
alamat: this.form.alamat,
|
||||
catatan: this.form.catatan,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
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 pendaftaran jadwal kegiatan");
|
||||
await pendaftaranJadwalKegiatanState.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update pendaftaran jadwal kegiatan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pendaftaran jadwal kegiatan:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update pendaftaran jadwal kegiatan"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pendaftaranJadwalKegiatanState.edit.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
pendaftaranJadwalKegiatanState.edit.id = "";
|
||||
pendaftaranJadwalKegiatanState.edit.form = { ...defaultForm };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default pendaftaranJadwalKegiatanState;
|
||||
@@ -23,7 +23,12 @@ type ProgramInovasiForm = Prisma.ProgramInovasiGetPayload<{
|
||||
|
||||
const programInovasi = proxy({
|
||||
create: {
|
||||
form: {} as ProgramInovasiForm,
|
||||
form: {
|
||||
name: "",
|
||||
description: "",
|
||||
imageId: "",
|
||||
link: ""
|
||||
} as ProgramInovasiForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
// Ensure all required fields are non-null
|
||||
|
||||
@@ -72,7 +72,7 @@ const sdgsDesa = proxy({
|
||||
].get({
|
||||
query,
|
||||
});
|
||||
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
sdgsDesa.findMany.data = res.data.data || [];
|
||||
sdgsDesa.findMany.total = res.data.total || 0;
|
||||
@@ -94,7 +94,7 @@ const sdgsDesa = proxy({
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.SDGSDesaGetPayload<{
|
||||
data: null as Prisma.SdgsDesaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
};
|
||||
|
||||
@@ -354,14 +354,39 @@ const kategoriKegiatan = proxy({
|
||||
id: string;
|
||||
nama: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
kategoriKegiatan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
kategoriKegiatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
kategoriKegiatan.findMany.page = page;
|
||||
kategoriKegiatan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res =
|
||||
await ApiFetch.api.lingkungan.kategorikegiatan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriKegiatan.findMany.data = res.data.data ?? [];
|
||||
kategoriKegiatan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
kategoriKegiatan.findMany.data = [];
|
||||
kategoriKegiatan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch kategori kegiatan paginated:", err);
|
||||
kategoriKegiatan.findMany.data = [];
|
||||
kategoriKegiatan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriKegiatan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KategoriKegiatanGetPayload<{
|
||||
|
||||
@@ -352,17 +352,19 @@ const posisiOrganisasi = proxy({
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
load: async (page = 1, limit?: number, search = "") => {
|
||||
const appliedLimit = limit ?? 10;
|
||||
posisiOrganisasi.findMany.page = page;
|
||||
posisiOrganisasi.findMany.search = search;
|
||||
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
const query: any = { page, limit: appliedLimit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi["find-many"].get({ query });
|
||||
|
||||
|
||||
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
posisiOrganisasi.findMany.data = res.data.data ?? [];
|
||||
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
|
||||
@@ -51,7 +51,7 @@ function Login() {
|
||||
Login
|
||||
</Title>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||
<Image loading="lazy" src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||
</Center>
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
@@ -63,7 +63,7 @@ function Registrasi() {
|
||||
Registrasi
|
||||
</Title>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||
<Image loading="lazy" src={"/darmasaba-icon.png"} alt="" w={80} />
|
||||
</Center>
|
||||
<Box>
|
||||
<TextInput placeholder='Username'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
|
||||
@@ -62,43 +62,50 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Layanan</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant='pills'
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconNews, IconCategory } from '@tabler/icons-react';
|
||||
@@ -49,43 +49,50 @@ function LayoutTabsBerita({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Berita Desa</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -198,6 +198,7 @@ function EditBerita() {
|
||||
objectFit: "contain",
|
||||
border: `1px solid ${colors["blue-button"]}`,
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -93,6 +93,7 @@ function DetailBerita() {
|
||||
h={200}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading='lazy'
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
|
||||
@@ -183,6 +183,7 @@ export default function CreateBerita() {
|
||||
objectFit: 'contain',
|
||||
border: '1px solid #ddd',
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -96,9 +96,11 @@ function ListBerita({ search }: { search: string }) {
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '30%' }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text fz="sm" c="dimmed">
|
||||
@@ -112,7 +114,7 @@ function ListBerita({ search }: { search: string }) {
|
||||
style={{ borderRadius: 8, overflow: 'hidden' }}
|
||||
>
|
||||
{item.image?.link ? (
|
||||
<Image src={item.image.link} alt="gambar" fit="cover" />
|
||||
<Image loading='lazy' src={item.image.link} alt="gambar" fit="cover" />
|
||||
) : (
|
||||
<Box bg={colors['blue-button']} w="100%" h="100%" />
|
||||
)}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function ListImage() {
|
||||
radius="md"
|
||||
onClick={() => {
|
||||
stateFileStorage
|
||||
.del({ name: v.name })
|
||||
.del({ id: v.id })
|
||||
.finally(() => toast("Foto berhasil dihapus"));
|
||||
}}
|
||||
>
|
||||
@@ -131,6 +131,7 @@ export default function ListImage() {
|
||||
h={120}
|
||||
fit="contain"
|
||||
opacity={0.7}
|
||||
loading="lazy"
|
||||
/>
|
||||
<Text c="dimmed" ta="center">
|
||||
Belum ada foto yang tersedia
|
||||
@@ -143,14 +144,15 @@ export default function ListImage() {
|
||||
<Flex justify="center">
|
||||
<Pagination
|
||||
total={total}
|
||||
value={stateFileStorage.page} // Changed from page to value
|
||||
size="md"
|
||||
radius="md"
|
||||
withEdges
|
||||
onChange={(page) => {
|
||||
stateFileStorage.page = page;
|
||||
stateFileStorage.load();
|
||||
stateFileStorage.load({ page });
|
||||
}}
|
||||
/>
|
||||
|
||||
</Flex>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
||||
@@ -48,43 +48,50 @@ function LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Gallery</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant='pills'
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -188,6 +188,7 @@ function EditSuratKeterangan() {
|
||||
objectFit: 'contain',
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -244,6 +245,7 @@ function EditSuratKeterangan() {
|
||||
objectFit: 'contain',
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -110,6 +110,7 @@ function DetailSuratKeterangan() {
|
||||
h={200}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">
|
||||
@@ -130,6 +131,7 @@ function DetailSuratKeterangan() {
|
||||
h={200}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">
|
||||
|
||||
@@ -170,6 +170,7 @@ function CreateSuratKeterangan() {
|
||||
alt="Preview Gambar Utama"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -219,6 +220,7 @@ function CreateSuratKeterangan() {
|
||||
alt="Preview Gambar Tambahan"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -124,14 +124,16 @@ function ListSuratKeterangan({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/desa/layanan/pelayanan_surat_keterangan/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
|
||||
@@ -183,6 +183,7 @@ function EditPenghargaan() {
|
||||
alt="Preview Gambar"
|
||||
radius="md"
|
||||
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -87,6 +87,7 @@ function DetailPenghargaan() {
|
||||
h={200}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">
|
||||
|
||||
@@ -152,6 +152,7 @@ function CreatePenghargaan() {
|
||||
alt="Preview Gambar"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -113,14 +113,16 @@ function ListPenghargaan({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() =>
|
||||
router.push(`/admin/desa/penghargaan/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconListDetails, IconCategory } from '@tabler/icons-react';
|
||||
@@ -48,37 +48,44 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Pengumuman</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant='pills'
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { IconCategory, IconListCheck } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -48,37 +48,44 @@ function LayoutTabsPotensi({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Potensi</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant='pills'
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
|
||||
@@ -202,6 +202,7 @@ function EditPotensi() {
|
||||
objectFit: "contain",
|
||||
border: `1px solid ${colors["blue-button"]}`,
|
||||
}}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -90,6 +90,7 @@ export default function DetailPotensi() {
|
||||
h={200}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading='lazy'
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
|
||||
@@ -165,6 +165,7 @@ function CreatePotensi() {
|
||||
alt="Preview Gambar"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading='lazy'
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -195,7 +196,7 @@ function CreatePotensi() {
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan Potensi
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -120,12 +120,14 @@ function ListPotensi({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/desa/potensi/list-potensi/${item.id}`)}
|
||||
>
|
||||
<IconDeviceImacCog size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconUser, IconUsers, IconCalendar } from '@tabler/icons-react';
|
||||
@@ -55,56 +55,63 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
|
||||
<Stack gap="lg">
|
||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Profile Desa</Title>
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant='pills'
|
||||
color={colors["blue-button"]}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||
}}
|
||||
>
|
||||
{/* Konten dummy, bisa diganti sesuai routing */}
|
||||
<>{children}</>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{/* Konten dummy, bisa diganti sesuai routing */}
|
||||
<>{children}</>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsDetail;
|
||||
export default LayoutTabsDetail;
|
||||
|
||||
@@ -241,6 +241,7 @@ function Page() {
|
||||
height={180}
|
||||
fit="cover"
|
||||
radius="sm"
|
||||
loading='lazy'
|
||||
/>
|
||||
<TextInput
|
||||
label={`Label Gambar ${index + 1}`}
|
||||
|
||||
@@ -55,7 +55,7 @@ function Page() {
|
||||
<Paper bg={colors['white-1']} withBorder radius="md" shadow="xs" p="lg">
|
||||
<Stack gap={0}>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
</Center>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
@@ -102,7 +102,7 @@ function Page() {
|
||||
<Paper bg={colors['white-1']} withBorder radius="md" shadow="xs" p="lg">
|
||||
<Stack gap={0}>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
</Center>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
@@ -152,7 +152,7 @@ function Page() {
|
||||
<Paper bg={colors['white-1']} withBorder radius="md" shadow="xs" p="lg">
|
||||
<Stack gap={0}>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 150, md: 250 }} alt="Logo Desa" />
|
||||
</Center>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
@@ -199,7 +199,7 @@ function Page() {
|
||||
<Paper bg={colors['white-1']} withBorder radius="md" shadow="xs" p="lg">
|
||||
<Stack gap={0}>
|
||||
<Center>
|
||||
<Image src="/pudak-icon.png" w={{ base: 150, md: 250 }} alt="Maskot Desa" />
|
||||
<Image loading='lazy' src="/pudak-icon.png" w={{ base: 150, md: 250 }} alt="Maskot Desa" />
|
||||
</Center>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
@@ -228,6 +228,7 @@ function Page() {
|
||||
fit="cover"
|
||||
radius="md"
|
||||
style={{ border: '1px solid #ccc' }}
|
||||
loading='lazy'
|
||||
/>
|
||||
</Center>
|
||||
<Text ta="center" mt="xs" fw="bold">{img.label}</Text>
|
||||
|
||||
@@ -161,6 +161,7 @@ function EditPerbekelDariMasaKeMasa() {
|
||||
objectFit: 'contain',
|
||||
border: `1px solid ${colors['blue-button']}`,
|
||||
}}
|
||||
loading='lazy'
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -75,6 +75,7 @@ function DetailPerbekelDariMasa() {
|
||||
h={150}
|
||||
radius="md"
|
||||
fit="cover"
|
||||
loading='lazy'
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||
|
||||
@@ -131,6 +131,7 @@ function CreatePerbekelDariMasaKeMasa() {
|
||||
alt="Preview Gambar"
|
||||
radius="md"
|
||||
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||
loading='lazy'
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
@@ -82,12 +82,14 @@ function ListPerbekelDariMasaKeMasa({ search }: { search: string }) {
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
size="xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconDeviceImacCog size={16} />}
|
||||
onClick={() => router.push(`/admin/desa/profile/profile-perbekel-dari-masa-ke-masa/${item.id}`)}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
Detail
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
|
||||
@@ -145,7 +145,7 @@ function ProfilePerbekel() {
|
||||
{/* Preview */}
|
||||
<Box mt="sm">
|
||||
{previewImage ? (
|
||||
<Image src={previewImage} alt="Preview" w={200} h={200} fit="cover" radius="md" />
|
||||
<Image loading='lazy' src={previewImage} alt="Preview" w={200} h={200} fit="cover" radius="md" />
|
||||
) : (
|
||||
<Center w={200} h={200} bg="gray.2">
|
||||
<Stack align="center" gap="xs">
|
||||
|
||||
@@ -56,7 +56,7 @@ function Page() {
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Center>
|
||||
<Image src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" />
|
||||
<Image loading='lazy' src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={12}>
|
||||
@@ -77,6 +77,7 @@ function Page() {
|
||||
alt="Foto Profil Perbekel"
|
||||
radius="md"
|
||||
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||
loading='lazy'
|
||||
/>
|
||||
</Center>
|
||||
<Paper
|
||||
|
||||
@@ -1,73 +1,150 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import {
|
||||
Stack,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsPanel,
|
||||
TabsTab,
|
||||
Title,
|
||||
Tooltip,
|
||||
ScrollArea,
|
||||
} from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
IconFileAnalytics,
|
||||
IconCoins,
|
||||
IconShoppingCart,
|
||||
IconWallet,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "APB Desa",
|
||||
value: "apbdesa",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa",
|
||||
icon: <IconFileAnalytics size={18} stroke={1.8} />,
|
||||
tooltip: "Lihat ringkasan Anggaran Pendapatan dan Belanja Desa",
|
||||
},
|
||||
{
|
||||
label: "Pendapatan",
|
||||
value: "pendapatan",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan",
|
||||
icon: <IconCoins size={18} stroke={1.8} />,
|
||||
tooltip: "Kelola data pendapatan desa",
|
||||
},
|
||||
{
|
||||
label: "Belanja",
|
||||
value: "belanja",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja",
|
||||
icon: <IconShoppingCart size={18} stroke={1.8} />,
|
||||
tooltip: "Atur data belanja desa",
|
||||
},
|
||||
{
|
||||
label: "Pembiayaan",
|
||||
value: "pembiayaan",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan",
|
||||
icon: <IconWallet size={18} stroke={1.8} />,
|
||||
tooltip: "Kelola data pembiayaan desa",
|
||||
},
|
||||
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const currentTab = tabs.find((tab) => tab.href === pathname);
|
||||
const [activeTab, setActiveTab] = useState<string | null>(
|
||||
currentTab?.value || tabs[0].value
|
||||
);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
const tab = tabs.find((t) => t.value === value);
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
router.push(tab.href);
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
setActiveTab(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
const match = tabs.find((tab) => tab.href === pathname);
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
setActiveTab(match.value);
|
||||
}
|
||||
}, [pathname])
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Pendapatan Asli Desa</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
<Stack gap="lg">
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Pendapatan Asli Desa
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
{/* ✅ Scroll horizontal wrapper */}
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: "pop", duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -3,7 +3,19 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
MultiSelect,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
Group,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
@@ -23,7 +35,7 @@ function EditAPBDesa() {
|
||||
pembiayaanIds: apbState.update.form.pembiayaanIds || [],
|
||||
});
|
||||
|
||||
// Load APB desa by id saat pertama kali
|
||||
// Load APB desa by id
|
||||
useEffect(() => {
|
||||
const loadAPBdesa = async () => {
|
||||
const id = params?.id as string;
|
||||
@@ -46,12 +58,10 @@ function EditAPBDesa() {
|
||||
};
|
||||
|
||||
loadAPBdesa();
|
||||
}, [params?.id]); // ✅ hapus beritaState dari dependency
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
// Update global state with form data
|
||||
apbState.update.form = {
|
||||
...apbState.update.form,
|
||||
tahun: Number(formData.tahun),
|
||||
@@ -70,65 +80,95 @@ function EditAPBDesa() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit APB Desa</Title>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<Group mb="md">
|
||||
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
p="xs"
|
||||
radius="md"
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit APB Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Card */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Tahun */}
|
||||
<TextInput
|
||||
type='number'
|
||||
type="number"
|
||||
value={formData.tahun}
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, tahun: val.target.value });
|
||||
}}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
|
||||
placeholder="masukkan tahun"
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, tahun: e.target.value })
|
||||
}
|
||||
label={<Text fz="sm" fw="bold">Tahun</Text>}
|
||||
placeholder="Masukkan tahun anggaran"
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Selects */}
|
||||
<SelectPendapatan
|
||||
selectedIds={formData.pendapatanIds}
|
||||
onSelectionChange={(ids) => {
|
||||
setFormData({ ...formData, pendapatanIds: ids });
|
||||
}}
|
||||
onSelectionChange={(ids) =>
|
||||
setFormData({ ...formData, pendapatanIds: ids })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectBelanja
|
||||
selectedIds={formData.belanjaIds}
|
||||
onSelectionChange={(ids) => {
|
||||
setFormData({ ...formData, belanjaIds: ids });
|
||||
}}
|
||||
onSelectionChange={(ids) =>
|
||||
setFormData({ ...formData, belanjaIds: ids })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectPembiayaan
|
||||
selectedIds={formData.pembiayaanIds}
|
||||
onSelectionChange={(ids) => {
|
||||
setFormData({ ...formData, pembiayaanIds: ids });
|
||||
}}
|
||||
onSelectionChange={(ids) =>
|
||||
setFormData({ ...formData, pembiayaanIds: ids })
|
||||
}
|
||||
/>
|
||||
<Button onClick={handleSubmit}>Simpan</Button>
|
||||
|
||||
{/* Save Button */}
|
||||
<Group justify="right">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
radius="md"
|
||||
size="md"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/* --- Sub Components --- */
|
||||
|
||||
/* Select Pendapatan */
|
||||
interface SelectPendapatanProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectPendapatan({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectPendapatanProps) {
|
||||
function SelectPendapatan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||
|
||||
useShallowEffect(() => {
|
||||
pendapatanState.findMany.load().then(() => {
|
||||
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
|
||||
});
|
||||
pendapatanState.findMany.load();
|
||||
}, []);
|
||||
|
||||
if (!pendapatanState.findMany.data) {
|
||||
@@ -137,10 +177,10 @@ function EditAPBDesa() {
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
|
||||
data={pendapatanState.findMany.data.map(p => ({
|
||||
label={<Text fz="sm" fw="bold">Pendapatan</Text>}
|
||||
data={pendapatanState.findMany.data.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name
|
||||
label: p.name,
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
@@ -152,22 +192,11 @@ function EditAPBDesa() {
|
||||
);
|
||||
}
|
||||
|
||||
/* Select Belanja */
|
||||
interface SelectBelanjaProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectBelanja({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectBelanjaProps) {
|
||||
function SelectBelanja({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||
|
||||
useShallowEffect(() => {
|
||||
belanjaState.findMany.load().then(() => {
|
||||
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
|
||||
});
|
||||
belanjaState.findMany.load();
|
||||
}, []);
|
||||
|
||||
if (!belanjaState.findMany.data) {
|
||||
@@ -176,10 +205,10 @@ function EditAPBDesa() {
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
|
||||
data={belanjaState.findMany.data.map(b => ({
|
||||
label={<Text fz="sm" fw="bold">Belanja</Text>}
|
||||
data={belanjaState.findMany.data.map((b: any) => ({
|
||||
value: b.id,
|
||||
label: b.name
|
||||
label: b.name,
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
@@ -191,22 +220,11 @@ function EditAPBDesa() {
|
||||
);
|
||||
}
|
||||
|
||||
/* Select Pembiayaan */
|
||||
interface SelectPembiayaanProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectPembiayaan({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectPembiayaanProps) {
|
||||
function SelectPembiayaan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||
|
||||
useShallowEffect(() => {
|
||||
pembiayaanState.findMany.load().then(() => {
|
||||
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
|
||||
});
|
||||
pembiayaanState.findMany.load();
|
||||
}, []);
|
||||
|
||||
if (!pembiayaanState.findMany.data) {
|
||||
@@ -215,10 +233,10 @@ function EditAPBDesa() {
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
|
||||
data={pembiayaanState.findMany.data.map(b => ({
|
||||
value: b.id,
|
||||
label: b.name
|
||||
label={<Text fz="sm" fw="bold">Pembiayaan</Text>}
|
||||
data={pembiayaanState.findMany.data.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
|
||||
@@ -2,148 +2,204 @@
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function DetailAPBDesa() {
|
||||
const apbState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const apbState = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
console.log("PARAM ID:", params?.id)
|
||||
apbState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
apbState.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
const formatRupiah = (value: number) =>
|
||||
new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
apbState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
|
||||
apbState.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push(
|
||||
'/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!apbState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = apbState.findUnique.data;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail APB Desa</Text>
|
||||
{apbState.findUnique.data ? (
|
||||
<Paper key={apbState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tahun</Text>
|
||||
<Text fz={"lg"}>{apbState.findUnique.data?.tahun}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Pembiayaan</Text>
|
||||
{(apbState.findUnique.data?.pembiayaan || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
<Box py={10}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<Paper
|
||||
withBorder
|
||||
w={{ base: '100%', md: '70%', lg: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||
Detail APB Desa
|
||||
</Text>
|
||||
|
||||
<Paper bg={colors['BG-trans']} p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz="lg" fw="bold">
|
||||
Tahun
|
||||
</Text>
|
||||
<Text fz="md" c="dimmed">
|
||||
{data.tahun}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Text fw="bold" fz="lg">
|
||||
Detail Pembiayaan
|
||||
</Text>
|
||||
{(data?.pembiayaan || []).map((item) => (
|
||||
<Text fz="md" c="dimmed" key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Belanja</Text>
|
||||
{(apbState.findUnique.data?.belanja || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
))}
|
||||
<Text fz="md" fw="bold">
|
||||
Total:{' '}
|
||||
{formatRupiah(
|
||||
(data?.pembiayaan || []).reduce(
|
||||
(sum, item) => sum + Number(item.value),
|
||||
0
|
||||
)
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Text fw="bold" fz="lg">
|
||||
Detail Belanja
|
||||
</Text>
|
||||
{(data?.belanja || []).map((item) => (
|
||||
<Text fz="md" c="dimmed" key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Pendapatan</Text>
|
||||
{(apbState.findUnique.data?.pendapatan || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
))}
|
||||
<Text fz="md" fw="bold">
|
||||
Total:{' '}
|
||||
{formatRupiah(
|
||||
(data?.belanja || []).reduce(
|
||||
(sum, item) => sum + Number(item.value),
|
||||
0
|
||||
)
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Stack gap="xs">
|
||||
<Text fw="bold" fz="lg">
|
||||
Detail Pendapatan
|
||||
</Text>
|
||||
{(data?.pendapatan || []).map((item) => (
|
||||
<Text fz="md" c="dimmed" key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
))}
|
||||
<Text fz="md" fw="bold">
|
||||
Total:{' '}
|
||||
{formatRupiah(
|
||||
(data?.pendapatan || []).reduce(
|
||||
(sum, item) => sum + Number(item.value),
|
||||
0
|
||||
)
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Group gap="sm" mt={10}>
|
||||
<Tooltip label="Hapus APB Desa" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
if (apbState.findUnique.data) {
|
||||
setSelectedId(apbState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={apbState.delete.loading || !apbState.findUnique.data}
|
||||
color={"red"}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit APB Desa" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (apbState.findUnique.data) {
|
||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${apbState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!apbState.findUnique.data}
|
||||
color={"green"}
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${data.id}/edit`
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus APB Desa ini?'
|
||||
text="Apakah Anda yakin ingin menghapus APB Desa ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||