Compare commits
10 Commits
nico/9-jun
...
nico/16-ju
| Author | SHA1 | Date | |
|---|---|---|---|
| f7437708c0 | |||
| 7bf5ee69d5 | |||
| e03b071b00 | |||
| 8ded234991 | |||
| 1462e1d256 | |||
| a1c2821153 | |||
| 9f66b037f9 | |||
| 9e725e2eea | |||
| e4b7418ed3 | |||
| 6d312b7a28 |
@@ -22,8 +22,9 @@
|
|||||||
"@mantine/carousel": "^7.16.2",
|
"@mantine/carousel": "^7.16.2",
|
||||||
"@mantine/charts": "^7.17.1",
|
"@mantine/charts": "^7.17.1",
|
||||||
"@mantine/core": "^7.17.4",
|
"@mantine/core": "^7.17.4",
|
||||||
"@mantine/dates": "^7.17.4",
|
"@mantine/dates": "^8.1.0",
|
||||||
"@mantine/dropzone": "^7.17.0",
|
"@mantine/dropzone": "^7.17.0",
|
||||||
|
"@mantine/form": "^8.1.0",
|
||||||
"@mantine/hooks": "^7.17.4",
|
"@mantine/hooks": "^7.17.4",
|
||||||
"@mantine/tiptap": "^7.17.4",
|
"@mantine/tiptap": "^7.17.4",
|
||||||
"@paljs/types": "^8.1.0",
|
"@paljs/types": "^8.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Pelayanan Penduduk Non-Permanent",
|
||||||
|
"deskripsi": "<p>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</p>"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
prisma/data/desa/layanan/pelayananPerizinanBerusaha.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
|
||||||
|
"deskripsi": "<p>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</p>",
|
||||||
|
"link" : "https://oss.go.id/"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
|
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
|
||||||
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
|
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
|
||||||
"pengalaman": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
"pengalaman": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
||||||
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>",
|
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>"
|
||||||
"imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
6
prisma/data/ppid/struktur-ppid/strukturPPID.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id" : "1",
|
||||||
|
"name" : "Struktur PPID"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -50,23 +50,43 @@ model AppMenuChild {
|
|||||||
// ========================================= FILE STORAGE ========================================= //
|
// ========================================= FILE STORAGE ========================================= //
|
||||||
|
|
||||||
model FileStorage {
|
model FileStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
realName String
|
realName String
|
||||||
path String
|
path String
|
||||||
mimeType String
|
mimeType String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
link String
|
link String
|
||||||
Berita Berita[]
|
Berita Berita[]
|
||||||
PotensiDesa PotensiDesa[]
|
PotensiDesa PotensiDesa[]
|
||||||
|
Posyandu Posyandu[]
|
||||||
|
ProfilePPID ProfilePPID[]
|
||||||
|
StrukturPPID StrukturPPID[]
|
||||||
|
|
||||||
Posyandu Posyandu[]
|
GalleryFoto GalleryFoto[]
|
||||||
|
|
||||||
|
PelayananSuratKeterangan PelayananSuratKeterangan[]
|
||||||
|
|
||||||
|
Penghargaan Penghargaan[]
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU PPID ========================================= //
|
//========================================= MENU PPID ========================================= //
|
||||||
|
|
||||||
|
//========================================= STRUKTUR PPID ========================================= //
|
||||||
|
model StrukturPPID {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @db.Text
|
||||||
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================= VISI MISI PPID ========================================= //
|
// ========================================= VISI MISI PPID ========================================= //
|
||||||
model VisiMisiPPID {
|
model VisiMisiPPID {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -91,17 +111,18 @@ model DasarHukumPPID {
|
|||||||
|
|
||||||
// ========================================= PROFILE PPID ========================================= //
|
// ========================================= PROFILE PPID ========================================= //
|
||||||
model ProfilePPID {
|
model ProfilePPID {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @db.Text
|
name String @db.Text
|
||||||
biodata String @db.Text
|
biodata String @db.Text
|
||||||
riwayat String @db.Text
|
riwayat String @db.Text
|
||||||
pengalaman String @db.Text
|
pengalaman String @db.Text
|
||||||
unggulan String @db.Text
|
unggulan String @db.Text
|
||||||
imageUrl String?
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
imageId String?
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
deletedAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
isActive Boolean @default(true)
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
|
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
|
||||||
@@ -182,7 +203,7 @@ model FormulirPermohonanKeberatan {
|
|||||||
|
|
||||||
// ========================================= IKM ========================================= //
|
// ========================================= IKM ========================================= //
|
||||||
model IndeksKepuasanMasyarakat {
|
model IndeksKepuasanMasyarakat {
|
||||||
id Int @id @default(autoincrement())
|
id String @id @default(cuid())
|
||||||
label String
|
label String
|
||||||
kepuasan String
|
kepuasan String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -320,51 +341,87 @@ model CategoryPengumuman {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= IMAGES ========================================= //
|
|
||||||
model Images {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
url String
|
|
||||||
label String @default("null")
|
|
||||||
active Boolean @default(true)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
GalleryFoto GalleryFoto[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================= VIDEOS ========================================= //
|
|
||||||
model Videos {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
url String
|
|
||||||
label String @default("null")
|
|
||||||
active Boolean @default(true)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
GalleryVideo GalleryVideo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================= GALLERY ========================================= //
|
// ========================================= GALLERY ========================================= //
|
||||||
model GalleryFoto {
|
model GalleryFoto {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
image String
|
deskripsi String @db.Text
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
imagesId String? @unique
|
imagesId String? @unique
|
||||||
imageGalleryFoto Images? @relation(fields: [imagesId], references: [id])
|
imageGalleryFoto FileStorage? @relation(fields: [imagesId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model GalleryVideo {
|
model GalleryVideo {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
video String
|
deskripsi String @db.Text
|
||||||
createdAt DateTime @default(now())
|
linkVideo String
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
deletedAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
isActive Boolean @default(true)
|
deletedAt DateTime @default(now())
|
||||||
videosId String? @unique
|
isActive Boolean @default(true)
|
||||||
videosGalleryVideo Videos? @relation(fields: [videosId], references: [id])
|
}
|
||||||
|
|
||||||
|
// ========================================= LAYANAN DESA ========================================= //
|
||||||
|
model PelayananSuratKeterangan {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model PelayananTelunjukSaktiDesa {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
link String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model PelayananPerizinanBerusaha {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
link String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model PelayananPendudukNonPermanen {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================= PENGHARGAAN ========================================= //
|
||||||
|
model Penghargaan {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
juara String
|
||||||
|
deskripsi String @db.Text
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= MENU KESEHATAN ========================================= //
|
// ========================================= MENU KESEHATAN ========================================= //
|
||||||
|
|||||||
138
prisma/seed.ts
@@ -6,13 +6,12 @@ import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInf
|
|||||||
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
||||||
import layanan from "./data/list-layanan.json";
|
import layanan from "./data/list-layanan.json";
|
||||||
import potensi from "./data/list-potensi.json";
|
import potensi from "./data/list-potensi.json";
|
||||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
|
||||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||||
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||||
import path from "path";
|
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||||
import fs from "fs";
|
import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
|
||||||
import { mkdir, writeFile } from "fs/promises";
|
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||||
import { v4 as uuid } from "uuid";
|
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const l of layanan) {
|
for (const l of layanan) {
|
||||||
@@ -31,6 +30,62 @@ import { v4 as uuid } from "uuid";
|
|||||||
|
|
||||||
console.log("layanan success ...");
|
console.log("layanan success ...");
|
||||||
|
|
||||||
|
for (const l of pelayananPerizinanBerusaha) {
|
||||||
|
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||||
|
where: {
|
||||||
|
id: l.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: l.name,
|
||||||
|
deskripsi: l.deskripsi,
|
||||||
|
link: l.link,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: l.id,
|
||||||
|
name: l.name,
|
||||||
|
deskripsi: l.deskripsi,
|
||||||
|
link: l.link,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("pelayanan perizinan berusaha success ...");
|
||||||
|
|
||||||
|
for (const l of pelayananPendudukNonPermanen) {
|
||||||
|
await prisma.pelayananPendudukNonPermanen.upsert({
|
||||||
|
where: {
|
||||||
|
id: l.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: l.name,
|
||||||
|
deskripsi: l.deskripsi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: l.id,
|
||||||
|
name: l.name,
|
||||||
|
deskripsi: l.deskripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("pelayanan perizinan berusaha success ...");
|
||||||
|
|
||||||
|
for (const s of strukturPPID) {
|
||||||
|
await prisma.strukturPPID.upsert({
|
||||||
|
where: {
|
||||||
|
id: s.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: s.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: s.id,
|
||||||
|
name: s.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("struktur ppid success ...");
|
||||||
|
|
||||||
for (const p of potensi) {
|
for (const p of potensi) {
|
||||||
await prisma.potensi.upsert({
|
await prisma.potensi.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -124,56 +179,29 @@ import { v4 as uuid } from "uuid";
|
|||||||
}
|
}
|
||||||
console.log("cara memperoleh salinan informasi success ...");
|
console.log("cara memperoleh salinan informasi success ...");
|
||||||
|
|
||||||
const seedProfilePPID = async () => {
|
for (const c of profilePPID) {
|
||||||
const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid")
|
await prisma.profilePPID.upsert({
|
||||||
|
where: { id: c.id },
|
||||||
// Buat folder hanya jika belum ada
|
update: {
|
||||||
if (!fs.existsSync(targetDir)) {
|
name: c.name,
|
||||||
await mkdir(targetDir, { recursive: true })
|
biodata: c.biodata,
|
||||||
}
|
riwayat: c.riwayat,
|
||||||
|
pengalaman: c.pengalaman,
|
||||||
for (const c of profilePPID) {
|
unggulan: c.unggulan,
|
||||||
let finalImageUrl = c.imageUrl
|
// imageId tidak di-update
|
||||||
|
},
|
||||||
if (c.imageUrl.startsWith("/uploads/seeded-images/")) {
|
create: {
|
||||||
const filename = path.basename(c.imageUrl)
|
id: c.id,
|
||||||
const seedImagePath = path.resolve("prisma", "seed-images", filename)
|
name: c.name,
|
||||||
|
biodata: c.biodata,
|
||||||
const targetFilename = `${uuid()}_${filename}`
|
riwayat: c.riwayat,
|
||||||
const targetPath = path.join(targetDir, targetFilename)
|
pengalaman: c.pengalaman,
|
||||||
|
unggulan: c.unggulan,
|
||||||
const buffer = fs.readFileSync(seedImagePath)
|
// imageId tidak di-create
|
||||||
await writeFile(targetPath, buffer)
|
},
|
||||||
|
});
|
||||||
finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}`
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.profilePPID.upsert({
|
|
||||||
where: { id: c.id },
|
|
||||||
update: {
|
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
|
||||||
riwayat: c.riwayat,
|
|
||||||
pengalaman: c.pengalaman,
|
|
||||||
unggulan: c.unggulan,
|
|
||||||
imageUrl: finalImageUrl,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: c.id,
|
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
|
||||||
riwayat: c.riwayat,
|
|
||||||
pengalaman: c.pengalaman,
|
|
||||||
unggulan: c.unggulan,
|
|
||||||
imageUrl: finalImageUrl,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("✅ profilePPID seeded from JSON with image copying")
|
|
||||||
}
|
}
|
||||||
|
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
|
||||||
await seedProfilePPID()
|
|
||||||
|
|
||||||
for (const v of visiMisiPPID) {
|
for (const v of visiMisiPPID) {
|
||||||
await prisma.visiMisiPPID.upsert({
|
await prisma.visiMisiPPID.upsert({
|
||||||
@@ -210,8 +238,6 @@ import { v4 as uuid } from "uuid";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("dasar hukum PPID success ...");
|
console.log("dasar hukum PPID success ...");
|
||||||
|
|
||||||
|
|
||||||
})()
|
})()
|
||||||
.then(() => prisma.$disconnect())
|
.then(() => prisma.$disconnect())
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
BIN
public/Share.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/bagikanPostingan.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/sematkan.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
public/struktur_ppid.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
BIN
public/video.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
@@ -21,7 +21,7 @@ export function ModalKonfirmasiHapus({
|
|||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title="Konfirmasi Hapus"
|
title={<Text fw={"bold"} fz={"xl"}>Konfirmasi Hapus</Text>}
|
||||||
centered
|
centered
|
||||||
>
|
>
|
||||||
<Text mb="md">{text}</Text>
|
<Text mb="md">{text}</Text>
|
||||||
|
|||||||
415
src/app/admin/(dashboard)/_state/desa/gallery.ts
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
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 fotoForm = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||||
|
imagesId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoForm = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||||
|
linkVideo: z.string().min(1, { message: "Link video is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormFoto = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imagesId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultFormVideo = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
linkVideo: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const foto = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormFoto },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = fotoForm.safeParse(foto.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
foto.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.gallery.foto["create"].post(
|
||||||
|
foto.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
foto.findMany.load();
|
||||||
|
return toast.success("Foto berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan foto");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
foto.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
foto.create.form = { ...defaultFormFoto };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.GalleryFotoGetPayload<{
|
||||||
|
include: {
|
||||||
|
imageGalleryFoto: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
foto.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.GalleryFotoGetPayload<{
|
||||||
|
include: {
|
||||||
|
imageGalleryFoto: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/gallery/foto/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
foto.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch foto:", res.statusText);
|
||||||
|
foto.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching foto:", error);
|
||||||
|
foto.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
foto.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/gallery/foto/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Foto berhasil dihapus");
|
||||||
|
await foto.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus foto");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus foto");
|
||||||
|
} finally {
|
||||||
|
foto.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormFoto },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/gallery/foto/${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,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imagesId: data.imagesId || "",
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading foto:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = fotoForm.safeParse(foto.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
foto.update.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/gallery/foto/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imagesId: this.form.imagesId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Foto berhasil diupdate");
|
||||||
|
await foto.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate foto");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating foto:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal mengupdate foto"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
foto.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
foto.update.id = "";
|
||||||
|
foto.update.form = { ...defaultFormFoto };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const video = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormVideo },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = videoForm.safeParse(video.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
video.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.gallery.video["create"].post(
|
||||||
|
video.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
video.findMany.load();
|
||||||
|
return toast.success("Video berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan video");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
video.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
video.create.form = { ...defaultFormVideo };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.GalleryVideoGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
video.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.GalleryVideoGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/gallery/video/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
video.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch video:", res.statusText);
|
||||||
|
video.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching video:", error);
|
||||||
|
video.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
video.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/gallery/video/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Video berhasil dihapus");
|
||||||
|
await video.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus video");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus video");
|
||||||
|
} finally {
|
||||||
|
video.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormVideo },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/gallery/video/${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,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
linkVideo: data.linkVideo,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading video:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = videoForm.safeParse(video.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
video.update.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/gallery/video/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
linkVideo: this.form.linkVideo,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Video berhasil diupdate");
|
||||||
|
await video.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate video");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating video:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal mengupdate video"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
video.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
video.update.id = "";
|
||||||
|
video.update.form = { ...defaultFormVideo };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateGallery = proxy({
|
||||||
|
foto,
|
||||||
|
video,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stateGallery;
|
||||||
718
src/app/admin/(dashboard)/_state/desa/layananDesa.ts
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
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 templateSuratKeteranganForm = z.object({
|
||||||
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const suratKeteranganForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const telunjukSaktiDesaForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
link: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const templateTelunjukSaktiDesaForm = z.object({
|
||||||
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const templatePelayananPerizinanBerusaha = z.object({
|
||||||
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
link: z.string().min(3, "Link minimal 3 karakter"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type pelayananPerizinanBerusahaForm =
|
||||||
|
Prisma.PelayananPerizinanBerusahaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
deskripsi: true;
|
||||||
|
link: true;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const pelayananPerizinanBerusahaForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
link: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const templatePelayananPendudukNonPermanen = z.object({
|
||||||
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type pelayananPendudukNonPermanenForm =
|
||||||
|
Prisma.PelayananPendudukNonPermanenGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
deskripsi: true;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const pelayananPendudukNonPermanenForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const suratKeterangan = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...suratKeteranganForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateSuratKeteranganForm.safeParse(
|
||||||
|
suratKeterangan.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
suratKeterangan.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
|
||||||
|
"create"
|
||||||
|
].post(suratKeterangan.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
suratKeterangan.findMany.load();
|
||||||
|
return toast.success("Surat Keterangan berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan surat keterangan");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
suratKeterangan.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
suratKeterangan.create.form = { ...suratKeteranganForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
|
include: { image: true };
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
suratKeterangan.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanansuratketerangan/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
suratKeterangan.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch surat keterangan:", res.statusText);
|
||||||
|
suratKeterangan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching surat keterangan:", error);
|
||||||
|
suratKeterangan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
suratKeterangan.delete.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanansuratketerangan/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Surat Keterangan berhasil dihapus");
|
||||||
|
await suratKeterangan.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus surat keterangan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus surat keterangan");
|
||||||
|
} finally {
|
||||||
|
suratKeterangan.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...suratKeteranganForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanansuratketerangan/${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,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId || "",
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching surat keterangan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateSuratKeteranganForm.safeParse(
|
||||||
|
suratKeterangan.edit.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
suratKeterangan.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanansuratketerangan/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Surat Keterangan berhasil diupdate");
|
||||||
|
await suratKeterangan.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
result.message || "Gagal mengupdate surat keterangan"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating surat keterangan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update surat keterangan"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
suratKeterangan.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pelayananTelunjukSaktiDesa = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...telunjukSaktiDesaForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateTelunjukSaktiDesaForm.safeParse(
|
||||||
|
pelayananTelunjukSaktiDesa.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pelayananTelunjukSaktiDesa.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
|
||||||
|
"create"
|
||||||
|
].post(pelayananTelunjukSaktiDesa.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
pelayananTelunjukSaktiDesa.findMany.load();
|
||||||
|
return toast.success("Telunjuk Sakti Desa berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan telunjuk sakti desa");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
pelayananTelunjukSaktiDesa.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
pelayananTelunjukSaktiDesa.create.form = { ...telunjukSaktiDesaForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanantelunjuksaktidesa/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
pelayananTelunjukSaktiDesa.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch telunjuk sakti desa:", res.statusText);
|
||||||
|
pelayananTelunjukSaktiDesa.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching telunjuk sakti desa:", error);
|
||||||
|
pelayananTelunjukSaktiDesa.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
pelayananTelunjukSaktiDesa.delete.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanantelunjuksaktidesa/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus");
|
||||||
|
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus telunjuk sakti desa");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus telunjuk sakti desa");
|
||||||
|
} finally {
|
||||||
|
pelayananTelunjukSaktiDesa.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...telunjukSaktiDesaForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanantelunjuksaktidesa/${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,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
link: data.link,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching telunjuk sakti desa:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateTelunjukSaktiDesaForm.safeParse(
|
||||||
|
pelayananTelunjukSaktiDesa.edit.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pelayananTelunjukSaktiDesa.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayanantelunjuksaktidesa/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
link: this.form.link,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate");
|
||||||
|
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
result.message || "Gagal mengupdate telunjuk sakti desa"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating telunjuk sakti desa:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update telunjuk sakti desa"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
pelayananTelunjukSaktiDesa.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const pelayananPerizinanBerusaha = proxy({
|
||||||
|
findById: {
|
||||||
|
data: null as pelayananPerizinanBerusahaForm | null,
|
||||||
|
loading: false,
|
||||||
|
initialize() {
|
||||||
|
pelayananPerizinanBerusaha.findById.data = {
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
link: "",
|
||||||
|
} as pelayananPerizinanBerusahaForm;
|
||||||
|
},
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
pelayananPerizinanBerusaha.findById.loading = true;
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananperizinanberusaha/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
pelayananPerizinanBerusaha.findById.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to fetch pelayanan perizinan berusaha:",
|
||||||
|
res.statusText
|
||||||
|
);
|
||||||
|
pelayananPerizinanBerusaha.findById.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching pelayanan perizinan berusaha:", error);
|
||||||
|
pelayananPerizinanBerusaha.findById.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...pelayananPerizinanBerusahaForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak boleh kosong");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananperizinanberusaha/${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;
|
||||||
|
pelayananPerizinanBerusaha.update.id = data.id;
|
||||||
|
pelayananPerizinanBerusaha.update.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
link: data.link,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching pelayanan perizinan berusaha:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update(data: pelayananPerizinanBerusahaForm) {
|
||||||
|
const cek = templatePelayananPerizinanBerusaha.safeParse(data);
|
||||||
|
if (!cek.success) {
|
||||||
|
const errors = cek.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pelayananPerizinanBerusaha.update.loading = true;
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananperizinanberusaha/${data.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
toast.success("Pelayanan perizinan berusaha berhasil diupdate");
|
||||||
|
await pelayananPerizinanBerusaha.findById.load(data.id);
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal mengupdate pelayanan perizinan berusaha");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating pelayanan perizinan berusaha:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat mengupdate pelayanan perizinan berusaha"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
pelayananPerizinanBerusaha.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pelayananPendudukNonPermanen = proxy({
|
||||||
|
findById: {
|
||||||
|
data: null as pelayananPendudukNonPermanenForm | null,
|
||||||
|
loading: false,
|
||||||
|
initialize() {
|
||||||
|
pelayananPendudukNonPermanen.findById.data = {
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
} as pelayananPendudukNonPermanenForm;
|
||||||
|
},
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
pelayananPendudukNonPermanen.findById.loading = true;
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananpenduduknonpermanen/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
pelayananPendudukNonPermanen.findById.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to fetch pelayanan penduduk non permanen:",
|
||||||
|
res.statusText
|
||||||
|
);
|
||||||
|
pelayananPendudukNonPermanen.findById.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching pelayanan penduduk non permanen:", error);
|
||||||
|
pelayananPendudukNonPermanen.findById.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...pelayananPendudukNonPermanenForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak boleh kosong");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananpenduduknonpermanen/${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;
|
||||||
|
pelayananPendudukNonPermanen.update.id = data.id;
|
||||||
|
pelayananPendudukNonPermanen.update.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching pelayanan penduduk non permanen:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update(data: pelayananPendudukNonPermanenForm) {
|
||||||
|
const cek = templatePelayananPendudukNonPermanen.safeParse(data);
|
||||||
|
if (!cek.success) {
|
||||||
|
const errors = cek.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pelayananPendudukNonPermanen.update.loading = true;
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/layanan/pelayananpenduduknonpermanen/${data.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
toast.success("Pelayanan penduduk non permanen berhasil diupdate");
|
||||||
|
await pelayananPendudukNonPermanen.findById.load(data.id);
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal mengupdate pelayanan penduduk non permanen");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating pelayanan penduduk non permanen:", error);
|
||||||
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat mengupdate pelayanan penduduk non permanen"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
pelayananPendudukNonPermanen.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateLayananDesa = proxy({
|
||||||
|
suratKeterangan,
|
||||||
|
pelayananPerizinanBerusaha,
|
||||||
|
pelayananTelunjukSaktiDesa,
|
||||||
|
pelayananPendudukNonPermanen,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stateLayananDesa;
|
||||||
221
src/app/admin/(dashboard)/_state/desa/penghargaan.ts
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
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).max(50),
|
||||||
|
juara: z.string().min(1).max(50),
|
||||||
|
deskripsi: z.string().min(1).max(5000),
|
||||||
|
imageId: z.string().min(1).max(50),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
juara: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const penghargaanState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(penghargaanState.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
penghargaanState.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.penghargaan["create"].post(
|
||||||
|
penghargaanState.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
penghargaanState.findMany.load();
|
||||||
|
return toast.success("success create");
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
penghargaanState.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PenghargaanGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.penghargaan["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
penghargaanState.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PenghargaanGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/penghargaan/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
penghargaanState.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
penghargaanState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading penghargaan:", error);
|
||||||
|
penghargaanState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
penghargaanState.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/desa/penghargaan/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "Penghargaan berhasil dihapus");
|
||||||
|
await penghargaanState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus penghargaan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus penghargaan");
|
||||||
|
} finally {
|
||||||
|
penghargaanState.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/desa/penghargaan/${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,
|
||||||
|
juara: data.juara,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading penghargaan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(penghargaanState.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
penghargaanState.edit.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/desa/penghargaan/${penghargaanState.edit.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
juara: this.form.juara,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update penghargaan");
|
||||||
|
await penghargaanState.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal update penghargaan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating penghargaan:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update penghargaan"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
penghargaanState.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
penghargaanState.edit.id = "";
|
||||||
|
penghargaanState.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export default penghargaanState;
|
||||||
@@ -11,12 +11,13 @@ const templateGrafikJenisKelamin = z.object({
|
|||||||
|
|
||||||
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
|
id: true;
|
||||||
laki: true;
|
laki: true;
|
||||||
perempuan: true;
|
perempuan: true;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const defaultForm: GrafikJenisKelamin = {
|
const defaultForm: Omit<GrafikJenisKelamin, 'id'> & { id?: string } = {
|
||||||
laki: "",
|
laki: "",
|
||||||
perempuan: "",
|
perempuan: "",
|
||||||
};
|
};
|
||||||
@@ -41,9 +42,16 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
"create"
|
"create"
|
||||||
].post(grafikBerdasarkanJenisKelamin.create.form);
|
].post(grafikBerdasarkanJenisKelamin.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
grafikBerdasarkanJenisKelamin.create.form = defaultForm;
|
const id = res.data?.data?.id;
|
||||||
grafikBerdasarkanJenisKelamin.findMany.load();
|
if (id) {
|
||||||
return toast.success("success create");
|
toast.success("Success create");
|
||||||
|
grafikBerdasarkanJenisKelamin.create.form = {
|
||||||
|
laki: "",
|
||||||
|
perempuan: "",
|
||||||
|
};
|
||||||
|
grafikBerdasarkanJenisKelamin.findMany.load();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return toast.error("failed create");
|
return toast.error("failed create");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -69,9 +77,103 @@ const grafikBerdasarkanJenisKelamin = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
grafikBerdasarkanJenisKelamin.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
grafikBerdasarkanJenisKelamin.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
|
||||||
|
grafikBerdasarkanJenisKelamin.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: {...defaultForm},
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templateGrafikJenisKelamin.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await grafikBerdasarkanJenisKelamin.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data grafik berdasarkan jenis kelamin");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
grafikBerdasarkanJenisKelamin.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Grafik berdasarkan jenis kelamin berhasil dihapus");
|
||||||
|
await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus grafik berdasarkan jenis kelamin");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin");
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanJenisKelamin.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const stateGrafikBerdasarkanJenisKelamin = proxy({
|
export default grafikBerdasarkanJenisKelamin;
|
||||||
grafikBerdasarkanJenisKelamin,
|
|
||||||
});
|
|
||||||
export default stateGrafikBerdasarkanJenisKelamin;
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const templateGrafikResponden = z.object({
|
|||||||
|
|
||||||
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
|
id: true;
|
||||||
sangatbaik: true;
|
sangatbaik: true;
|
||||||
baik: true;
|
baik: true;
|
||||||
kurangbaik: true;
|
kurangbaik: true;
|
||||||
@@ -20,7 +21,7 @@ type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const defaultForm: GrafikResponden = {
|
const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
|
||||||
sangatbaik: "",
|
sangatbaik: "",
|
||||||
baik: "",
|
baik: "",
|
||||||
kurangbaik: "",
|
kurangbaik: "",
|
||||||
@@ -47,9 +48,18 @@ const grafikBerdasarkanResponden = proxy({
|
|||||||
"create"
|
"create"
|
||||||
].post(grafikBerdasarkanResponden.create.form);
|
].post(grafikBerdasarkanResponden.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
grafikBerdasarkanResponden.create.form = defaultForm;
|
const id = res.data?.data?.id;
|
||||||
grafikBerdasarkanResponden.findMany.load();
|
if (id) {
|
||||||
return toast.success("success create");
|
toast.success("Success create");
|
||||||
|
grafikBerdasarkanResponden.create.form = {
|
||||||
|
sangatbaik: "",
|
||||||
|
baik: "",
|
||||||
|
kurangbaik: "",
|
||||||
|
tidakbaik: "",
|
||||||
|
};
|
||||||
|
grafikBerdasarkanResponden.findMany.load();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return toast.error("failed create");
|
return toast.error("failed create");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -75,10 +85,112 @@ const grafikBerdasarkanResponden = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/ppid/grafikberdasarkanresponden/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
grafikBerdasarkanResponden.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
grafikBerdasarkanResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik berdasarkan responden:", error);
|
||||||
|
grafikBerdasarkanResponden.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: {...defaultForm},
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cek = templateGrafikResponden.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/grafikberdasarkanresponden/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
|
||||||
|
await grafikBerdasarkanResponden.findMany.load();
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data grafik berdasarkan responden");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
grafikBerdasarkanResponden.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/ppid/grafikberdasarkanresponden/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Grafik berdasarkan responden berhasil dihapus");
|
||||||
|
await grafikBerdasarkanResponden.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus grafik berdasarkan responden");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan responden");
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanResponden.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const stateGrafikResponden = proxy({
|
export default grafikBerdasarkanResponden;
|
||||||
grafikBerdasarkanResponden,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default stateGrafikResponden;
|
|
||||||
@@ -5,80 +5,182 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateGrafikUmur = z.object({
|
const templateGrafikUmur = z.object({
|
||||||
remaja: z.string().min(2, "Data remaja harus diisi"),
|
remaja: z.string().min(2, "Data remaja harus diisi"),
|
||||||
dewasa: z.string().min(2, "Data dewasa harus diisi"),
|
dewasa: z.string().min(2, "Data dewasa harus diisi"),
|
||||||
orangtua: z.string().min(2, "Data orangtua harus diisi"),
|
orangtua: z.string().min(2, "Data orangtua harus diisi"),
|
||||||
lansia: z.string().min(2, "Data lansia harus diisi"),
|
lansia: z.string().min(2, "Data lansia harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
remaja: true;
|
id: true;
|
||||||
dewasa: true;
|
remaja: true;
|
||||||
orangtua: true;
|
dewasa: true;
|
||||||
lansia: true;
|
orangtua: true;
|
||||||
};
|
lansia: true;
|
||||||
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const defaultForm: GrafikUmur = {
|
const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
|
||||||
remaja: "",
|
remaja: "",
|
||||||
dewasa: "",
|
dewasa: "",
|
||||||
orangtua: "",
|
orangtua: "",
|
||||||
lansia: "",
|
lansia: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const grafikBerdasarkanUmur = proxy({
|
const grafikBerdasarkanUmur = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: defaultForm,
|
form: defaultForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templateGrafikUmur.safeParse(
|
const cek = templateGrafikUmur.safeParse(
|
||||||
grafikBerdasarkanUmur.create.form
|
grafikBerdasarkanUmur.create.form
|
||||||
);
|
);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
.join("\n")}] required`;
|
.join("\n")}] required`;
|
||||||
return toast.error(err);
|
return toast.error(err);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
grafikBerdasarkanUmur.create.loading = true;
|
grafikBerdasarkanUmur.create.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
||||||
"create"
|
"create"
|
||||||
].post(grafikBerdasarkanUmur.create.form);
|
].post(grafikBerdasarkanUmur.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
grafikBerdasarkanUmur.create.form = defaultForm;
|
const id = res.data?.data?.id;
|
||||||
grafikBerdasarkanUmur.findMany.load();
|
if (id) {
|
||||||
return toast.success("success create");
|
toast.success("Success create");
|
||||||
}
|
grafikBerdasarkanUmur.create.form = {
|
||||||
return toast.error("failed create");
|
remaja: "",
|
||||||
} catch (error) {
|
dewasa: "",
|
||||||
console.log((error as Error).message);
|
orangtua: "",
|
||||||
} finally {
|
lansia: "",
|
||||||
grafikBerdasarkanUmur.create.loading = false;
|
};
|
||||||
}
|
grafikBerdasarkanUmur.findMany.load();
|
||||||
},
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanUmur.create.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
findMany: {
|
},
|
||||||
data: null as
|
findMany: {
|
||||||
| Prisma.GrafikBerdasarkanUmurGetPayload<{
|
data: null as
|
||||||
omit: { isActive: true };
|
| Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
|
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/ppid/grafikberdasarkanumur/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
grafikBerdasarkanUmur.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
grafikBerdasarkanUmur.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik berdasarkan umur:", error);
|
||||||
|
grafikBerdasarkanUmur.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cek = templateGrafikUmur.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/grafikberdasarkanumur/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
await grafikBerdasarkanUmur.findMany.load();
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data grafik berdasarkan umur");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
const stateGrafikBerdasarkanUmur = proxy({
|
try {
|
||||||
grafikBerdasarkanUmur,
|
grafikBerdasarkanUmur.delete.loading = true;
|
||||||
})
|
|
||||||
|
|
||||||
export default stateGrafikBerdasarkanUmur;
|
const response = await fetch(`/api/ppid/grafikberdasarkanumur/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Grafik berdasarkan umur berhasil dihapus");
|
||||||
|
await grafikBerdasarkanUmur.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus grafik berdasarkan umur");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan umur");
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanUmur.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default grafikBerdasarkanUmur;
|
||||||
@@ -11,12 +11,13 @@ const templateGrafikHasilKepuasanMasyarakat = z.object({
|
|||||||
|
|
||||||
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
|
id: true;
|
||||||
label: true;
|
label: true;
|
||||||
kepuasan: true;
|
kepuasan: true;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const defaultForm: GrafikHasilKepuasanMasyarakat = {
|
const defaultForm: Omit<GrafikHasilKepuasanMasyarakat, 'id'> & { id?: string } = {
|
||||||
label: "",
|
label: "",
|
||||||
kepuasan: "",
|
kepuasan: "",
|
||||||
};
|
};
|
||||||
@@ -41,13 +42,18 @@ const grafikHasilKepuasanMasyarakat = proxy({
|
|||||||
grafikHasilKepuasanMasyarakat.create.form
|
grafikHasilKepuasanMasyarakat.create.form
|
||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
grafikHasilKepuasanMasyarakat.create.form = {
|
const id = res.data?.data?.id;
|
||||||
label: "",
|
if (id) {
|
||||||
kepuasan: ""
|
toast.success("Success create");
|
||||||
};
|
grafikHasilKepuasanMasyarakat.create.form = {
|
||||||
grafikHasilKepuasanMasyarakat.findMany.load();
|
label: "",
|
||||||
return toast.success("success create");
|
kepuasan: "",
|
||||||
|
};
|
||||||
|
grafikHasilKepuasanMasyarakat.findMany.load();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return toast.error("failed create");
|
return toast.error("failed create");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log((error as Error).message);
|
console.log((error as Error).message);
|
||||||
@@ -58,19 +64,127 @@ const grafikHasilKepuasanMasyarakat = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[]
|
| Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||||
| null,
|
omit: { isActive: true };
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get();
|
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat[
|
||||||
if (res.status === 200) {
|
"find-many"
|
||||||
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||||
|
omit: { isActive: true };
|
||||||
|
}> | null,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/ppid/grafikhasilkepuasamanmasyarakat/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
grafikHasilKepuasanMasyarakat.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
grafikHasilKepuasanMasyarakat.findUnique.data = null;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading grafik hasil kepuasan masyarakat:", error);
|
||||||
|
grafikHasilKepuasanMasyarakat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async byId() {
|
||||||
|
// Method implementation if needed
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
const id = this.id;
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Validasi pakai Zod
|
||||||
|
const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/grafikhasilkepuasamanmasyarakat/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !result?.success) {
|
||||||
|
throw new Error(result?.message || "Gagal update data");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Berhasil update data!");
|
||||||
|
|
||||||
|
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
|
||||||
|
await grafikHasilKepuasanMasyarakat.findMany.load();
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error update data:", error);
|
||||||
|
toast.error("Gagal update data grafik hasil kepuasan masyarakat");
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
grafikHasilKepuasanMasyarakat.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/ppid/grafikhasilkepuasamanmasyarakat/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Grafik hasil kepuasan masyarakat berhasil dihapus");
|
||||||
|
await grafikHasilKepuasanMasyarakat.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus grafik hasil kepuasan masyarakat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus grafik hasil kepuasan masyarakat");
|
||||||
|
} finally {
|
||||||
|
grafikHasilKepuasanMasyarakat.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const stateGrafikHasilKepuasanMasyarakat = proxy({
|
export default grafikHasilKepuasanMasyarakat;
|
||||||
grafikHasilKepuasanMasyarakat,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default stateGrafikHasilKepuasanMasyarakat;
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import ApiFetch from "@/lib/api-fetch";
|
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
@@ -13,11 +12,18 @@ const templateForm = z.object({
|
|||||||
riwayat: z.string().min(3, "Riwayat minimal 3 karakter"),
|
riwayat: z.string().min(3, "Riwayat minimal 3 karakter"),
|
||||||
pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"),
|
pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"),
|
||||||
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
|
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
|
||||||
|
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const defaultForm = {
|
||||||
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
|
name: "",
|
||||||
*/
|
biodata: "",
|
||||||
|
riwayat: "",
|
||||||
|
pengalaman: "",
|
||||||
|
unggulan: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
id: true;
|
id: true;
|
||||||
@@ -26,147 +32,170 @@ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
|||||||
riwayat: true;
|
riwayat: true;
|
||||||
pengalaman: true;
|
pengalaman: true;
|
||||||
unggulan: true;
|
unggulan: true;
|
||||||
imageUrl: true;
|
imageId: true;
|
||||||
|
image?: {
|
||||||
|
select: {
|
||||||
|
link: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State utama ProfilePPID yang mencakup fitur:
|
* Improved State Management - Consolidated and more robust
|
||||||
* - Ambil data berdasarkan ID
|
|
||||||
* - Update data
|
|
||||||
* - Upload gambar
|
|
||||||
*/
|
*/
|
||||||
const stateProfilePPID = proxy({
|
const stateProfilePPID = proxy({
|
||||||
/**
|
// Consolidated data management
|
||||||
* Bagian untuk ambil data berdasarkan ID
|
profile: {
|
||||||
*/
|
|
||||||
findById: {
|
|
||||||
data: null as ProfilePPIDForm | null,
|
data: null as ProfilePPIDForm | null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
error: null as string | null,
|
||||||
|
|
||||||
/**
|
// Single method to load profile data
|
||||||
* Inisialisasi data kosong ke dalam state.
|
|
||||||
*/
|
|
||||||
initialize() {
|
|
||||||
stateProfilePPID.findById.data = {
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
biodata: '',
|
|
||||||
riwayat: '',
|
|
||||||
pengalaman: '',
|
|
||||||
unggulan: '',
|
|
||||||
imageUrl: ''
|
|
||||||
} as ProfilePPIDForm;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mengambil data profil berdasarkan ID.
|
|
||||||
* @param {string} id - ID dari profile yang ingin diambil.
|
|
||||||
*/
|
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
if (!id) {
|
||||||
stateProfilePPID.findById.loading = true;
|
toast.warn("ID tidak valid");
|
||||||
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
|
return null;
|
||||||
query: { id },
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
this.loading = true;
|
||||||
stateProfilePPID.findById.data = res.data?.data ?? null;
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/profileppid/${id}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.data = result.data;
|
||||||
|
return result.data;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal mengambil data profile");
|
throw new Error(result.message || "Gagal mengambil data profile");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error((error as Error).message);
|
const errorMessage = (error as Error).message;
|
||||||
|
this.error = errorMessage;
|
||||||
|
console.error("Load profile error:", errorMessage);
|
||||||
toast.error("Terjadi kesalahan saat mengambil data profile");
|
toast.error("Terjadi kesalahan saat mengambil data profile");
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
stateProfilePPID.findById.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Reset profile data
|
||||||
|
reset() {
|
||||||
|
this.data = null;
|
||||||
|
this.error = null;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// Edit form management
|
||||||
* Bagian untuk update data profile
|
editForm: {
|
||||||
*/
|
id: "",
|
||||||
update: {
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
|
error: null as string | null,
|
||||||
|
isReadOnly: false, // Flag untuk data yang tidak bisa diedit
|
||||||
|
|
||||||
/**
|
// Initialize form with profile data
|
||||||
* Melakukan validasi dan menyimpan perubahan data profile ke server.
|
initialize(profileData: ProfilePPIDForm) {
|
||||||
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
|
this.id = profileData.id;
|
||||||
*/
|
this.isReadOnly = false; // Semua data bisa diedit
|
||||||
async save(data: ProfilePPIDForm) {
|
this.form = {
|
||||||
const cek = templateForm.safeParse(data);
|
name: profileData.name || "",
|
||||||
|
biodata: profileData.biodata || "",
|
||||||
|
riwayat: profileData.riwayat || "",
|
||||||
|
pengalaman: profileData.pengalaman || "",
|
||||||
|
unggulan: profileData.unggulan || "",
|
||||||
|
imageId: profileData.imageId || "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
if (!cek.success) {
|
// Update form field
|
||||||
const errors = cek.error.issues
|
updateField(field: keyof typeof defaultForm, value: string) {
|
||||||
|
this.form[field] = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Submit form
|
||||||
|
async submit() {
|
||||||
|
// Validate form
|
||||||
|
const validation = templateForm.safeParse(this.form);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
const errors = validation.error.issues
|
||||||
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
toast.error(`Form tidak valid: ${errors}`);
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
this.loading = true;
|
||||||
stateProfilePPID.update.loading = true;
|
this.error = null;
|
||||||
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/profileppid/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
toast.success("Berhasil update profile");
|
toast.success("Berhasil update profile");
|
||||||
await stateProfilePPID.findById.load(data.id);
|
// Refresh profile data
|
||||||
|
await stateProfilePPID.profile.load(this.id);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
toast.error("Gagal update profile");
|
throw new Error(result.message || "Gagal update profile");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error((error as Error).message);
|
const errorMessage = (error as Error).message;
|
||||||
|
this.error = errorMessage;
|
||||||
|
console.error("Update profile error:", errorMessage);
|
||||||
toast.error("Terjadi kesalahan saat update profile");
|
toast.error("Terjadi kesalahan saat update profile");
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
stateProfilePPID.update.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
reset() {
|
||||||
|
this.id = "";
|
||||||
|
this.form = { ...defaultForm };
|
||||||
|
this.error = null;
|
||||||
|
this.loading = false;
|
||||||
|
this.isReadOnly = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// Helper methods
|
||||||
* Bagian untuk upload gambar profil
|
async loadForEdit(id: string) {
|
||||||
*/
|
const profileData = await this.profile.load(id);
|
||||||
uploadImage: {
|
if (profileData) {
|
||||||
loading: false,
|
this.editForm.initialize(profileData);
|
||||||
|
}
|
||||||
/**
|
return profileData;
|
||||||
* Mengunggah gambar profil berdasarkan ID.
|
|
||||||
* @param {File} file - File gambar yang akan diunggah.
|
|
||||||
* @param {string} id - ID dari profil yang akan diperbarui gambarnya.
|
|
||||||
*/
|
|
||||||
async save(file: File, id: string) {
|
|
||||||
if (!file || !id) {
|
|
||||||
toast.error("File atau ID harus disertakan");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
stateProfilePPID.uploadImage.loading = true;
|
|
||||||
|
|
||||||
const form = new FormData();
|
|
||||||
form.append("file", file);
|
|
||||||
form.append("id", id);
|
|
||||||
|
|
||||||
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
toast.success("Berhasil mengunggah gambar");
|
|
||||||
await stateProfilePPID.findById.load(id);
|
|
||||||
} else {
|
|
||||||
toast.error("Gagal mengunggah gambar");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error((error as Error).message);
|
|
||||||
toast.error("Terjadi kesalahan saat mengunggah gambar");
|
|
||||||
} finally {
|
|
||||||
stateProfilePPID.uploadImage.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.profile.reset();
|
||||||
|
this.editForm.reset();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
export default stateProfilePPID;
|
||||||
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
|
|
||||||
*/
|
|
||||||
export default stateProfilePPID;
|
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
|
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
type StrukturPPIDForm = Prisma.StrukturPPIDGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
imageId: true;
|
||||||
|
image?: {
|
||||||
|
select: {
|
||||||
|
link: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const stateStrukturPPID = proxy({
|
||||||
|
struktur: {
|
||||||
|
data: null as StrukturPPIDForm | null,
|
||||||
|
loading: false,
|
||||||
|
error: null as string | null,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if(!id) {
|
||||||
|
toast.warn("ID tidak valid")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/strukturppid/${id}`);
|
||||||
|
|
||||||
|
if(!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if(result.success) {
|
||||||
|
this.data = result.data;
|
||||||
|
return result.data
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengambil data struktur")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = (error as Error).message;
|
||||||
|
this.error = errorMessage;
|
||||||
|
console.error("Load struktur error:", errorMessage);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data struktur");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.data = null;
|
||||||
|
this.error = null;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
editStruktur: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
error: null as string | null,
|
||||||
|
isReadOnly: false,
|
||||||
|
|
||||||
|
initialize(strukturData: StrukturPPIDForm) {
|
||||||
|
this.id = strukturData.id;
|
||||||
|
this.isReadOnly = false;
|
||||||
|
this.form = {
|
||||||
|
name: strukturData.name || "",
|
||||||
|
imageId: strukturData.imageId || "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
updateField(field: keyof typeof defaultForm, value: string) {
|
||||||
|
this.form[field] = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
const validation = templateForm.safeParse(this.form);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
const errors = validation.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/ppid/strukturppid/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.form),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update struktur");
|
||||||
|
await stateStrukturPPID.struktur.load(this.id);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update struktur");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = (error as Error).message;
|
||||||
|
this.error = errorMessage;
|
||||||
|
console.error("Update struktur error:", errorMessage);
|
||||||
|
toast.error("Terjadi kesalahan saat update struktur");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.id = "";
|
||||||
|
this.form = { ...defaultForm };
|
||||||
|
this.error = null;
|
||||||
|
this.loading = false;
|
||||||
|
this.isReadOnly = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadForEdit(id: string) {
|
||||||
|
const strukturData = await this.struktur.load(id);
|
||||||
|
if (strukturData) {
|
||||||
|
this.editStruktur.initialize(strukturData);
|
||||||
|
}
|
||||||
|
return strukturData;
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.struktur.reset();
|
||||||
|
this.editStruktur.reset();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default stateStrukturPPID;
|
||||||
|
|
||||||
72
src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Pelayanan Surat Keterangan",
|
||||||
|
value: "pelayanansuratketerangan",
|
||||||
|
href: "/admin/desa/layanan/pelayanan_surat_keterangan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pelayanan Perizinan Berusaha",
|
||||||
|
value: "pelayananperizinanusaha",
|
||||||
|
href: "/admin/desa/layanan/pelayanan_perizinan_berusaha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pelayanan Telunjuk Sakti Desa",
|
||||||
|
value: "pelayanantelunjuksaktidesa",
|
||||||
|
href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pelayanan Penduduk Non-Permanent",
|
||||||
|
value: "pelayanantelunjuknonpermanent",
|
||||||
|
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find(t => t.value === value)
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href)
|
||||||
|
}
|
||||||
|
setActiveTab(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
|
if (match) {
|
||||||
|
setActiveTab(match.value)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Layanan</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 */}
|
||||||
|
<></>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutTabsLayanan;
|
||||||
119
src/app/admin/(dashboard)/desa/gallery/foto/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function EditFoto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadFoto = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await fotoState.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
if (data?.imageGalleryFoto?.link) {
|
||||||
|
setPreviewImage(data.imageGalleryFoto.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading foto:', error);
|
||||||
|
toast.error('Gagal memuat data foto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadFoto();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
fotoState.update.form.imagesId = uploaded.id;
|
||||||
|
}
|
||||||
|
await fotoState.update.update();
|
||||||
|
toast.success('Foto berhasil diperbarui!');
|
||||||
|
router.push('/admin/desa/gallery/foto');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating foto:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui foto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Edit Foto</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||||
|
placeholder='Masukkan judul foto'
|
||||||
|
value={fotoState.update.form.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
(fotoState.update.form.name = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={fotoState.update.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
fotoState.update.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditFoto;
|
||||||
112
src/app/admin/(dashboard)/desa/gallery/foto/[id]/page.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
'use client'
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
|
import React from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailFoto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
fotoState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
fotoState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/gallery/foto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fotoState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Foto</Text>
|
||||||
|
{fotoState.findUnique.data ? (
|
||||||
|
<Paper key={fotoState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{fotoState.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Tanggal Foto</Text>
|
||||||
|
<Text fz={"lg"}>{new Date(fotoState.findUnique.data?.createdAt).toDateString()}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: fotoState.findUnique.data?.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 300, md: 350}} src={fotoState.findUnique.data?.imageGalleryFoto?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (fotoState.findUnique.data) {
|
||||||
|
setSelectedId(fotoState.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={fotoState.delete.loading || !fotoState.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (fotoState.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/gallery/foto/${fotoState.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!fotoState.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailFoto;
|
||||||
@@ -1,45 +1,108 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateFoto() {
|
function CreateFoto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
fotoState.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imagesId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
setPreviewImage(null)
|
||||||
|
setFile(null)
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
fotoState.create.form.imagesId = uploaded.id;
|
||||||
|
await fotoState.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/desa/gallery/foto")
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Foto</Title>
|
<Title order={4}>Create Foto</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
||||||
placeholder='Masukkan judul foto'
|
placeholder='Masukkan judul foto'
|
||||||
|
value={fotoState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
fotoState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<FileInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
placeholder='Masukkan tanggal foto'
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
||||||
<KeamananEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={fotoState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
fotoState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailFoto() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Foto</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Judul Foto</Text>
|
|
||||||
<Text>Foto 1</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Tanggal Foto</Text>
|
|
||||||
<Text>2022-01-01</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Deskripsi Foto</Text>
|
|
||||||
<Text>Deskripsi Foto 1</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/foto/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailFoto;
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function EditFoto() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Edit Foto</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
|
|
||||||
placeholder='Masukkan judul foto'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
|
|
||||||
placeholder='Masukkan tanggal foto'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
|
|
||||||
<KeamananEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditFoto;
|
|
||||||
@@ -1,12 +1,29 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import JudulListTab from '../../../_com/jusulListTab';
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateGallery from '../../../_state/desa/gallery';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function Foto() {
|
function Foto() {
|
||||||
|
const fotoState = useProxy(stateGallery.foto)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
fotoState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!fotoState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -26,16 +43,20 @@ function Foto() {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{fotoState.findMany.data?.map((item) => (
|
||||||
<TableTd>Foto 1</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>2022-01-01</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>Deskripsi Foto 1</TableTd>
|
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/foto/detail')}>
|
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<IconDeviceImac size={20} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd>
|
||||||
</TableTd>
|
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
|
||||||
</TableTr>
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
10
src/app/admin/(dashboard)/desa/gallery/layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
'use client'
|
||||||
|
import LayoutTabsGallery from "../../ppid/_com/layoutTabsGallery"
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LayoutTabsGallery>
|
||||||
|
{children}
|
||||||
|
</LayoutTabsGallery>
|
||||||
|
)
|
||||||
|
}
|
||||||
18
src/app/admin/(dashboard)/desa/gallery/lib/youtube-utils.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function convertYoutubeUrlToEmbed(url: string): string | null {
|
||||||
|
const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
|
||||||
|
const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
|
||||||
|
|
||||||
|
const matchWatch = url.match(watchRegex);
|
||||||
|
const matchShort = url.match(shortRegex);
|
||||||
|
|
||||||
|
if (matchWatch) {
|
||||||
|
return `https://www.youtube.com/embed/${matchWatch[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchShort) {
|
||||||
|
return `https://www.youtube.com/embed/${matchShort[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
33
src/app/admin/(dashboard)/desa/gallery/lib/youtubeEmbed.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// components/YoutubeEmbed.tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, Text } from "@mantine/core";
|
||||||
|
|
||||||
|
type YoutubeEmbedProps = {
|
||||||
|
url?: string;
|
||||||
|
showRawUrl?: boolean; // opsional, buat nampilin URL mentahnya
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function YoutubeEmbed({ url, showRawUrl = false }: YoutubeEmbedProps) {
|
||||||
|
if (!url || !url.includes("embed")) {
|
||||||
|
return <Text c="red">Link embed Youtube tidak valid</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
component="iframe"
|
||||||
|
src={url}
|
||||||
|
width="100%"
|
||||||
|
height={300}
|
||||||
|
allowFullScreen
|
||||||
|
style={{ borderRadius: 8 }}
|
||||||
|
/>
|
||||||
|
{showRawUrl && (
|
||||||
|
<Text fz="sm" c="dimmed" mt={5}>
|
||||||
|
{url}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
|
||||||
import Foto from './foto/page';
|
|
||||||
import Video from './video/page';
|
|
||||||
|
|
||||||
function Gallery() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Gallery</Title>
|
|
||||||
<Tabs color={colors['blue-button']} variant="pills" defaultValue="foto">
|
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
|
||||||
<TabsTab value="foto" leftSection={<IconPhoto size={12} />}>
|
|
||||||
Foto
|
|
||||||
</TabsTab>
|
|
||||||
<TabsTab value="video" leftSection={<IconVideo size={12} />}>
|
|
||||||
Video
|
|
||||||
</TabsTab>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsPanel value="foto">
|
|
||||||
<Foto/>
|
|
||||||
</TabsPanel>
|
|
||||||
|
|
||||||
<TabsPanel value="video">
|
|
||||||
<Video/>
|
|
||||||
</TabsPanel>
|
|
||||||
</Tabs>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Gallery;
|
|
||||||
142
src/app/admin/(dashboard)/desa/gallery/video/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function EditVideo() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [embedLink, setEmbedLink] = useState("");
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
|
const params = useParams()
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: videoState.findUnique.data?.name || '',
|
||||||
|
deskripsi: videoState.findUnique.data?.deskripsi || '',
|
||||||
|
linkVideo: videoState.findUnique.data?.linkVideo || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadVideo = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await videoState.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
linkVideo: data.linkVideo || '',
|
||||||
|
});
|
||||||
|
const embed = convertYoutubeUrlToEmbed(data.linkVideo);
|
||||||
|
setEmbedLink(embed || "");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading video:', error);
|
||||||
|
toast.error('Gagal memuat data video');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadVideo();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
videoState.update.form = {
|
||||||
|
...videoState.update.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
linkVideo: formData.linkVideo,
|
||||||
|
};
|
||||||
|
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||||
|
if (!converted) {
|
||||||
|
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await videoState.update.update();
|
||||||
|
toast.success('Video berhasil diperbarui!');
|
||||||
|
router.push('/admin/desa/gallery/video');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating video:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui video');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Edit Video</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||||
|
placeholder='Masukkan judul video'
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
name: val.target.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<TextInput
|
||||||
|
label="Link Video YouTube"
|
||||||
|
placeholder="https://www.youtube.com/watch?v=abc123"
|
||||||
|
value={formData.linkVideo}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
linkVideo: e.currentTarget.value,
|
||||||
|
})
|
||||||
|
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
||||||
|
setEmbedLink(embed || "");
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
{embedLink && (
|
||||||
|
<iframe
|
||||||
|
className="rounded"
|
||||||
|
width="100%"
|
||||||
|
height="200"
|
||||||
|
src={embedLink}
|
||||||
|
title="Preview Video"
|
||||||
|
allowFullScreen
|
||||||
|
></iframe>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
deskripsi: val,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditVideo;
|
||||||
133
src/app/admin/(dashboard)/desa/gallery/video/[id]/page.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function DetailVideo() {
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
videoState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
videoState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/gallery/video")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!videoState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Video</Text>
|
||||||
|
{videoState.findUnique.data ? (
|
||||||
|
<Paper key={videoState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{videoState.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Video</Text>
|
||||||
|
<Box component="iframe"
|
||||||
|
src={convertToEmbedUrl(videoState.findUnique.data?.linkVideo)}
|
||||||
|
width="100%"
|
||||||
|
height={300}
|
||||||
|
allowFullScreen
|
||||||
|
style={{ borderRadius: 8 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Tanggal Video</Text>
|
||||||
|
<Text fz={"lg"}>{new Date(videoState.findUnique.data?.createdAt).toDateString()}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: videoState.findUnique.data?.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (videoState.findUnique.data) {
|
||||||
|
setSelectedId(videoState.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={videoState.delete.loading || !videoState.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (videoState.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/gallery/video/${videoState.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!videoState.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
);
|
||||||
|
function convertToEmbedUrl(youtubeUrl: string): string {
|
||||||
|
try {
|
||||||
|
const url = new URL(youtubeUrl);
|
||||||
|
const videoId = url.searchParams.get("v");
|
||||||
|
if (!videoId) return youtubeUrl;
|
||||||
|
|
||||||
|
return `https://www.youtube.com/embed/${videoId}`;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error converting YouTube URL to embed:", err);
|
||||||
|
return youtubeUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailVideo;
|
||||||
@@ -1,45 +1,99 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { convertYoutubeUrlToEmbed } from '../../lib/youtube-utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CreateVideo() {
|
function CreateVideo() {
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [link, setLink] = useState("");
|
||||||
|
const [embedLink, setEmbedLink] = useState("");
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
videoState.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
linkVideo: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const converted = convertYoutubeUrlToEmbed(videoState.create.form.linkVideo);
|
||||||
|
if (!converted) {
|
||||||
|
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await videoState.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/desa/gallery/video")
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Video</Title>
|
<Title order={4}>Create Video</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||||
placeholder='Masukkan judul video'
|
placeholder='Masukkan judul video'
|
||||||
/>
|
value={videoState.create.form.name}
|
||||||
<TextInput
|
onChange={(val) => {
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
|
videoState.create.form.name = val.target.value;
|
||||||
placeholder='Masukkan tanggal video'
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<TextInput
|
||||||
|
label="Link Video YouTube"
|
||||||
|
placeholder="https://www.youtube.com/watch?v=abc123"
|
||||||
|
value={link}
|
||||||
|
onChange={(e) => {
|
||||||
|
setLink(e.currentTarget.value);
|
||||||
|
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
||||||
|
setEmbedLink(embed || "");
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
{embedLink && (
|
||||||
|
<iframe
|
||||||
|
style={{ borderRadius: 10, width: "100%", height: 400 }}
|
||||||
|
src={embedLink}
|
||||||
|
title="Preview Video"
|
||||||
|
allowFullScreen
|
||||||
|
></iframe>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||||
<KeamananEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={videoState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
videoState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailVideo() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Video</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Judul Video</Text>
|
|
||||||
<Text>Video 1</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Tanggal Video</Text>
|
|
||||||
<Text>2022-01-01</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Deskripsi Video</Text>
|
|
||||||
<Text>Deskripsi Video 1</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/video/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailVideo;
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function EditVideo() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Edit Video</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
|
||||||
placeholder='Masukkan judul video'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
|
|
||||||
placeholder='Masukkan tanggal video'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
|
||||||
<KeamananEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditVideo;
|
|
||||||
@@ -1,12 +1,29 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import JudulListTab from '../../../_com/jusulListTab';
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateGallery from '../../../_state/desa/gallery';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function Video() {
|
function Video() {
|
||||||
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
videoState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!videoState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -26,16 +43,20 @@ function Video() {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{videoState.findMany.data?.map((item) => (
|
||||||
<TableTd>Video 1</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>2022-01-01</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>Deskripsi Video 1</TableTd>
|
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/desa/gallery/video/detail')}>
|
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<IconDeviceImac size={20} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd>
|
||||||
</TableTd>
|
<Button onClick={() => router.push(`/admin/desa/gallery/video/${item.id}`)}>
|
||||||
</TableTr>
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
10
src/app/admin/(dashboard)/desa/layanan/layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
'use client'
|
||||||
|
import LayoutTabsLayanan from "../_com/layoutTabLayanan";
|
||||||
|
|
||||||
|
export default function Layout({children} : {children: React.ReactNode}) {
|
||||||
|
return (
|
||||||
|
<LayoutTabsLayanan>
|
||||||
|
{children}
|
||||||
|
</LayoutTabsLayanan>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import SuratKeterangan from './ui/surat_keterangan/page';
|
|
||||||
import PerizinanUsaha from './ui/perizinan_usaha/page';
|
|
||||||
import TelunjukSaktiDesa from './ui/telunjuk_sakti_desa/page';
|
|
||||||
import PendudukNonPermanent from './ui/penduduk_non_permanent/page';
|
|
||||||
|
|
||||||
function Page() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Stack >
|
|
||||||
<Title order={3}>Layanan</Title>
|
|
||||||
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Pelayanan Surat Keterangan"}>
|
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
|
||||||
<TabsTab value="Pelayanan Surat Keterangan">
|
|
||||||
Pelayanan Surat Keterangan
|
|
||||||
</TabsTab>
|
|
||||||
<TabsTab value="Pelayanan Perizinan Berusaha">
|
|
||||||
Pelayanan Perizinan Berusaha
|
|
||||||
</TabsTab>
|
|
||||||
<TabsTab value="Pelayanan Telunjuk Sakti Desa">
|
|
||||||
Pelayanan Telunjuk Sakti Desa
|
|
||||||
</TabsTab>
|
|
||||||
<TabsTab value="Pelayanan Penduduk Non-Permanent">
|
|
||||||
Pelayanan Penduduk Non-Permanent
|
|
||||||
</TabsTab>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsPanel value="Pelayanan Surat Keterangan">
|
|
||||||
<SuratKeterangan />
|
|
||||||
</TabsPanel>
|
|
||||||
<TabsPanel value="Pelayanan Perizinan Berusaha">
|
|
||||||
<PerizinanUsaha />
|
|
||||||
</TabsPanel>
|
|
||||||
<TabsPanel value="Pelayanan Telunjuk Sakti Desa">
|
|
||||||
<TelunjukSaktiDesa />
|
|
||||||
</TabsPanel>
|
|
||||||
<TabsPanel value="Pelayanan Penduduk Non-Permanent">
|
|
||||||
<PendudukNonPermanent />
|
|
||||||
</TabsPanel>
|
|
||||||
</Tabs>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Page;
|
|
||||||
|
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditPelayananPendudukNonPermanent() {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
const statePendudukNonPermanent = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: statePendudukNonPermanent.findById.data?.name || '',
|
||||||
|
deskripsi: statePendudukNonPermanent.findById.data?.deskripsi || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPelayananPerizinan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await statePendudukNonPermanent.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pelayanan perizinan berusaha:", error);
|
||||||
|
toast.error("Gagal memuat data pelayanan perizinan berusaha");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPelayananPerizinan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (statePendudukNonPermanent.findById.data) {
|
||||||
|
statePendudukNonPermanent.findById.data.name = formData.name;
|
||||||
|
statePendudukNonPermanent.findById.data.deskripsi = formData.deskripsi;
|
||||||
|
statePendudukNonPermanent.update.update(statePendudukNonPermanent.findById.data)
|
||||||
|
}
|
||||||
|
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent')
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={'xs'}>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant={'subtle'}
|
||||||
|
onClick={() => router.back()}
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap={'xs'}>
|
||||||
|
<Title order={3}>Edit Pelayanan Penduduk Non Permanent</Title>
|
||||||
|
<Text fw={"bold"}>Judul</Text>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
name: val.target.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text fw={"bold"}>Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
deskripsi: val,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
loading={statePendudukNonPermanent.update.loading}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPelayananPendudukNonPermanent;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
|
||||||
|
function SuratKeterangan() {
|
||||||
|
const router = useRouter()
|
||||||
|
const pelayananPendudukNonPermanen = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
pelayananPendudukNonPermanen.findById.load('1')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!pelayananPendudukNonPermanen.findById.data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton radius={10} h={800} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Box py={15}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
|
<Text fz={"h4"} fw={"bold"}>Preview Pelayanan Perizinan Berusaha</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
|
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent/edit')}>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>{pelayananPendudukNonPermanen.findById.data.name}</Text>
|
||||||
|
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }} dangerouslySetInnerHTML={{ __html: pelayananPendudukNonPermanen.findById.data.deskripsi }} />
|
||||||
|
</Paper>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SuratKeterangan;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditPelayananPerizinanBerusaha() {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
const statePerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: statePerizinanBerusaha.findById.data?.name || '',
|
||||||
|
deskripsi: statePerizinanBerusaha.findById.data?.deskripsi || '',
|
||||||
|
link: statePerizinanBerusaha.findById.data?.link || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPelayananPerizinan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await statePerizinanBerusaha.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
link: data.link || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pelayanan perizinan berusaha:", error);
|
||||||
|
toast.error("Gagal memuat data pelayanan perizinan berusaha");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPelayananPerizinan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (statePerizinanBerusaha.findById.data) {
|
||||||
|
statePerizinanBerusaha.findById.data.name = formData.name;
|
||||||
|
statePerizinanBerusaha.findById.data.deskripsi = formData.deskripsi;
|
||||||
|
statePerizinanBerusaha.findById.data.link = formData.link;
|
||||||
|
statePerizinanBerusaha.update.update(statePerizinanBerusaha.findById.data)
|
||||||
|
}
|
||||||
|
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha')
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={'xs'}>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant={'subtle'}
|
||||||
|
onClick={() => router.back()}
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap={'xs'}>
|
||||||
|
<Title order={3}>Edit Pelayanan Perizinan Berusaha</Title>
|
||||||
|
<Text fw={"bold"}>Judul</Text>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
name: val.target.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text fw={"bold"}>Link</Text>
|
||||||
|
<TextInput
|
||||||
|
value={formData.link}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
link: val.target.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text fw={"bold"}>Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
deskripsi: val,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
loading={statePerizinanBerusaha.update.loading}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPelayananPerizinanBerusaha;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Grid, GridCol, Group, Paper, Skeleton, Stack, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
|
||||||
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
|
function PerizinanBerusaha() {
|
||||||
|
const router = useRouter()
|
||||||
|
const pelayananPerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
|
||||||
|
const [active, setActive] = useState(1);
|
||||||
|
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
|
||||||
|
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
|
||||||
|
useShallowEffect(() => {
|
||||||
|
pelayananPerizinanBerusaha.findById.load('1')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if(!pelayananPerizinanBerusaha.findById.data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton radius={10} h={800} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Box py={15}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
|
<Text fz={"h4"} fw={"bold"}>Preview Pelayanan Perizinan Berusaha</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
|
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha/edit')}>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>{pelayananPerizinanBerusaha.findById.data.name}</Text>
|
||||||
|
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }} dangerouslySetInnerHTML={{__html: pelayananPerizinanBerusaha.findById.data.deskripsi}} />
|
||||||
|
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
|
||||||
|
<Box p={"xl"} w={{ base: "100%", md: "100%" }} >
|
||||||
|
<Stepper active={active} onStepClick={setActive} orientation="vertical"
|
||||||
|
styles={{
|
||||||
|
separator: {
|
||||||
|
marginLeft: 25
|
||||||
|
},
|
||||||
|
|
||||||
|
step: {
|
||||||
|
padding: '12px 0'
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
|
||||||
|
Pendaftaran akun pada portal OSS
|
||||||
|
</StepperStep>
|
||||||
|
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
|
||||||
|
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
|
||||||
|
</StepperStep>
|
||||||
|
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI ">
|
||||||
|
Memilih KBLI dengan jenis usaha yang akan didaftarkan
|
||||||
|
</StepperStep>
|
||||||
|
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
|
||||||
|
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
|
||||||
|
</StepperStep>
|
||||||
|
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
|
||||||
|
Proses verifikasi dan persetujuan oleh instansi terkait
|
||||||
|
</StepperStep>
|
||||||
|
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
|
||||||
|
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
|
||||||
|
</StepperStep>
|
||||||
|
<StepperCompleted >
|
||||||
|
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
|
||||||
|
</StepperCompleted>
|
||||||
|
</Stepper>
|
||||||
|
|
||||||
|
<Group justify="center" mt="xl">
|
||||||
|
<Button variant="default" onClick={prevStep}>Back</Button>
|
||||||
|
<Button onClick={nextStep}>Next step</Button>
|
||||||
|
</Group>
|
||||||
|
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
|
||||||
|
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
|
||||||
|
resmi OSS <a href={pelayananPerizinanBerusaha.findById.data.link}>{pelayananPerizinanBerusaha.findById.data.link}</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.</Text>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PerizinanBerusaha;
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditSuratKeterangan() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: stateSurat.edit.form.name,
|
||||||
|
deskripsi: stateSurat.edit.form.deskripsi,
|
||||||
|
imageId: stateSurat.edit.form.imageId,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadSurat = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await stateSurat.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
});
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading surat:", error);
|
||||||
|
toast.error("Gagal memuat data surat");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadSurat();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
stateSurat.edit.form = {
|
||||||
|
...stateSurat.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
stateSurat.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await stateSurat.edit.update()
|
||||||
|
toast.success("Surat berhasil diperbarui!")
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating surat:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui surat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Surat Keterangan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({ ...formData, name: val.target.value });
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
|
||||||
|
placeholder="masukkan nama surat keterangan"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditSuratKeterangan;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function DetailSuratKeterangan() {
|
||||||
|
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
suratKeteranganState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
suratKeteranganState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!suratKeteranganState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
{Array.from({ length: 10 }).map((_, k) => (
|
||||||
|
<Skeleton key={k} h={40} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Surat Keterangan</Text>
|
||||||
|
{suratKeteranganState.findUnique.data ? (
|
||||||
|
<Paper key={suratKeteranganState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
|
<Text fz={"lg"}>{suratKeteranganState.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: suratKeteranganState.findUnique.data?.deskripsi }}></Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (suratKeteranganState.findUnique.data) {
|
||||||
|
setSelectedId(suratKeteranganState.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={suratKeteranganState.delete.loading || !suratKeteranganState.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (suratKeteranganState.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/layanan/pelayanan_surat_keterangan/${suratKeteranganState.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!suratKeteranganState.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailSuratKeterangan;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function CreateSuratKeterangan() {
|
||||||
|
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateSurat.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
}
|
||||||
|
setPreviewImage(null)
|
||||||
|
setFile(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.error("Silahkan pilih file gambar terlebih dahulu")
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file: file,
|
||||||
|
name: file.name
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploaded = res.data?.data
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar")
|
||||||
|
}
|
||||||
|
|
||||||
|
stateSurat.create.form.imageId = uploaded.id
|
||||||
|
|
||||||
|
await stateSurat.create.create()
|
||||||
|
resetForm()
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
|
||||||
|
|
||||||
|
}
|
||||||
|
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"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Create Surat Keterangan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={stateSurat.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateSurat.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
|
||||||
|
placeholder="masukkan nama surat keterangan"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateSurat.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
stateSurat.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateSuratKeterangan;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
'use client'
|
||||||
|
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
function SuratKeterangan() {
|
||||||
|
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
suratKeteranganState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!suratKeteranganState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Surat Keterangan'
|
||||||
|
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{suratKeteranganState.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={item.image?.link} alt="gambar" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_surat_keterangan/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SuratKeterangan;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditPelayananTelunjukSakti() {
|
||||||
|
const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: stateTelunjukDesa.edit.form.name,
|
||||||
|
deskripsi: stateTelunjukDesa.edit.form.deskripsi,
|
||||||
|
link: stateTelunjukDesa.edit.form.link,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPelayananTelunjukSakti = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await stateTelunjukDesa.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
link: data.link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pelayanan telunjuk sakti:", error);
|
||||||
|
toast.error("Gagal memuat data pelayanan telunjuk sakti");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPelayananTelunjukSakti();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
stateTelunjukDesa.edit.form = {
|
||||||
|
...stateTelunjukDesa.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
link: formData.link,
|
||||||
|
}
|
||||||
|
await stateTelunjukDesa.edit.update()
|
||||||
|
toast.success("Pelayanan telunjuk sakti berhasil diperbarui!")
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating pelayanan telunjuk sakti:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui pelayanan telunjuk sakti");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Surat Keterangan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({ ...formData, name: val.target.value });
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
|
||||||
|
placeholder="masukkan nama surat keterangan"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.link}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({ ...formData, link: val.target.value });
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
|
||||||
|
placeholder="masukkan link"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPelayananTelunjukSakti;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
'use client'
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function DetailPelayananTelunjukSakti() {
|
||||||
|
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
telunjukSaktiState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
telunjukSaktiState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!telunjukSaktiState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
{Array.from({ length: 10 }).map((_, k) => (
|
||||||
|
<Skeleton key={k} h={40} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Pelayanan Telunjuk Sakti Desa</Text>
|
||||||
|
{telunjukSaktiState.findUnique.data ? (
|
||||||
|
<Paper key={telunjukSaktiState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Nama</Text>
|
||||||
|
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Link</Text>
|
||||||
|
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.link}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: telunjukSaktiState.findUnique.data?.deskripsi }}></Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (telunjukSaktiState.findUnique.data) {
|
||||||
|
setSelectedId(telunjukSaktiState.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={telunjukSaktiState.delete.loading || !telunjukSaktiState.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (telunjukSaktiState.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${telunjukSaktiState.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!telunjukSaktiState.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPelayananTelunjukSakti;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function CreatePelayananTelunjukDesa() {
|
||||||
|
const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateTelunjukDesa.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
link: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await stateTelunjukDesa.create.create()
|
||||||
|
resetForm()
|
||||||
|
router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa")
|
||||||
|
|
||||||
|
}
|
||||||
|
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"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Create Pelayanan Telunjuk Sakti Desa</Title>
|
||||||
|
<TextInput
|
||||||
|
value={stateTelunjukDesa.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateTelunjukDesa.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Pelayanan Telunjuk Sakti Desa</Text>}
|
||||||
|
placeholder="masukkan nama pelayanan telunjuk sakti desa"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={stateTelunjukDesa.create.form.link}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateTelunjukDesa.create.form.link = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
|
||||||
|
placeholder="masukkan link"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateTelunjukDesa.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
stateTelunjukDesa.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreatePelayananTelunjukDesa;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
'use client'
|
||||||
|
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import stateLayananDesa from '../../../_state/desa/layananDesa';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
function PelayananTelunjukSakti() {
|
||||||
|
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
telunjukSaktiState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!telunjukSaktiState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Pelayanan Telunjuk Sakti Desa'
|
||||||
|
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{telunjukSaktiState.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd><Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /></TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PelayananTelunjukSakti;
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListPendudukNonPermanent() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>List Penduduk Non-Permanent</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListPendudukNonPermanent;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
import { DesaEditor } from '../../../_com/desaEditor';
|
|
||||||
import ListPendudukNonPermanent from './listPage';
|
|
||||||
|
|
||||||
function PendudukNonPermanent() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Penduduk Non-Permanent</Title>
|
|
||||||
<Text fw={"bold"}>Deskripsi Penduduk Non-Permanent</Text>
|
|
||||||
<DesaEditor showSubmit={false} />
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListPendudukNonPermanent />
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PendudukNonPermanent;
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListPerizinanUsaha() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>List Perizinan Usaha</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListPerizinanUsaha;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
import ListPerizinanUsaha from './listPage';
|
|
||||||
import { DesaEditor } from '../../../_com/desaEditor';
|
|
||||||
|
|
||||||
function PerizinanUsaha() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Pelayanan Perizinan Usaha</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi Perizinan Usaha</Text>
|
|
||||||
<DesaEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListPerizinanUsaha />
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PerizinanUsaha;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListSuratKeterangan() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>List Surat Keterangan</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListSuratKeterangan;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, SimpleGrid, Paper, Stack, Title, Button, Group, TextInput, Text, Center, Flex } from '@mantine/core';
|
|
||||||
import { IconUpload } from '@tabler/icons-react';
|
|
||||||
import React from 'react';
|
|
||||||
import ListSuratKeterangan from './listPage';
|
|
||||||
|
|
||||||
function SuratKeterangan() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Pelayanan Surat Keterangan</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
|
|
||||||
placeholder='masukkan nama surat keterangan'
|
|
||||||
/>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Upload Gambar Surat Keterangan</Text>
|
|
||||||
<Box bg={colors['BG-trans']} p={"md"}>
|
|
||||||
<Center>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Center>
|
|
||||||
</Box>
|
|
||||||
<Flex>
|
|
||||||
<Text fz={"xs"} c={"red"}>*</Text>
|
|
||||||
<Text fz={"xs"} c={"dimmed"} fw={"bold"}>Upload foto untuk konten surat keterangan</Text>
|
|
||||||
</Flex>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListSuratKeterangan />
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SuratKeterangan;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListTelunjukSaktiDesa() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>List Telunjuk Sakti Desa</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListTelunjukSaktiDesa;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
import { DesaEditor } from '../../../_com/desaEditor';
|
|
||||||
import ListTelunjukSaktiDesa from './listPage';
|
|
||||||
|
|
||||||
function TelunjukSaktiDesa() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Telunjuk Sakti Desa</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi Telunjuk Sakti Desa</Text>
|
|
||||||
<DesaEditor showSubmit={false} />
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListTelunjukSaktiDesa />
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TelunjukSaktiDesa;
|
|
||||||
147
src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditPenghargaan() {
|
||||||
|
const statePenghargaan = useProxy(penghargaanState)
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null)
|
||||||
|
const [file, setFile] = useState<File | null>(null)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: statePenghargaan.findUnique.data?.name || '',
|
||||||
|
juara: statePenghargaan.findUnique.data?.juara || '',
|
||||||
|
deskripsi: statePenghargaan.findUnique.data?.deskripsi || '',
|
||||||
|
imageId: statePenghargaan.findUnique.data?.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPenghargaan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await statePenghargaan.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
juara: data.juara || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading penghargaan:", error);
|
||||||
|
toast.error("Gagal memuat data penghargaan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPenghargaan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
statePenghargaan.edit.form = {
|
||||||
|
...statePenghargaan.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
juara: formData.juara,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
statePenghargaan.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await statePenghargaan.edit.update();
|
||||||
|
toast.success("Penghargaan berhasil diperbarui!");
|
||||||
|
router.push("/admin/desa/penghargaan");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating penghargaan:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui penghargaan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Penghargaan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.juara}
|
||||||
|
onChange={(e) => setFormData({ ...formData, juara: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
||||||
|
placeholder="masukkan juara"
|
||||||
|
/>
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
|
||||||
|
statePenghargaan.edit.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit}>Edit Penghargaan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPenghargaan;
|
||||||
111
src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import penghargaanState from '../../../_state/desa/penghargaan';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailPenghargaan() {
|
||||||
|
const statePenghargaan = useProxy(penghargaanState)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePenghargaan.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
statePenghargaan.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/penghargaan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statePenghargaan.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Penghargaan</Text>
|
||||||
|
{statePenghargaan.findUnique.data ? (
|
||||||
|
<Paper key={statePenghargaan.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{statePenghargaan.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Juara</Text>
|
||||||
|
<Text fz={"lg"}>{statePenghargaan.findUnique.data?.juara}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: statePenghargaan.findUnique.data?.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 150, lg: 150 }} src={statePenghargaan.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (statePenghargaan.findUnique.data) {
|
||||||
|
setSelectedId(statePenghargaan.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={statePenghargaan.delete.loading || !statePenghargaan.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (statePenghargaan.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/penghargaan/${statePenghargaan.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!statePenghargaan.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus penghargaan ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPenghargaan;
|
||||||
114
src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import penghargaanState from '../../../_state/desa/penghargaan';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
|
||||||
|
|
||||||
|
function CreatePenghargaan() {
|
||||||
|
const statePenghargaan = useProxy(penghargaanState)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
statePenghargaan.create.form = {
|
||||||
|
name: "",
|
||||||
|
juara: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
}
|
||||||
|
setPreviewImage(null)
|
||||||
|
setFile(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.error("Silahkan pilih file gambar terlebih dahulu")
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file: file,
|
||||||
|
name: file.name
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploaded = res.data?.data
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar")
|
||||||
|
}
|
||||||
|
|
||||||
|
statePenghargaan.create.form.imageId = uploaded.id
|
||||||
|
|
||||||
|
await statePenghargaan.create.create()
|
||||||
|
resetForm()
|
||||||
|
router.push("/admin/desa/penghargaan")
|
||||||
|
|
||||||
|
}
|
||||||
|
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"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Create Penghargaan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={statePenghargaan.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
statePenghargaan.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Penghargaan</Text>}
|
||||||
|
placeholder="masukkan nama penghargaan"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={statePenghargaan.create.form.juara}
|
||||||
|
onChange={(val) => {
|
||||||
|
statePenghargaan.create.form.juara = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
||||||
|
placeholder="masukkan juara"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={statePenghargaan.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
statePenghargaan.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreatePenghargaan;
|
||||||
@@ -1,34 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import React from 'react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import Penghargaan from './ui/penghargaan/page';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import GambarPerhargaan from './ui/gambar_perhargaan/page';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import JudulListTab from '../../_com/jusulListTab';
|
||||||
|
|
||||||
function Page() {
|
function Penghargaan() {
|
||||||
|
const state = useProxy(penghargaanState)
|
||||||
|
const router = useRouter()
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!state.findMany.data) {
|
||||||
|
return(
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Stack>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title order={3}>Penghargaan</Title>
|
<JudulListTab
|
||||||
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Penghargaan"}>
|
title='List Penghargaan'
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
href='/admin/desa/penghargaan/create'
|
||||||
<TabsTab value="Penghargaan">
|
placeholder='pencarian'
|
||||||
Penghargaan
|
searchIcon={<IconSearch size={16} />}
|
||||||
</TabsTab>
|
/>
|
||||||
<TabsTab value="Gambar Penghargaan">
|
<Table striped withTableBorder withRowBorders>
|
||||||
Gambar Penghargaan
|
<TableThead>
|
||||||
</TabsTab>
|
<TableTr>
|
||||||
</TabsList>
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TabsPanel value="Penghargaan">
|
<TableTh>Image</TableTh>
|
||||||
<Penghargaan/>
|
<TableTh>Detail</TableTh>
|
||||||
</TabsPanel>
|
</TableTr>
|
||||||
<TabsPanel value="Gambar Penghargaan">
|
</TableThead>
|
||||||
<GambarPerhargaan/>
|
<TableTbody>
|
||||||
</TabsPanel>
|
{state.findMany.data?.map((item) => (
|
||||||
</Tabs>
|
<TableTr key={item.id}>
|
||||||
</Stack>
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={item.image?.link} alt="gambar" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/penghargaan/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Penghargaan;
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListGambarPenghargaan() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>List Gambar Penghargaan</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListGambarPenghargaan;
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, SimpleGrid, Stack, Title, Text, Group, Button, TextInput, Center } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
import { DesaEditor } from '../../../_com/desaEditor';
|
|
||||||
import ListGambarPenghargaan from './listPage';
|
|
||||||
import { IconUpload } from '@tabler/icons-react';
|
|
||||||
|
|
||||||
|
|
||||||
function GambarPerhargaan() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Tambah Gambar Penghargaan</Title>
|
|
||||||
<TextInput
|
|
||||||
label="Judul Gambar Penghargaan"
|
|
||||||
placeholder='masukkan judul gambar penghargaan'
|
|
||||||
/>
|
|
||||||
<Text fw={"bold"}>Deskripsi Gambar Penghargaan</Text>
|
|
||||||
<DesaEditor showSubmit={false} />
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"}>Upload Gambar Penghargaan</Text>
|
|
||||||
<Box bg={colors['BG-trans']} p={"md"}>
|
|
||||||
<Center>
|
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
||||||
</Center>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListGambarPenghargaan />
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GambarPerhargaan;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ListPenghargaan() {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>List Penghargaan</Title>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListPenghargaan;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
|
||||||
import React from 'react';
|
|
||||||
import { DesaEditor } from '../../../_com/desaEditor';
|
|
||||||
import ListPenghargaan from './listPage';
|
|
||||||
|
|
||||||
function Penghargaan() {
|
|
||||||
return (
|
|
||||||
<Box py={10}>
|
|
||||||
<Stack>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Penghargaan</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi Penghargaan</Text>
|
|
||||||
<DesaEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<ListPenghargaan/>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Penghargaan;
|
|
||||||
@@ -1,11 +1,70 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
|
||||||
function Page() {
|
function BeasiswaDesa() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
beasiswa-desa
|
<HeaderSearch
|
||||||
</div>
|
title='Beasiswa Desa'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListBeasiswaDesa/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListBeasiswaDesa() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<Title order={4}>List Beasiswa Desa</Title>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/beasiswa-desa/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BeasiswaDesa;
|
||||||
|
|||||||
@@ -1,11 +1,74 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
|
||||||
function Page() {
|
function BimbinganBelajarDesa() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
bimbingan-belajar-desa
|
<HeaderSearch
|
||||||
</div>
|
title='Bimbingan Belajar Desa'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListBimbinganBelajarDesa/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListBimbinganBelajarDesa() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Bimbingan Belajar Desa'
|
||||||
|
href='/admin/pendidikan/bimbingan-belajar-desa/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/bimbingan-belajar-desa/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BimbinganBelajarDesa;
|
||||||
|
|||||||
@@ -1,11 +1,74 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
|
||||||
function Page() {
|
function DataPendidikan() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
data-pendidikan
|
<HeaderSearch
|
||||||
</div>
|
title='Data Pendidikan'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListDataPendidikan/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListDataPendidikan() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Data Pendidikan'
|
||||||
|
href='/admin/pendidikan/data-pendidikan/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/data-pendidikan/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataPendidikan;
|
||||||
|
|||||||
@@ -1,11 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
function Page() {
|
function InfoSekolahPaud() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
info-sekolah-paud
|
<HeaderSearch
|
||||||
</div>
|
title='Info Sekolah & PAUD'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListInfoSekolahPaud/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListInfoSekolahPaud() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Info Sekolah & PAUD'
|
||||||
|
href='/admin/pendidikan/info-sekolah-paud/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Sekolah PAUD</TableTh>
|
||||||
|
<TableTh>Gambar</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>Sekolah PAUD</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={"/"} alt="image" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>Deskripsi</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/info-sekolah-paud/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfoSekolahPaud;
|
||||||
|
|||||||
@@ -1,11 +1,74 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
|
||||||
function Page() {
|
function PendidikanNonFormal() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
pendidikan-non-formal
|
<HeaderSearch
|
||||||
</div>
|
title='Pendidikan Non Formal'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListPendidikanNonFormal/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListPendidikanNonFormal() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Pendidikan Non Formal'
|
||||||
|
href='/admin/pendidikan/pendidikan-non-formal/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/pendidikan-non-formal/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PendidikanNonFormal;
|
||||||
|
|||||||
@@ -1,11 +1,74 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
|
||||||
function Page() {
|
function PerpustakaanDigital() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
perpustakaan-digital
|
<HeaderSearch
|
||||||
</div>
|
title='Perpustakaan Digital'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListPerpustakaanDigital/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListPerpustakaanDigital() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Perpustakaan Digital'
|
||||||
|
href='/admin/pendidikan/perpustakaan-digital/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/perpustakaan-digital/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PerpustakaanDigital;
|
||||||
|
|||||||
@@ -1,11 +1,74 @@
|
|||||||
import React from 'react';
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
|
||||||
function Page() {
|
function ProgramPendidikanAnak() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
program-pendidikan-anak
|
<HeaderSearch
|
||||||
</div>
|
title='Program Pendidikan Anak'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListProgramPendidikanAnak/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListProgramPendidikanAnak() {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p="md">
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Program Pendidikan Anak'
|
||||||
|
href='/admin/pendidikan/program-pendidikan-anak/create'
|
||||||
|
/>
|
||||||
|
<Box style={{overflowX: 'auto'}}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nomor</TableTh>
|
||||||
|
<TableTh>Nama Lengkap</TableTh>
|
||||||
|
<TableTh>Nomor Telepon</TableTh>
|
||||||
|
<TableTh>Email</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
<TableTr>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>1</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate="end" fz={"sm"}>Email</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push('/admin/pendidikan/program-pendidikan-anak/detail')}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProgramPendidikanAnak;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function PPIDTextEditor({ onSubmit, onChange, showSubmit = true, initialC
|
|||||||
}, [initialContent, editor]);
|
}, [initialContent, editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack >
|
||||||
<RichTextEditor editor={editor}>
|
<RichTextEditor editor={editor}>
|
||||||
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
||||||
<RichTextEditor.ControlsGroup>
|
<RichTextEditor.ControlsGroup>
|
||||||
|
|||||||
72
src/app/admin/(dashboard)/ppid/_com/layoutTabs.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Grafik Hasil Kepuasan Masyarakat",
|
||||||
|
value: "grafikhasilkepuasamanmasyarakat",
|
||||||
|
href: "/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grafik Berdasarkan Jenis Kelamin Responden",
|
||||||
|
value: "grafikberdasarkanjeniskelaminresponden",
|
||||||
|
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grafik Berdasarkan Pilihan Responden",
|
||||||
|
value: "grafikberdasarkanpilihanresponden",
|
||||||
|
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Grafik Berdasarkan Umur Responden",
|
||||||
|
value: "grafikberdasarkanumurresponden",
|
||||||
|
href: "/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find(t => t.value === value)
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href)
|
||||||
|
}
|
||||||
|
setActiveTab(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
|
if (match) {
|
||||||
|
setActiveTab(match.value)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Indeks Kepuasan Masyarakat (IKM) Desa Darmasaba</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 */}
|
||||||
|
<></>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutTabs;
|
||||||
63
src/app/admin/(dashboard)/ppid/_com/layoutTabsGallery.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Foto",
|
||||||
|
value: "foto",
|
||||||
|
href: "/admin/desa/gallery/foto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Video",
|
||||||
|
value: "video",
|
||||||
|
href: "/admin/desa/gallery/video"
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
|
const handleTabChange = (value: string | null) => {
|
||||||
|
const tab = tabs.find(t => t.value === value)
|
||||||
|
if (tab) {
|
||||||
|
router.push(tab.href)
|
||||||
|
}
|
||||||
|
setActiveTab(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = tabs.find(tab => tab.href === pathname)
|
||||||
|
if (match) {
|
||||||
|
setActiveTab(match.value)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Gallery</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 */}
|
||||||
|
<></>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutTabsGallery;
|
||||||
@@ -38,6 +38,7 @@ function EditDasarHukum() {
|
|||||||
dasarHukumState.findById.data.content = content;
|
dasarHukumState.findById.data.content = content;
|
||||||
dasarHukumState.update.save(dasarHukumState.findById.data)
|
dasarHukumState.update.save(dasarHukumState.findById.data)
|
||||||
}
|
}
|
||||||
|
router.push('/admin/ppid/dasar-hukum')
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -34,15 +34,13 @@ function Page() {
|
|||||||
</Button>
|
</Button>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box>
|
||||||
<Stack gap={'lg'}>
|
<Stack gap={'lg'}>
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||||
<Box px={{ base: 20, md: 50 }} pb={30}>
|
<Box px={{ base: 0, md: 30 }}>
|
||||||
<Text ta={"center"} fz={{ base: "h3", md: "h2" }} fw={"bold"}>
|
<Text ta={"center"} fz={{ base: "h3", md: "h2" }} fw={"bold"} dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }} />
|
||||||
{listDasarHukum.findById.data.judul}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box px={{ base: 20, md: 50 }}>
|
<Box px={{ base: 0, md: 30 }}>
|
||||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.content }} />
|
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.content }} />
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
'use client'
|
|
||||||
import stateGrafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Center, Flex, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
function GrafikBerdasarkanJenisKelamin() {
|
|
||||||
const grafikBerdasarkanJenisKelamin = useProxy(stateGrafikBerdasarkanJenisKelamin.grafikBerdasarkanJenisKelamin)
|
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const updateChartData = (data: any) => {
|
|
||||||
if (data && data.length > 0) {
|
|
||||||
const totalLaki = data.reduce((acc: number, cur: any) => acc + Number(cur.laki || 0), 0);
|
|
||||||
const totalPerempuan = data.reduce((acc: number, cur: any) => acc + Number(cur.perempuan || 0), 0);
|
|
||||||
|
|
||||||
setDonutData([
|
|
||||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'], key: 'laki-laki' },
|
|
||||||
{ name: 'Perempuan', value: totalPerempuan, color: '#FF6384', key: 'perempuan' }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
|
||||||
if (grafikBerdasarkanJenisKelamin.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanJenisKelamin.findMany.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
// Simpan data baru
|
|
||||||
await grafikBerdasarkanJenisKelamin.create.create();
|
|
||||||
|
|
||||||
// Muat ulang data
|
|
||||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
|
||||||
|
|
||||||
// Update chart dengan data baru
|
|
||||||
if (grafikBerdasarkanJenisKelamin.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanJenisKelamin.findMany.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset form setelah submit
|
|
||||||
grafikBerdasarkanJenisKelamin.create.form.laki = '';
|
|
||||||
grafikBerdasarkanJenisKelamin.create.form.perempuan = '';
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting data:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box py={15}>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Laki-laki"
|
|
||||||
placeholder="masukkan jumlah laki-laki"
|
|
||||||
value={grafikBerdasarkanJenisKelamin.create.form.laki}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanJenisKelamin.create.form.laki = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Perempuan"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah perempuan"
|
|
||||||
value={grafikBerdasarkanJenisKelamin.create.form.perempuan}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanJenisKelamin.create.form.perempuan = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title pb={10} order={3}>Grafik Berdasarkan Jenis Kelamin Responden</Title>
|
|
||||||
{mounted && donutData.length > 0 && (
|
|
||||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
|
||||||
<Center>
|
|
||||||
<PieChart
|
|
||||||
width={1000} height={400}
|
|
||||||
data={donutData}
|
|
||||||
>
|
|
||||||
|
|
||||||
<Pie
|
|
||||||
dataKey="value"
|
|
||||||
nameKey="name"
|
|
||||||
data={donutData}
|
|
||||||
innerRadius={120}
|
|
||||||
outerRadius={230}
|
|
||||||
label={true}
|
|
||||||
>
|
|
||||||
{donutData.map((entry, index) => (
|
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
||||||
))}
|
|
||||||
</Pie>
|
|
||||||
</PieChart>
|
|
||||||
</Center>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#FF6384'} w={20} h={20} />
|
|
||||||
<Text>Perempuan: {donutData.find((entry) => entry.name === 'Perempuan')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
|
||||||
<Text>Laki-laki: {donutData.find((entry) => entry.name === 'Laki-laki')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GrafikBerdasarkanJenisKelamin;
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import stateGrafikResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Center, Flex, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { PieChart, Pie, Cell } from 'recharts';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
function GrafikBerdasarkanResponden() {
|
|
||||||
const grafikBerdasarkanResponden = useProxy(stateGrafikResponden.grafikBerdasarkanResponden)
|
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const updateChartData = (data: any) => {
|
|
||||||
if (data && data.length > 0) {
|
|
||||||
const totalSangatBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
|
||||||
const totalBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
|
||||||
const totalKurangBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
|
||||||
const totalTidakBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.tidakbaik || 0), 0);
|
|
||||||
setDonutData([
|
|
||||||
{ name: 'sangatbaik', value: totalSangatBaik, color: colors['blue-button'], key: 'sangatbaik' },
|
|
||||||
{ name: 'baik', value: totalBaik, color: '#10A85AFF', key: 'baik' },
|
|
||||||
{ name: 'kurangbaik', value: totalKurangBaik, color: '#B3AA12FF', key: 'kurangbaik' },
|
|
||||||
{ name: 'tidakbaik', value: totalTidakBaik, color: '#B21313FF', key: 'tidakbaik' }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
await grafikBerdasarkanResponden.findMany.load();
|
|
||||||
if (grafikBerdasarkanResponden.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanResponden.findMany.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
// Simpan data baru
|
|
||||||
await grafikBerdasarkanResponden.create.create();
|
|
||||||
|
|
||||||
// Muat ulang data
|
|
||||||
await grafikBerdasarkanResponden.findMany.load();
|
|
||||||
|
|
||||||
// Update chart dengan data baru
|
|
||||||
if (grafikBerdasarkanResponden.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanResponden.findMany.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset form setelah submit
|
|
||||||
grafikBerdasarkanResponden.create.form.sangatbaik = '';
|
|
||||||
grafikBerdasarkanResponden.create.form.baik = '';
|
|
||||||
grafikBerdasarkanResponden.create.form.kurangbaik = '';
|
|
||||||
grafikBerdasarkanResponden.create.form.tidakbaik = '';
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting data:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box py={15}>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Grafik Berdasarkan Responden</Title>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Sangat Baik"
|
|
||||||
value={grafikBerdasarkanResponden.create.form.sangatbaik}
|
|
||||||
placeholder="masukkan jumlah respon sangat baik"
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanResponden.create.form.sangatbaik = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Baik"
|
|
||||||
value={grafikBerdasarkanResponden.create.form.baik}
|
|
||||||
placeholder="masukkan jumlah respon baik"
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanResponden.create.form.baik = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Kurang Baik"
|
|
||||||
value={grafikBerdasarkanResponden.create.form.kurangbaik}
|
|
||||||
placeholder="masukkan jumlah respon kurang baik"
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanResponden.create.form.kurangbaik = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Tidak Baik"
|
|
||||||
value={grafikBerdasarkanResponden.create.form.tidakbaik}
|
|
||||||
placeholder="masukkan jumlah respon tidak baik"
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanResponden.create.form.tidakbaik = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
|
||||||
{mounted && donutData.length > 0 && (
|
|
||||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
|
||||||
<Center>
|
|
||||||
<PieChart
|
|
||||||
width={1000} height={400}
|
|
||||||
data={donutData}
|
|
||||||
>
|
|
||||||
<Pie
|
|
||||||
dataKey="value"
|
|
||||||
nameKey="name"
|
|
||||||
data={donutData}
|
|
||||||
innerRadius={120}
|
|
||||||
outerRadius={230}
|
|
||||||
label={true}
|
|
||||||
>
|
|
||||||
{donutData.map((entry, index) => (
|
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
||||||
))}
|
|
||||||
</Pie>
|
|
||||||
</PieChart>
|
|
||||||
</Center>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
|
||||||
<Text>Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
|
||||||
<Text>Baik: {donutData.find((entry) => entry.name === 'baik')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#B3AA12FF'} w={20} h={20} />
|
|
||||||
<Text>Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#B21313FF'} w={20} h={20} />
|
|
||||||
<Text>Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GrafikBerdasarkanResponden;
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Center, Flex, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import stateGrafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
|
||||||
import { PieChart, Pie, Cell } from 'recharts';
|
|
||||||
|
|
||||||
function GrafikBerdasarakanUmur() {
|
|
||||||
const grafikBerdasarkanUmur = useProxy(stateGrafikBerdasarkanUmur.grafikBerdasarkanUmur)
|
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateChartData = (data: any) => {
|
|
||||||
if (data && data.length > 0) {
|
|
||||||
const totalRemaja = data.reduce((acc: number, cur: any) => acc + Number(cur.remaja || 0), 0);
|
|
||||||
const totalDewasa = data.reduce((acc: number, cur: any) => acc + Number(cur.dewasa || 0), 0);
|
|
||||||
const totalOrangtua = data.reduce((acc: number, cur: any) => acc + Number(cur.orangtua || 0), 0);
|
|
||||||
const totalLansia = data.reduce((acc: number, cur: any) => acc + Number(cur.lansia || 0), 0);
|
|
||||||
|
|
||||||
setDonutData([
|
|
||||||
{ name: 'Remaja', value: totalRemaja, color: colors['blue-button'], key: 'remaja' },
|
|
||||||
{ name: 'Dewasa', value: totalDewasa, color: '#D32711FF', key: 'dewasa' },
|
|
||||||
{ name: 'Orangtua', value: totalOrangtua, color: '#B46B04FF', key: 'orangtua' },
|
|
||||||
{ name: 'Lansia', value: totalLansia, color: '#038617FF', key: 'lansia' }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
await grafikBerdasarkanUmur.findMany.load();
|
|
||||||
if (grafikBerdasarkanUmur.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanUmur.findMany.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
await grafikBerdasarkanUmur.create.create();
|
|
||||||
await grafikBerdasarkanUmur.findMany.load();
|
|
||||||
if (grafikBerdasarkanUmur.findMany.data) {
|
|
||||||
updateChartData(grafikBerdasarkanUmur.findMany.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting data:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box py={15}>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Grafik Berdasarkan Umur Responden</Title>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Remaja"
|
|
||||||
placeholder="masukkan jumlah responden remaja"
|
|
||||||
value={grafikBerdasarkanUmur.create.form.remaja}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanUmur.create.form.remaja = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Dewasa"
|
|
||||||
placeholder="masukkan jumlah responden dewasa"
|
|
||||||
value={grafikBerdasarkanUmur.create.form.dewasa}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanUmur.create.form.dewasa = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Orangtua"
|
|
||||||
placeholder="masukkan jumlah responden orangtua"
|
|
||||||
value={grafikBerdasarkanUmur.create.form.orangtua}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanUmur.create.form.orangtua = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
w={{ base: '100%', md: '50%' }}
|
|
||||||
label="Lansia"
|
|
||||||
placeholder="masukkan jumlah responden lansia"
|
|
||||||
value={grafikBerdasarkanUmur.create.form.lansia}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikBerdasarkanUmur.create.form.lansia = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title pb={10} order={3}>Grafik Berdasarkan Umur Responden</Title>
|
|
||||||
{mounted && donutData.length > 0 && (
|
|
||||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
|
||||||
<Center>
|
|
||||||
<PieChart
|
|
||||||
width={1000} height={400}
|
|
||||||
data={donutData}
|
|
||||||
>
|
|
||||||
<Pie
|
|
||||||
dataKey="value"
|
|
||||||
nameKey="name"
|
|
||||||
data={donutData}
|
|
||||||
innerRadius={120}
|
|
||||||
outerRadius={230}
|
|
||||||
label={true}
|
|
||||||
>
|
|
||||||
{donutData.map((entry, index) => (
|
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
||||||
))}
|
|
||||||
</Pie>
|
|
||||||
</PieChart>
|
|
||||||
</Center>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
|
||||||
<Text>17 - 25 tahun: {donutData.find((entry) => entry.name === 'remaja')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#D32711FF'} w={20} h={20} />
|
|
||||||
<Text>26 - 45 tahun: {donutData.find((entry) => entry.name === 'dewasa')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#B46B04FF'} w={20} h={20} />
|
|
||||||
<Text>46 - 60 tahun: {donutData.find((entry) => entry.name === 'orangtua')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#038617FF'} w={20} h={20} />
|
|
||||||
<Text>di atas 60 tahun: {donutData.find((entry) => entry.name === 'lansia')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GrafikBerdasarakanUmur;
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
'use client'
|
|
||||||
import stateGrafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
function GrafikHasilKepuasan() {
|
|
||||||
const grafikHasilKepuasan = useProxy(stateGrafikHasilKepuasanMasyarakat.grafikHasilKepuasanMasyarakat)
|
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
await grafikHasilKepuasan.findMany.load();
|
|
||||||
if (grafikHasilKepuasan.findMany.data && grafikHasilKepuasan.findMany.data.length > 0) {
|
|
||||||
setChartData(grafikHasilKepuasan.findMany.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box py={15}>
|
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
|
||||||
<TextInput
|
|
||||||
label="Label"
|
|
||||||
placeholder="masukkan label"
|
|
||||||
value={grafikHasilKepuasan.create.form.label}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikHasilKepuasan.create.form.label = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Jumlah Kepuasan"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah kepuasan"
|
|
||||||
value={grafikHasilKepuasan.create.form.kepuasan}
|
|
||||||
onChange={(val) => {
|
|
||||||
grafikHasilKepuasan.create.form.kepuasan = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={async () => {
|
|
||||||
await grafikHasilKepuasan.create.create();
|
|
||||||
await grafikHasilKepuasan.findMany.load();
|
|
||||||
if (grafikHasilKepuasan.findMany.data) {
|
|
||||||
setChartData(grafikHasilKepuasan.findMany.data);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Chart */}
|
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
|
||||||
<Paper style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
|
|
||||||
{mounted && chartData.length > 0 && (
|
|
||||||
<BarChart width={isMobile ? 300 : isTablet ? 600 : 900} height={380} data={chartData} >
|
|
||||||
<XAxis dataKey="label" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
|
||||||
</BarChart>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GrafikHasilKepuasan;
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
function EditGrafikBerdasarkanJenisKelaminResponden() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams() as { id: string }
|
||||||
|
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||||
|
const id = params.id
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(id){
|
||||||
|
stategrafikBerdasarkanJenisKelamin.findUnique.load(id).then(() => {
|
||||||
|
const data = stategrafikBerdasarkanJenisKelamin.findUnique.data
|
||||||
|
if(data){
|
||||||
|
stategrafikBerdasarkanJenisKelamin.update.form = {
|
||||||
|
laki: data.laki || '',
|
||||||
|
perempuan: data.perempuan || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.update.id = id;
|
||||||
|
await stategrafikBerdasarkanJenisKelamin.update.submit();
|
||||||
|
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Laki-laki"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanJenisKelamin.update.form.laki}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.update.form.laki = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Perempuan"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanJenisKelamin.update.form.perempuan}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.update.form.perempuan = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGrafikBerdasarkanJenisKelaminResponden;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
function GrafikBerdasarkanJenisKelaminRespondenCreate() {
|
||||||
|
const router = useRouter();
|
||||||
|
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.create.form = {
|
||||||
|
...stategrafikBerdasarkanJenisKelamin.create.form,
|
||||||
|
laki: "",
|
||||||
|
perempuan: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const id = await stategrafikBerdasarkanJenisKelamin.create.create();
|
||||||
|
if (id) {
|
||||||
|
const idStr = String(id);
|
||||||
|
await stategrafikBerdasarkanJenisKelamin.findUnique.load(idStr);
|
||||||
|
if (stategrafikBerdasarkanJenisKelamin.findUnique.data) {
|
||||||
|
setDonutData([stategrafikBerdasarkanJenisKelamin.findUnique.data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden")
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Laki-laki"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanJenisKelamin.create.form.laki}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.create.form.laki = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Perempuan"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanJenisKelamin.create.form.perempuan}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanJenisKelamin.create.form.perempuan = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarkanJenisKelaminRespondenCreate;
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
|
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function GrafikBerdasarkanJenisKelamin() {
|
||||||
|
const stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
await grafikBerdasarkanJenisKelamin.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
|
||||||
|
stategrafikBerdasarkanJenisKelamin.findMany.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
stategrafikBerdasarkanJenisKelamin.findMany.load()
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stategrafikBerdasarkanJenisKelamin.findMany.data) {
|
||||||
|
const totalLaki = stategrafikBerdasarkanJenisKelamin.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.laki || 0), 0);
|
||||||
|
const totalPerempuan = stategrafikBerdasarkanJenisKelamin.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.perempuan || 0), 0);
|
||||||
|
setDonutData([
|
||||||
|
{ name: 'laki', value: totalLaki, color: colors['blue-button'], key: 'laki' },
|
||||||
|
{ name: 'perempuan', value: totalPerempuan, color: '#10A85AFF', key: 'perempuan' }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [stategrafikBerdasarkanJenisKelamin.findMany.data])
|
||||||
|
|
||||||
|
if (!stategrafikBerdasarkanJenisKelamin.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p={"md"}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Grafik Berdasarkan Jenis Kelamin Responden'
|
||||||
|
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Laki-laki</TableTh>
|
||||||
|
<TableTh>Perempuan</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{stategrafikBerdasarkanJenisKelamin.findMany.data.length === 0 ? (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={6}>
|
||||||
|
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
) : (
|
||||||
|
stategrafikBerdasarkanJenisKelamin.findMany.data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.laki}</TableTd>
|
||||||
|
<TableTd>{item.perempuan}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin_responden/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
disabled={stategrafikBerdasarkanJenisKelamin.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||||
|
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||||
|
<PieChart
|
||||||
|
width={800} height={300}
|
||||||
|
data={donutData}
|
||||||
|
>
|
||||||
|
<Pie
|
||||||
|
dataKey="value"
|
||||||
|
nameKey="name"
|
||||||
|
data={donutData}
|
||||||
|
cx={500}
|
||||||
|
cy={150}
|
||||||
|
innerRadius={60}
|
||||||
|
outerRadius={115}
|
||||||
|
label={true}
|
||||||
|
>
|
||||||
|
{donutData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||||
|
<Text>Laki-laki: {donutData.find((entry) => entry.name === 'laki')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||||
|
<Text>Perempuan: {donutData.find((entry) => entry.name === 'perempuan')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarkanJenisKelamin;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
function EditGrafikBerdasarkanResponden() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams() as { id: string }
|
||||||
|
const stateGrafikResponden = useProxy(grafikBerdasarkanResponden)
|
||||||
|
const id = params.id
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(id){
|
||||||
|
stateGrafikResponden.findUnique.load(id).then(() => {
|
||||||
|
const data = stateGrafikResponden.findUnique.data
|
||||||
|
if(data){
|
||||||
|
stateGrafikResponden.update.form = {
|
||||||
|
sangatbaik: data.sangatbaik || '',
|
||||||
|
baik: data.baik || '',
|
||||||
|
kurangbaik: data.kurangbaik || '',
|
||||||
|
tidakbaik: data.tidakbaik || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
stateGrafikResponden.update.id = id;
|
||||||
|
await stateGrafikResponden.update.submit();
|
||||||
|
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Sangat Baik"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stateGrafikResponden.update.form.sangatbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikResponden.update.form.sangatbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stateGrafikResponden.update.form.baik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikResponden.update.form.baik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Kurang Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stateGrafikResponden.update.form.kurangbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikResponden.update.form.kurangbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tidak Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stateGrafikResponden.update.form.tidakbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateGrafikResponden.update.form.tidakbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGrafikBerdasarkanResponden;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
|
import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function GrafikBerdasarkanRespondenCreate() {
|
||||||
|
const router = useRouter()
|
||||||
|
const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form = {
|
||||||
|
...stategrafikBerdasarkanResponden.create.form,
|
||||||
|
sangatbaik: "",
|
||||||
|
baik: "",
|
||||||
|
kurangbaik: "",
|
||||||
|
tidakbaik: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const id = await stategrafikBerdasarkanResponden.create.create();
|
||||||
|
if (id) {
|
||||||
|
const idStr = String(id);
|
||||||
|
await stategrafikBerdasarkanResponden.findUnique.load(idStr);
|
||||||
|
if (stategrafikBerdasarkanResponden.findUnique.data) {
|
||||||
|
setDonutData([stategrafikBerdasarkanResponden.findUnique.data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden")
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Sangat Baik"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.sangatbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.sangatbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.baik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.baik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Kurang Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.kurangbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.kurangbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tidak Baik"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.tidakbaik}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.tidakbaik = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarkanRespondenCreate;
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||||
|
|
||||||
|
function GrafikBerdasarkanResponden() {
|
||||||
|
|
||||||
|
const stategrafikBerdasarkanResponden = useSnapshot(grafikBerdasarkanResponden)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
await stategrafikBerdasarkanResponden.delete.byId(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
|
||||||
|
// Refresh data agar chart & tabel ikut update
|
||||||
|
stategrafikBerdasarkanResponden.findMany.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
stategrafikBerdasarkanResponden.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stategrafikBerdasarkanResponden.findMany.data) {
|
||||||
|
const totalSangatBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
||||||
|
const totalBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
||||||
|
const totalKurangBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||||
|
const totalTidakBaik = stategrafikBerdasarkanResponden.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.tidakbaik || 0), 0);
|
||||||
|
setDonutData([
|
||||||
|
{ name: 'sangatbaik', value: totalSangatBaik, color: colors['blue-button'], key: 'sangatbaik' },
|
||||||
|
{ name: 'baik', value: totalBaik, color: '#10A85AFF', key: 'baik' },
|
||||||
|
{ name: 'kurangbaik', value: totalKurangBaik, color: '#B3AA12FF', key: 'kurangbaik' },
|
||||||
|
{ name: 'tidakbaik', value: totalTidakBaik, color: '#B21313FF', key: 'tidakbaik' }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [stategrafikBerdasarkanResponden.findMany.data])
|
||||||
|
|
||||||
|
if (!stategrafikBerdasarkanResponden.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p={"md"}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Grafik Berdasarkan Pilihan Responden'
|
||||||
|
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Sangat Baik</TableTh>
|
||||||
|
<TableTh>Baik</TableTh>
|
||||||
|
<TableTh>Kurang Baik</TableTh>
|
||||||
|
<TableTh>Tidak Baik</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{stategrafikBerdasarkanResponden.findMany.data.length === 0 ? (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={6}>
|
||||||
|
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
) : (
|
||||||
|
stategrafikBerdasarkanResponden.findMany.data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.sangatbaik}</TableTd>
|
||||||
|
<TableTd>{item.baik}</TableTd>
|
||||||
|
<TableTd>{item.kurangbaik}</TableTd>
|
||||||
|
<TableTd>{item.tidakbaik}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_responden/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
disabled={stategrafikBerdasarkanResponden.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||||
|
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||||
|
<PieChart
|
||||||
|
width={800} height={300}
|
||||||
|
data={donutData}
|
||||||
|
>
|
||||||
|
<Pie
|
||||||
|
dataKey="value"
|
||||||
|
nameKey="name"
|
||||||
|
data={donutData}
|
||||||
|
cx={500}
|
||||||
|
cy={150}
|
||||||
|
innerRadius={60}
|
||||||
|
outerRadius={115}
|
||||||
|
label={true}
|
||||||
|
>
|
||||||
|
{donutData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||||
|
<Text>Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||||
|
<Text>Baik: {donutData.find((entry) => entry.name === 'baik')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#B3AA12FF'} w={20} h={20} />
|
||||||
|
<Text>Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#B21313FF'} w={20} h={20} />
|
||||||
|
<Text>Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarkanResponden;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditGrafikBerdasarakanUmur() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams() as { id: string }
|
||||||
|
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||||
|
const id = params.id
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(id){
|
||||||
|
stategrafikBerdasarkanUmur.findUnique.load(id).then(() => {
|
||||||
|
const data = stategrafikBerdasarkanUmur.findUnique.data
|
||||||
|
if(data){
|
||||||
|
stategrafikBerdasarkanUmur.update.form = {
|
||||||
|
remaja: data.remaja || '',
|
||||||
|
dewasa: data.dewasa || '',
|
||||||
|
orangtua: data.orangtua || '',
|
||||||
|
lansia: data.lansia || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
stategrafikBerdasarkanUmur.update.id = id;
|
||||||
|
await stategrafikBerdasarkanUmur.update.submit();
|
||||||
|
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur')
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Remaja"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.update.form.remaja}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.update.form.remaja = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Dewasa"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.update.form.dewasa}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.update.form.dewasa = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Orangtua"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.update.form.orangtua}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.update.form.orangtua = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Lansia"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.update.form.lansia}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.update.form.lansia = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGrafikBerdasarakanUmur;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function GrafikBerdasarakanUmurCreate() {
|
||||||
|
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stategrafikBerdasarkanUmur.create.form = {
|
||||||
|
...stategrafikBerdasarkanUmur.create.form,
|
||||||
|
remaja: "",
|
||||||
|
dewasa: "",
|
||||||
|
orangtua: "",
|
||||||
|
lansia: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const id = await stategrafikBerdasarkanUmur.create.create();
|
||||||
|
if (id) {
|
||||||
|
const idStr = String(id);
|
||||||
|
await stategrafikBerdasarkanUmur.findUnique.load(idStr);
|
||||||
|
if (stategrafikBerdasarkanUmur.findUnique.data) {
|
||||||
|
setDonutData([stategrafikBerdasarkanUmur.findUnique.data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur")
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Remaja"
|
||||||
|
type='number'
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.create.form.remaja}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.create.form.remaja = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Dewasa"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.create.form.dewasa}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.create.form.dewasa = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Orangtua"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.create.form.orangtua}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.create.form.orangtua = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Lansia"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah"
|
||||||
|
value={stategrafikBerdasarkanUmur.create.form.lansia}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanUmur.create.form.lansia = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarakanUmurCreate;
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import JudulListTab from '../../../_com/jusulListTab';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function GrafikBerdasarakanUmur() {
|
||||||
|
const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
await grafikBerdasarkanUmur.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
|
||||||
|
stategrafikBerdasarkanUmur.findMany.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
stategrafikBerdasarkanUmur.findMany.load()
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stategrafikBerdasarkanUmur.findMany.data) {
|
||||||
|
const totalRemaja = stategrafikBerdasarkanUmur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.remaja || 0), 0);
|
||||||
|
const totalDewasa = stategrafikBerdasarkanUmur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.dewasa || 0), 0);
|
||||||
|
const totalOrangtua = stategrafikBerdasarkanUmur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.orangtua || 0), 0);
|
||||||
|
const totalLansia = stategrafikBerdasarkanUmur.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.lansia || 0), 0);
|
||||||
|
setDonutData([
|
||||||
|
{ name: 'remaja', value: totalRemaja, color: colors['blue-button'], key: 'remaja' },
|
||||||
|
{ name: 'dewasa', value: totalDewasa, color: '#D32711FF', key: 'dewasa' },
|
||||||
|
{ name: 'orangtua', value: totalOrangtua, color: '#B46B04FF', key: 'orangtua' },
|
||||||
|
{ name: 'lansia', value: totalLansia, color: '#038617FF', key: 'lansia' }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [stategrafikBerdasarkanUmur.findMany.data])
|
||||||
|
|
||||||
|
if (!stategrafikBerdasarkanUmur.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p={"md"}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Grafik Berdasarkan Umur Responden'
|
||||||
|
href='/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Remaja</TableTh>
|
||||||
|
<TableTh>Dewasa</TableTh>
|
||||||
|
<TableTh>Orangtua</TableTh>
|
||||||
|
<TableTh>Lansia</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{stategrafikBerdasarkanUmur.findMany.data.length === 0 ? (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={6}>
|
||||||
|
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
) : (
|
||||||
|
stategrafikBerdasarkanUmur.findMany.data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.remaja}</TableTd>
|
||||||
|
<TableTd>{item.dewasa}</TableTd>
|
||||||
|
<TableTd>{item.orangtua}</TableTd>
|
||||||
|
<TableTd>{item.lansia}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
disabled={stategrafikBerdasarkanUmur.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||||
|
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||||
|
<PieChart
|
||||||
|
width={800} height={300}
|
||||||
|
data={donutData}
|
||||||
|
>
|
||||||
|
<Pie
|
||||||
|
dataKey="value"
|
||||||
|
nameKey="name"
|
||||||
|
data={donutData}
|
||||||
|
cx={500}
|
||||||
|
cy={150}
|
||||||
|
innerRadius={60}
|
||||||
|
outerRadius={115}
|
||||||
|
label={true}
|
||||||
|
>
|
||||||
|
{donutData.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||||
|
<Text>Remaja: {donutData.find((entry) => entry.name === 'remaja')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#D32711FF'} w={20} h={20} />
|
||||||
|
<Text>Dewasa: {donutData.find((entry) => entry.name === 'dewasa')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#B46B04FF'} w={20} h={20} />
|
||||||
|
<Text>Orangtua: {donutData.find((entry) => entry.name === 'orangtua')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={"md"} align={"center"}>
|
||||||
|
<Box bg={'#038617FF'} w={20} h={20} />
|
||||||
|
<Text>Lansia: {donutData.find((entry) => entry.name === 'lansia')?.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text='Apakah anda yakin ingin menghapus grafik berdasarkan hasil responden ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikBerdasarakanUmur;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditGrafikHasilKepuasan() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams() as { id: string }
|
||||||
|
const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat)
|
||||||
|
|
||||||
|
const id = params.id
|
||||||
|
|
||||||
|
// Load data saat komponen mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
grafikHasilKepuasan.findUnique.load(id).then(() => {
|
||||||
|
const data = grafikHasilKepuasan.findUnique.data
|
||||||
|
if (data) {
|
||||||
|
grafikHasilKepuasan.update.form = {
|
||||||
|
label: data.label || '',
|
||||||
|
kepuasan: data.kepuasan || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// Set the ID before submitting
|
||||||
|
grafikHasilKepuasan.update.id = id;
|
||||||
|
await grafikHasilKepuasan.update.submit();
|
||||||
|
router.push('/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Edit Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Label"
|
||||||
|
placeholder="masukkan label"
|
||||||
|
value={grafikHasilKepuasan.update.form.label}
|
||||||
|
onChange={(val) => {
|
||||||
|
grafikHasilKepuasan.update.form.label = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jumlah Kepuasan"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah kepuasan"
|
||||||
|
value={grafikHasilKepuasan.update.form.kepuasan}
|
||||||
|
onChange={(val) => {
|
||||||
|
grafikHasilKepuasan.update.form.kepuasan = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Simpan Perubahan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditGrafikHasilKepuasan;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function GrafikHasilKepuasan() {
|
||||||
|
const router = useRouter()
|
||||||
|
const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat)
|
||||||
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
grafikHasilKepuasan.create.form = {
|
||||||
|
...grafikHasilKepuasan.create.form,
|
||||||
|
label: "",
|
||||||
|
kepuasan: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const id = await grafikHasilKepuasan.create.create();
|
||||||
|
if (id) {
|
||||||
|
// Ensure id is a string
|
||||||
|
const idStr = String(id);
|
||||||
|
await grafikHasilKepuasan.findUnique.load(idStr);
|
||||||
|
if (grafikHasilKepuasan.findUnique.data) {
|
||||||
|
setChartData([grafikHasilKepuasan.findUnique.data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat")
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Label"
|
||||||
|
placeholder="masukkan label"
|
||||||
|
value={grafikHasilKepuasan.create.form.label}
|
||||||
|
onChange={(val) => {
|
||||||
|
grafikHasilKepuasan.create.form.label = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jumlah Kepuasan"
|
||||||
|
type="number"
|
||||||
|
placeholder="masukkan jumlah kepuasan"
|
||||||
|
value={grafikHasilKepuasan.create.form.kepuasan}
|
||||||
|
onChange={(val) => {
|
||||||
|
grafikHasilKepuasan.create.form.kepuasan = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default GrafikHasilKepuasan;
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
'use client'
|
||||||
|
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import grafikHasilKepuasanMasyarakat from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||||
|
|
||||||
|
|
||||||
|
function GrafikHasilKepuasanMasyarakat() {
|
||||||
|
type IKMGrafik = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
kepuasan: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const stateGrafikHasilKepuasan = useSnapshot(grafikHasilKepuasanMasyarakat)
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [chartData, setChartData] = useState<IKMGrafik[]>([]);
|
||||||
|
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||||
|
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
stateGrafikHasilKepuasan.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
|
||||||
|
stateGrafikHasilKepuasan.findMany.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
stateGrafikHasilKepuasan.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stateGrafikHasilKepuasan.findMany.data) {
|
||||||
|
setChartData(
|
||||||
|
stateGrafikHasilKepuasan.findMany.data.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
label: item.label,
|
||||||
|
kepuasan: Number(item.kepuasan),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [stateGrafikHasilKepuasan.findMany.data]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!stateGrafikHasilKepuasan.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulListTab
|
||||||
|
title='List Grafik Hasil Kepuasan Masyarakat'
|
||||||
|
href='/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/create'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Label</TableTh>
|
||||||
|
<TableTh>Jumlah Kepuasan</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{stateGrafikHasilKepuasan.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.label}</TableTd>
|
||||||
|
<TableTd>{item.kepuasan}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/${item.id}`)}>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
disabled={stateGrafikHasilKepuasan.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}}>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }}>
|
||||||
|
<Paper style={{ width: '100%', minWidth: 300, height: 500, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
|
||||||
|
{mounted && chartData.length > 0 ? (
|
||||||
|
<BarChart width={isMobile ? 300 : isTablet ? 300 : 300} height={380} data={chartData} >
|
||||||
|
<XAxis dataKey="label" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||||
|
</BarChart>
|
||||||
|
) : (
|
||||||
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GrafikHasilKepuasanMasyarakat;
|
||||||