Compare commits

...

8 Commits

107 changed files with 4287 additions and 2811 deletions

View File

@@ -0,0 +1,57 @@
[
{
"id" : "cmdxyb9zi0010vniiaeyi55ui",
"name" : "Surat Keterangan Beda Biodata Diri",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxycqz40014vniidftrixvf",
"name" : "Surat Keterangan Yatim Piatu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau KIA atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdwx3wph0003vnr74us2t7h7",
"name" : "Surat Keterangan Domisili Organisasi",
"deskripsi" : "<p>Persyaratan Dokumen:</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</p></li><li><p>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</p></li><li><p>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</p></li></ul><p>Alur Pelayanan:</p>"
},
{
"id" : "cmdxxv3i80004vniidg1mrucc",
"name" : "Surat Keterangan Penghasilan",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</p></li><li><p>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxwp070008vnii9jbdcto7",
"name" : "Surat Keterangan Tidak Mampu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP/KIA atau Kartu Keluarga</p></li><li><p>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</p></li><li><p>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxyfkl000cvnii1bxinnfi",
"name" : "Surat Keterangan Kelahiran",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat lahir dari dokter/bidan (jika ada)</p></li><li><p>Fotocopy Kartu Keluarga</p></li><li><p>Fotocopy KTP 2 orang saksi</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy23pl000gvniihsg38aq4",
"name" : "Surat Keterangan Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy4mgt000kvniib1nemjem",
"name" : "Surat Keterangan Kematian",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Surat Kematian dari rumah sakit atau dokter (jika ada)</p></li><li><p>tanggal kematian</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy61a1000ovniif4ytb9hs",
"name" : "Surat Keterangan Tempat Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi&nbsp;dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li><li><p>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy754q000svniiiz8oqyo0",
"name" : "Surat Keterangan Belum Kawin",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy8pi2000wvnii48fc1sxd",
"name" : "Surat Keterangan Kelakuan Baik",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
}
]

View File

@@ -0,0 +1,20 @@
[
{
"id": "cmdy0dwx10000vnnb6nmt06rv",
"name": "Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kelahiran",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0ttpz0001vnnbrvr9jb3z",
"name": "Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Perkawinan",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0vjic0002vnnbcp0e9lgq",
"name": "Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kematian",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf"
}
]

View File

@@ -0,0 +1,22 @@
[
{
"id": "cmdwq7qp60008vntw67s4j6sq",
"name": "Pembiayaan",
"jumlah": "295 M"
},
{
"id": "cmdwpsprc0003vntw9o4d33dr",
"name": "Pendapatan",
"jumlah": "495 M"
},
{
"id": "cmdwqe8xl000cvntwcuqpvdhp",
"name": "Belanja",
"jumlah": "395 M"
},
{
"id": "cmdwqq4b6000gvntwm07rinx4",
"name": "Pangan",
"jumlah": "285 M"
}
]

View File

@@ -0,0 +1,14 @@
[
{
"id": "cmdwrolsl0000vnd3e24q5440",
"name": "Olahraga dan Kepemudaan"
},
{
"id": "cmdwrot900001vnd30b5kj96g",
"name": "Hukum dan Kesadaran Masyarakat"
},
{
"id": "cmdwrp0pr0002vnd35w6nkjh0",
"name": "Tata Kelola dan Inovasi Desa"
}
]

View File

@@ -0,0 +1,20 @@
[
{
"id": "cmdwrrxkh0005vnd3p5rxkiev",
"name": "Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali",
"deskripsi": "<p>Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali</p>",
"kategoriId": "cmdwrolsl0000vnd3e24q5440"
},
{
"id": "cmdwrzs740008vnd329ysez5x",
"name": "Prestasi Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024",
"deskripsi": "<p>Prestasi Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024</p>",
"kategoriId": "cmdwrot900001vnd30b5kj96g"
},
{
"id": "cmdws0sgq000bvnd32o7m94im",
"name": "Peringkat 5 Dalam Ajang Bergengsi Mangupura Award",
"deskripsi": "<p>Peringkat 5 Dalam Ajang Bergengsi Mangupura Award</p>",
"kategoriId": "cmdwrp0pr0002vnd35w6nkjh0"
}
]

View File

@@ -0,0 +1,114 @@
[
{
"id": "cmdsjzdl30002vneknuvo4irv",
"name": "Desa Tanpa Kemiskinan",
"jumlah": "52.62",
"imageId": ""
},
{
"id": "cmdskargd0005vnek0mu2ofk9",
"name": "Desa Tanpa Kelaparan",
"jumlah": "35.75",
"imageId": ""
},
{
"id": "cmdskbvl0008vnek5dmieatb",
"name": "Desa Sehat Dan Sejahtera",
"jumlah": "77.37",
"imageId": ""
},
{
"id": "cmdskcx91000bvneko7tuaoqa",
"name": "Pendidikan Desa Berkualitas",
"jumlah": "34.11",
"imageId": ""
},
{
"id": "cmdskjare000evnek1hglu0x8",
"name": "Keterlibatan Perempuan Desa",
"jumlah": "45.70",
"imageId": ""
},
{
"id": "cmdskqcpc0002vnvnqjkqgm92",
"name": "Desa Layak Air Bersih Dan Sanitasi",
"jumlah": "48.54",
"imageId": ""
},
{
"id": "cmdsktl3x0005vnvne15seefw",
"name": "Desa Berenergi Bersih Dan Terbarukan",
"jumlah": "99.64",
"imageId": ""
},
{
"id": "cmdskuncw0008vnvcsdqoeog",
"name": "Pertumbuhan Ekonomi Desa Merata",
"jumlah": "40.92",
"imageId": ""
},
{
"id": "cmdskw83j000bvvn9szqrea6",
"name": "Infrastruktur Dan Inovasi Desa Sesuai Kebutuhan",
"jumlah": "35.37",
"imageId": ""
},
{
"id": "cmdskwrq7000envnvy0c5nbgf",
"name": "Desa Tanpa Kesenjangan",
"jumlah": "35.47",
"imageId": ""
},
{
"id": "cmdskxivx000hnvnvsx520gv1",
"name": "Kawasan Pemukiman Desa Aman Dan Nyaman",
"jumlah": "40.35",
"imageId": ""
},
{
"id": "cmdskzg4c000kvnnkiv61gkt",
"name": "Konsumsi Dan Produksi Desa Sadar Lingkungan",
"jumlah": "16.67",
"imageId": ""
},
{
"id": "cmdsl07lk000nvnnvnrepsdy5m",
"name": "Desa Tanggap Perubahan Iklim",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl10rq000qvnvnlch9c1yv",
"name": "Desa Peduli Lingkungan Laut",
"jumlah": "50.00",
"imageId": ""
},
{
"id": "cmdsl1mc2000tvnvn357n8usi",
"name": "Desa Peduli Lingkungan Darat",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl2bx3000wvnvntshi4gnj",
"name": "Desa Damai Berkeadilan",
"jumlah": "78.65",
"imageId": ""
},
{
"id": "cmdsl2yz3000zvnvnmf60ok7q",
"name": "Kemitraan Untuk Pembangunan Desa",
"jumlah": "20.00",
"imageId": ""
},
{
"id": "cmdsl492h0012vnvnmckm3n2x",
"name": "Kelembagaan Desa Dinamis Dan Budaya Desa Adaptif",
"jumlah": "47.22",
"imageId": ""
}
]

View File

@@ -50,52 +50,54 @@ model AppMenuChild {
// ========================================= FILE STORAGE ========================================= //
model FileStorage {
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
link String
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
PelayananSuratKeterangan PelayananSuratKeterangan[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
Pelapor Pelapor[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
link String
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
Pelapor Pelapor[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
DataPerpustakaan DataPerpustakaan[]
@@ -130,15 +132,15 @@ model ProgramInovasi {
}
model MediaSosial {
id String @id @default(cuid())
id String @id @default(cuid())
name String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
isActive Boolean @default(true)
}
//========================================= PROFILE ========================================= //
@@ -148,7 +150,7 @@ model DesaAntiKorupsi {
deskripsi String @db.Text
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
kategoriId String
file FileStorage? @relation(fields: [fileId], references: [id])
file FileStorage? @relation(fields: [fileId], references: [id])
fileId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -168,30 +170,30 @@ model KategoriDesaAntiKorupsi {
//========================================= SDGS Desa ========================================= //
model SDGSDesa {
id String @id @default(cuid())
name String @unique
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= APBDes ========================================= //
model APBDes {
id String @id @default(cuid())
name String @unique
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation("APBDesImage", fields: [imageId], references: [id])
imageId String
file FileStorage @relation("APBDesFile", fields: [fileId], references: [id])
fileId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
imageId String?
file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id])
fileId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= PRESTASI DESA ========================================= //
@@ -313,14 +315,14 @@ model StrukturPPID {
}
model PosisiOrganisasiPPID {
id String @id @default(cuid())
nama String @db.VarChar(100)
deskripsi String? @db.Text
id String @id @default(cuid())
nama String @db.VarChar(100)
deskripsi String? @db.Text
hierarki Int
pegawai PegawaiPPID[]
strukturOrganisasi StrukturPPID[] // Relasi balik
parentId String?
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
parentId String?
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent")
}
@@ -609,17 +611,28 @@ model KategoriBerita {
// ========================================= POTENSI DESA ========================================= //
model PotensiDesa {
id String @id @default(cuid())
name String
deskripsi String
kategori String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
name String
deskripsi String
kategori KategoriPotensi? @relation(fields: [kategoriId], references: [id])
kategoriId String?
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model KategoriPotensi {
id String @id @default(cuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PotensiDesa PotensiDesa[]
}
// ========================================= PENGUMUMAN ========================================= //
@@ -672,15 +685,17 @@ model GalleryVideo {
// ========================================= LAYANAN DESA ========================================= //
model PelayananSuratKeterangan {
id String @id @default(cuid())
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)
deskripsi String @db.Text
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
imageId String?
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
image2Id String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PelayananTelunjukSaktiDesa {

View File

@@ -2,6 +2,9 @@ import prisma from "@/lib/prisma";
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
import programInovasi from "./data/landing-page/profile/programInovasi.json";
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
import apbdes from "./data/landing-page/apbdes/apbdes.json";
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
import categoryPengumuman from "./data/category-pengumuman.json";
import kategoriBerita from "./data/kategori-berita.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
@@ -96,6 +99,23 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("media sosial success ...");
// =========== LAYANAN DESA ===========
for (const p of pelayananSuratKeterangan) {
await prisma.pelayananSuratKeterangan.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
},
});
}
console.log("media sosial success ...");
// =========== LAYANAN ===========
@@ -115,6 +135,46 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
console.log("layanan success ...");
// =========== SDGSDesa ===========
for (const l of sdgsDesa) {
await prisma.sDGSDesa.upsert({
where: {
name: l.name,
jumlah: l.jumlah,
},
update: {
name: l.name,
jumlah: l.jumlah,
},
create: {
name: l.name,
jumlah: l.jumlah,
},
});
}
console.log("sdgs desa success ...");
// =========== APBDes ===========
for (const l of apbdes) {
await prisma.aPBDes.upsert({
where: {
name: l.name,
jumlah: l.jumlah,
},
update: {
name: l.name,
jumlah: l.jumlah,
},
create: {
name: l.name,
jumlah: l.jumlah,
},
});
}
console.log("sdgs desa success ...");
for (const l of sejarahDesa) {
await prisma.sejarahDesa.upsert({
where: {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -8,12 +9,14 @@ 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(),
image2Id: z.string().nonempty(),
});
const suratKeteranganForm = {
name: "",
deskripsi: "",
imageId: "",
image2Id: "",
};
const telunjukSaktiDesaForm = {
@@ -105,15 +108,38 @@ const suratKeterangan = proxy({
},
},
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 ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
suratKeterangan.findMany.loading = true; // Use the full path to access the property
suratKeterangan.findMany.page = page;
try {
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
suratKeterangan.findMany.data = res.data.data || [];
suratKeterangan.findMany.total = res.data.total || 0;
suratKeterangan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load surat keterangan:", res.data?.message);
suratKeterangan.findMany.data = [];
suratKeterangan.findMany.total = 0;
suratKeterangan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading surat keterangan:", error);
suratKeterangan.findMany.data = [];
suratKeterangan.findMany.total = 0;
suratKeterangan.findMany.totalPages = 1;
} finally {
suratKeterangan.findMany.loading = false;
}
},
},
@@ -121,6 +147,7 @@ const suratKeterangan = proxy({
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
include: {
image: true;
image2: true;
};
}> | null,
async load(id: string) {
@@ -202,6 +229,7 @@ const suratKeterangan = proxy({
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId || "",
image2Id: data.image2Id || "",
};
return data;
} else {
@@ -238,6 +266,7 @@ const suratKeterangan = proxy({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
image2Id: this.form.image2Id,
}),
}
);
@@ -307,15 +336,38 @@ const pelayananTelunjukSaktiDesa = proxy({
},
},
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 ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
pelayananTelunjukSaktiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pelayananTelunjukSaktiDesa.findMany.data = res.data.data || [];
pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load telunjuk sakti desa:", res.data?.message);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading telunjuk sakti desa:", error);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
} finally {
pelayananTelunjukSaktiDesa.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,219 +6,470 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
kategori: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
})
name: z.string().min(1).max(5000),
deskripsi: z.string().min(1).max(5000),
kategoriId: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
kategori: "",
imageId: "",
content: "",
}
name: "",
deskripsi: "",
kategoriId: "",
imageId: "",
content: "",
};
const potensiDesa = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesa.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(
potensiDesa.create.form
);
if (res.status === 200) {
potensiDesa.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesa.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
potensiDesa.findMany.loading = true; // Use the full path to access the property
potensiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.potensi[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
potensiDesa.findMany.data = res.data.data || [];
potensiDesa.findMany.total = res.data.total || 0;
potensiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load potensi desa:", res.data?.message);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading potensi desa:", error);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
} finally {
potensiDesa.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PotensiDesaGetPayload<{
include: {
image: true;
kategori: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch potensi:", res.statusText);
potensiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching potensi:", error);
potensiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesa.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesa.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/potensi/${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,
kategoriId: data.kategoriId,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesa.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategoriId: this.form.kategoriId,
imageId: this.form.imageId,
content: this.form.content,
}),
});
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 potensi");
await potensiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update potensi"
);
return false;
} finally {
potensiDesa.edit.loading = false;
}
},
reset() {
potensiDesa.edit.id = "";
potensiDesa.edit.form = { ...defaultForm };
},
},
});
const templateKategoriPotensi = z.object({
nama: z.string().min(1, "Nama harus diisi"),
});
const defaultKategoriPotensi = {
nama: "",
};
const kategoriPotensi = proxy({
create: {
form: { ...defaultKategoriPotensi },
loading: false,
async create() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriPotensi.create.loading = true;
const res = await ApiFetch.api.desa.kategoripotensi["create"].post(
kategoriPotensi.create.form
);
if (res.status === 200) {
kategoriPotensi.findMany.load();
return toast.success("Data Kategori Potensi Berhasil Dibuat");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log(error);
return toast.error("failed create");
} finally {
kategoriPotensi.create.loading = false;
}
},
},
findMany: {
data: [] as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get();
if (res.status === 200) {
kategoriPotensi.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/desa/kategoripotensi/${id}`);
if (res.ok) {
const data = await res.json();
kategoriPotensi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriPotensi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriPotensi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async delete(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriPotensi.delete.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Data Kategori Potensi berhasil dihapus"
);
await kategoriPotensi.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus Data Kategori Potensi"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Data Kategori Potensi");
} finally {
kategoriPotensi.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultKategoriPotensi },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/kategoripotensi/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriPotensi.update.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
}),
});
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 data kategori potensi");
await kategoriPotensi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal update data kategori potensi"
);
}
} catch (error) {
console.error("Error updating data kategori potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update data kategori potensi"
);
return false;
} finally {
kategoriPotensi.update.loading = false;
}
},
reset() {
kategoriPotensi.update.id = "";
kategoriPotensi.update.form = { ...defaultKategoriPotensi };
},
},
});
const potensiDesaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesaState.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(potensiDesaState.create.form);
if (res.status === 200) {
potensiDesaState.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesaState.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.potensi["find-many"].get();
if (res.status === 200) {
potensiDesaState.findMany.data = res.data?.data ?? [];
}
}
},
findUnique: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesaState.findUnique.data = data.data ?? null;
} else {
console.error('Failed to fetch potensi:', res.statusText);
potensiDesaState.findUnique.data = null;
}
} catch (error) {
console.error('Error fetching potensi:', error);
potensiDesaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesaState.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesaState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
potensiDesa,
kategoriPotensi,
});
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/potensi/${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,
kategori: data.kategori,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesaState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesaState.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategori: this.form.kategori,
imageId: this.form.imageId,
content: this.form.content,
}),
});
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 potensi");
await potensiDesaState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update potensi");
return false;
} finally {
potensiDesaState.edit.loading = false;
}
},
reset() {
potensiDesaState.edit.id = "";
potensiDesaState.edit.form = { ...defaultForm };
}
}
})
export default potensiDesaState
export default potensiDesaState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -52,19 +53,38 @@ const sdgsDesa = proxy({
},
},
findMany: {
data: null as Array<
Prisma.SDGSDesaGetPayload<{
include: {
image: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.sdgsdesa[
"find-many"
].get();
if (res.status === 200) {
sdgsDesa.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
sdgsDesa.findMany.loading = true; // Use the full path to access the property
sdgsDesa.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.sdgsdesa[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
sdgsDesa.findMany.data = res.data.data || [];
sdgsDesa.findMany.total = res.data.total || 0;
sdgsDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
} finally {
sdgsDesa.findMany.loading = false;
}
},
},

View File

@@ -4,8 +4,9 @@ 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 { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
@@ -16,11 +17,14 @@ function EditSuratKeterangan() {
const params = useParams()
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
const [formData, setFormData] = useState({
name: stateSurat.edit.form.name,
deskripsi: stateSurat.edit.form.deskripsi,
imageId: stateSurat.edit.form.imageId,
image2Id: stateSurat.edit.form.image2Id,
})
useEffect(() => {
@@ -31,12 +35,22 @@ function EditSuratKeterangan() {
const data = await stateSurat.edit.load(id);
if (data) {
setFormData({
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
name: data.name || "",
deskripsi: data.deskripsi || "",
imageId: data.imageId || "",
image2Id: data.image2Id || "",
});
if (data?.image?.link) {
if (data.image?.link) {
setPreviewImage(data.image.link);
} else {
setPreviewImage(null);
}
if (data.image2?.link) {
setPreviewImage2(data.image2.link);
} else {
setPreviewImage2(null);
}
}
} catch (error) {
@@ -54,6 +68,7 @@ function EditSuratKeterangan() {
name: formData.name,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
image2Id: formData.image2Id,
}
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
@@ -66,6 +81,17 @@ function EditSuratKeterangan() {
stateSurat.edit.form.imageId = uploaded.id;
}
if (file2) {
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
stateSurat.edit.form.image2Id = uploaded.id;
}
await stateSurat.edit.update()
toast.success("Surat berhasil diperbarui!")
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
@@ -103,25 +129,106 @@ function EditSuratKeterangan() {
}}
/>
</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>
)}
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box >
<Dropzone
onDrop={(files) => {
const file = files[0]; // Hanya ambil file pertama
if (file) {
setFile(file);
setPreviewImage(URL.createObjectURL(file)); // Buat preview
}
}}
maxSize={5 * 1024 ** 2} // 5MB
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag images here or click to select files
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb
</Text>
</div>
</Group>
</Dropzone>
{previewImage && (
<Image
src={previewImage}
alt="Preview"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box >
<Dropzone
onDrop={(files) => {
const file = files[0]; // Hanya ambil file pertama
if (file) {
setFile2(file);
setPreviewImage2(URL.createObjectURL(file)); // Buat preview
}
}}
maxSize={5 * 1024 ** 2} // 5MB
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag images here or click to select files
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb
</Text>
</div>
</Group>
</Dropzone>
{previewImage2 && (
<Image
src={previewImage2}
alt="Preview"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -64,6 +64,10 @@ function DetailSuratKeterangan() {
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image2?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -3,8 +3,9 @@ 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 { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
@@ -12,8 +13,8 @@ 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 [previewImage2, setPreviewImage2] = useState<{ preview: string; file: File } | null>(null);
const [previewImage, setPreviewImage] = useState<{ preview: string; file: File } | null>(null);
const router = useRouter()
const resetForm = () => {
@@ -21,33 +22,57 @@ function CreateSuratKeterangan() {
name: "",
deskripsi: "",
imageId: "",
image2Id: ""
}
setPreviewImage(null)
setFile(null)
setPreviewImage2(null)
}
const handleSubmit = async () => {
if (!file) {
return toast.error("Silahkan pilih file gambar terlebih dahulu")
if (!previewImage) {
return toast.warn("Pilih file gambar utama terlebih dahulu");
}
const res = await ApiFetch.api.fileStorage.create.post({
file: file,
name: file.name
})
try {
// Upload gambar utama
const res1 = await ApiFetch.api.fileStorage.create.post({
file: previewImage.file,
name: `main_${previewImage.file.name}`,
});
const uploaded = res.data?.data
if (!uploaded?.id) {
return toast.error("Gagal upload gambar")
const uploadedImage1 = res1.data?.data;
if (!uploadedImage1?.id) {
return toast.error("Gagal upload gambar utama");
}
let uploadedImage2 = null;
// Upload gambar kedua jika ada
if (previewImage2) {
const res2 = await ApiFetch.api.fileStorage.create.post({
file: previewImage2.file,
name: `secondary_${previewImage2.file.name}`,
});
uploadedImage2 = res2.data?.data;
}
// Set form data
stateSurat.create.form.imageId = uploadedImage1.id;
if (uploadedImage2?.id) {
stateSurat.create.form.image2Id = uploadedImage2.id;
}
// Create the record
await stateSurat.create.create();
// Reset form dan redirect
resetForm();
toast.success("Data surat keterangan berhasil ditambahkan");
router.push("/admin/desa/layanan/pelayanan_surat_keterangan");
} catch (error) {
console.error("Error creating surat keterangan:", error);
toast.error("Terjadi kesalahan saat menambahkan surat keterangan");
}
stateSurat.create.form.imageId = uploaded.id
await stateSurat.create.create()
resetForm()
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
}
};
return (
<Box>
<Box mb={10}>
@@ -75,25 +100,105 @@ function CreateSuratKeterangan() {
}}
/>
</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>
)}
<Box>
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Utama</Text>
<Dropzone
onDrop={(files) => {
const file = files[0];
if (file) {
setPreviewImage({
file,
preview: URL.createObjectURL(file)
});
}
}}
maxSize={5 * 1024 ** 2}
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
<Text size="sm" c="dimmed" inline mt={7} display="block">
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
</Text>
</div>
</Group>
</Dropzone>
{previewImage && (
<Image
src={previewImage.preview}
alt="Preview Gambar Utama"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
<Box mt="lg">
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Tambahan (Opsional)</Text>
<Dropzone
onDrop={(files) => {
const file = files[0];
if (file) {
setPreviewImage2({
file,
preview: URL.createObjectURL(file)
});
}
}}
maxSize={5 * 1024 ** 2}
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
<Text size="sm" c="dimmed" inline mt={7} display="block">
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
</Text>
</div>
</Group>
</Dropzone>
{previewImage2 ? (
<Image
src={previewImage2.preview}
alt="Preview Gambar Tambahan"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
) : (
<Text size="sm" c="dimmed" mt="sm">
Kosongkan jika tidak ada gambar tambahan
</Text>
)}
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -1,21 +1,21 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, 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';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function SuratKeterangan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
title='Pelayanan Surat Keterangan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
@@ -30,54 +30,90 @@ function ListSuratKeterangan({ search }: { search: string }) {
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
const router = useRouter()
useShallowEffect(() => {
suratKeteranganState.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = suratKeteranganState.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (suratKeteranganState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!suratKeteranganState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Surat Keterangan'
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
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>
{filteredData.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" />
<Box w={200}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_surat_keterangan/${item.id}`)}>
@@ -90,6 +126,18 @@ function ListSuratKeterangan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,5 @@
'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';
@@ -76,6 +75,14 @@ function EditPelayananTelunjukSakti() {
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
placeholder="masukkan nama surat keterangan"
/>
<TextInput
value={formData.deskripsi}
onChange={(val) => {
setFormData({ ...formData, deskripsi: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={formData.link}
onChange={(val) => {
@@ -84,15 +91,6 @@ function EditPelayananTelunjukSakti() {
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>

View File

@@ -58,11 +58,24 @@ function DetailPelayananTelunjukSakti() {
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Link</Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.link}</Text>
<Text
component="a"
href={telunjukSaktiState.findUnique.data?.link}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{telunjukSaktiState.findUnique.data?.link}
</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: telunjukSaktiState.findUnique.data?.deskripsi }}></Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.deskripsi}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button

View File

@@ -1,5 +1,4 @@
'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';
@@ -43,6 +42,14 @@ function CreatePelayananTelunjukDesa() {
label={<Text fz={"sm"} fw={"bold"}>Nama Pelayanan Telunjuk Sakti Desa</Text>}
placeholder="masukkan nama pelayanan telunjuk sakti desa"
/>
<TextInput
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(val) => {
stateTelunjukDesa.create.form.deskripsi = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={stateTelunjukDesa.create.form.link}
onChange={(val) => {
@@ -51,15 +58,6 @@ function CreatePelayananTelunjukDesa() {
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>

View File

@@ -1,89 +1,153 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, 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';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function PelayananTelunjukSakti() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
}
function ListPelayananTelunjukSakti({ search }: { search: string }) {
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
useShallowEffect(() => {
telunjukSaktiState.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = telunjukSaktiState.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (telunjukSaktiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
if (!telunjukSaktiState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={300} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Pelayanan Telunjuk Sakti Desa'
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={3}>
<Text fz={"sm"} color="gray.5">
Tidak ada data
</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
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>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.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>
<TableTd>
<Box w={100}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.name }} />
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<a href={item.link} target="_blank" rel="noopener noreferrer">
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} truncate="end" fz={"sm"} />
</a>
</Box>
</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>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View 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 LayoutTabsPotensi({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "List Potensi",
value: "list_potensi",
href: "/admin/desa/potensi/list-potensi"
},
{
label: "Kategori Potensi",
value: "kategori_potensi",
href: "/admin/desa/potensi/kategori-potensi"
},
];
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}>Potensi</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 LayoutTabsPotensi;

View File

@@ -1,128 +0,0 @@
'use client';
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 { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../_state/desa/potensi';
import { useRouter } from 'next/navigation';
import CreateEditor from '../../../_com/createEditor';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
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');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategori: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
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 Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={potensiState.create.form.kategori}
onChange={(val) => (potensiState.create.form.kategori = val.target.value)}
label={<Text fz="sm" fw="bold">Kategori</Text>}
placeholder="masukkan kategori"
/>
<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 fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
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 EditKategoriPotensi() {
const editState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
nama: editState.update.form.nama || '',
});
useEffect(() => {
const loadKategori = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
nama: data.nama || '',
});
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error("Gagal memuat data kategori potensi");
}
};
loadKategori();
}, [params?.id]);
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
nama: formData.nama,
};
await editState.update.update();
toast.success('Kategori Potensi berhasil diperbarui!');
router.push('/admin/desa/potensi/kategori-potensi');
} catch (error) {
console.error('Error updating kategori potensi:', error);
toast.error('Terjadi kesalahan saat memperbarui kategori potensi');
}
};
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 Kategori Potensi</Title>
<TextInput
value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Potensi</Text>}
placeholder="masukkan nama kategori potensi"
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditKategoriPotensi;

View File

@@ -0,0 +1,55 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
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';
import { useProxy } from 'valtio/utils';
function CreateKategoriPotensi() {
const createState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const resetForm = () => {
createState.create.form = {
nama: "",
};
};
const handleSubmit = async () => {
await createState.create.create();
resetForm();
router.push("/admin/desa/potensi/kategori-potensi")
};
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}>Create Kategori Potensi</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Potensi</Text>}
placeholder='Masukkan nama kategori Potensi'
value={createState.create.form.nama}
onChange={(val) => {
createState.create.form.nama = val.target.value;
}}
/>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateKategoriPotensi;

View File

@@ -0,0 +1,128 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import potensiDesaState from '../../../_state/desa/potensi';
function KategoriPotensi() {
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Kategori Potensi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKategoriPotensi search={search} />
</Box>
);
}
function ListKategoriPotensi({ search }: { search: string }) {
const listDataState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
useEffect(() => {
listDataState.findMany.load()
}, [])
const handleDelete = () => {
if (selectedId) {
listDataState.delete.delete(selectedId)
setModalHapus(false)
setSelectedId(null)
listDataState.findMany.load()
}
}
const filteredData = (listDataState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
if (!listDataState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<JudulList
title='List Kategori Potensi'
href='/admin/desa/potensi/kategori-potensi/create'
/>
<Box style={{ overflowX: 'auto' }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Hapus</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
</Box>
</TableTd>
<TableTd>{item.nama}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/desa/potensi/kategori-potensi/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={listDataState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus kategori Potensi ini?'
/>
</Box>
)
}
export default KategoriPotensi;

View File

@@ -0,0 +1,14 @@
'use client'
import React from 'react';
import LayoutTabsPotensi from './_lib/layoutTabs';
function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsPotensi>
{children}
</LayoutTabsPotensi>
)
}
export default Layout;

View File

@@ -1,21 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
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 } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { IconArrowBack, IconPhoto, IconUpload, IconX } 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 EditPotensi() {
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
const router = useRouter()
const params = useParams()
@@ -24,23 +25,24 @@ function EditPotensi() {
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
kategori: '',
kategoriId: '',
content: '',
imageId: ''
});
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
const loadPotensi = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await potensiDesaState.edit.load(id); // ambil data dari API
const data = await potensiState.edit.load(id); // ambil data dari API
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
kategori: data.kategori || '',
kategoriId: data.kategoriId || '',
content: data.content || '',
imageId: data.imageId || '',
});
@@ -65,7 +67,7 @@ function EditPotensi() {
...potensiState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
kategori: formData.kategori,
kategoriId: formData.kategoriId,
content: formData.content,
};
@@ -82,7 +84,7 @@ function EditPotensi() {
await potensiState.edit.update();
toast.success("Potensi berhasil diperbarui!");
router.push("/admin/desa/potensi");
router.push("/admin/desa/potensi/list-potensi");
} catch (error) {
console.error("Error updating potensi:", error);
toast.error("Terjadi kesalahan saat memperbarui potensi");
@@ -119,40 +121,82 @@ function EditPotensi() {
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={formData.kategori}
onChange={(e) => {
const val = e.target.value;
setFormData((prev) => ({ ...prev, kategori: val }));
potensiState.edit.form.kategori = val;
}}
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
placeholder="masukkan kategori"
<Select
value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
data={
potensiDesaState.kategoriPotensi.findMany.data?.map((v) => ({
value: v.id,
label: v.nama
})) || []
}
clearable
searchable
required
error={!formData.kategoriId ? "Pilih kategori" : undefined}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<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>
)}
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.content}
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
potensiState.edit.form.content = htmlContent;

View File

@@ -5,18 +5,19 @@ import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../../_state/desa/potensi';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
export default function DetailPotensi() {
const router = useRouter()
const params = useParams()
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
useShallowEffect(() => {
potensiState.findUnique.load(params?.id as string)
@@ -60,7 +61,7 @@ export default function DetailPotensi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori}</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori?.nama}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
@@ -91,7 +92,7 @@ export default function DetailPotensi() {
<Button
onClick={() => {
if (potensiState.findUnique.data) {
router.push(`/admin/desa/potensi/edit/${potensiState.findUnique.data.id}`)
router.push(`/admin/desa/potensi/list-potensi/${potensiState.findUnique.data.id}/edit`)
}
}}
disabled={!potensiState.findUnique.data}

View File

@@ -0,0 +1,176 @@
'use client';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState.potensiDesa);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
}, [])
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');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi/list-potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategoriId: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
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 Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<Select
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
value={potensiState.create.form.kategoriId || ""}
onChange={(val) => {
potensiState.create.form.kategoriId = val ?? "";
}}
data={potensiDesaState.kategoriPotensi.findMany.data?.map((item) => ({
value: item.id,
label: item.nama,
}))}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,158 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import potensiDesaState from '../../../_state/desa/potensi';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = potensiState.potensiDesa.findMany;
useEffect(() => {
potensiState.kategoriPotensi.findMany.load()
load(page, 10)
}, [])
const filteredData = (potensiState.potensiDesa.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori?.nama.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={4}>Tidak Ada Data</TableTd>
</TableTr>
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori?.nama}</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/list-potensi/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default Potensi;

View File

@@ -1,100 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import potensiDesaState from '../../_state/desa/potensi';
import { useState } from 'react';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
useShallowEffect(() => {
potensiState.findMany.load()
}, [])
const filteredData = (potensiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori.toLowerCase().includes(keyword)
);
});
if (!potensiState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/detail/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
export default Potensi;

View File

@@ -195,7 +195,6 @@ function CreateAPBDes() {
placeholder='Masukkan judul'
/>
<TextInput
type='number'
value={stateAPBDes.create.form.jumlah}
onChange={(val) => {
stateAPBDes.create.form.jumlah = val.target.value;

View File

@@ -77,7 +77,7 @@ function ListAPBDes({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.jumlah}</Text>
<Text truncate="end" fz={"sm"}>Rp. {item.jumlah}</Text>
</TableTd>
<TableTd>
{item.file?.link ? (

View File

@@ -68,7 +68,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
<Skeleton height={550} />
</Stack>
);
}

View File

@@ -59,7 +59,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
<Skeleton height={550} />
</Stack>
);
}

View File

@@ -77,8 +77,10 @@ function ListPrestasi({ search }: { search: string }) {
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<TableTd >
<Box w={150}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>{item.kategori?.name}</TableTd>
<TableTd>

View File

@@ -57,7 +57,7 @@ function ListMediaSosial({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
<Skeleton height={550} />
</Stack>
);
}

View File

@@ -57,7 +57,7 @@ function ListProgramInovasi({ search }: { search: string }) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
<Skeleton height={550} />
</Stack>
);
}

View File

@@ -163,7 +163,7 @@ function EditKolaborasiInovasi() {
<iframe
src={previewImage}
width="100%"
height="500px"
height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/>
) : (

View File

@@ -110,7 +110,7 @@ function CreateSDGsDesa() {
<iframe
src={previewImage}
width="100%"
height="500px"
height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/>
) : (

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
@@ -30,24 +30,61 @@ function SdgsDesa() {
function ListSdgsDesa({ search }: { search: string }) {
const listState = useProxy(sdgsDesa)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany;
useEffect(() => {
listState.findMany.load()
load(page, 10)
}, [])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.jumlah.toLowerCase().includes(keyword)
)
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.jumlah?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (!listState.findMany.data) {
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Desa Anti Korupsi'
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama SDGs Desa</TableTh>
<TableTh>Jumlah SDGs Desa</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
@@ -90,6 +127,18 @@ function ListSdgsDesa({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -98,7 +98,7 @@ export const navBar = [
{
id: "Desa_2",
name: "Potensi",
path: "/admin/desa/potensi"
path: "/admin/desa/potensi/list-potensi"
},
{
id: "Desa_3",

View File

@@ -7,6 +7,7 @@ import GalleryFoto from "./gallery/foto";
import GalleryVideo from "./gallery/video";
import LayananDesa from "./layanan";
import Penghargaan from "./penghargaan";
import KategoriPotensi from "./potensi/kategori-potensi";
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
@@ -18,5 +19,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
.use(GalleryVideo)
.use(LayananDesa)
.use(Penghargaan)
.use(KategoriPotensi)
export default Desa;

View File

@@ -7,6 +7,7 @@ type FormCreate = Prisma.PelayananSuratKeteranganGetPayload<{
name: true;
deskripsi: true;
imageId: true;
image2Id: true;
};
}>;
async function createPelayananSuratKeterangan(context: Context) {
@@ -17,6 +18,7 @@ async function createPelayananSuratKeterangan(context: Context) {
name: body.name,
deskripsi: body.deskripsi,
imageId: body.imageId,
image2Id: body.image2Id,
},
});
return {

View File

@@ -17,6 +17,7 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
where: { id },
include: {
image: true,
image2: true,
},
});
@@ -40,6 +41,18 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
}
}
if (pelayananSuratKeterangan.image2) {
try {
const filePath = path.join(pelayananSuratKeterangan.image2.path, pelayananSuratKeterangan.image2.name);
await fs.unlink(filePath);
await prisma.fileStorage.delete({
where: { id: pelayananSuratKeterangan.image2.id },
});
} catch (err) {
console.error("Gagal hapus gambar lama:", err);
}
}
const deleted = await prisma.pelayananSuratKeterangan.delete({
where: { id },
});

View File

@@ -1,24 +1,47 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pelayananSuratKeteranganFindMany() {
try {
const data = await prisma.pelayananSuratKeterangan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
export default async function pelayananSuratKeteranganFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch pelayanan surat keterangan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch pelayanan surat keterangan",
};
}
try {
const [data, total] = await Promise.all([
prisma.pelayananSuratKeterangan.findMany({
where: { isActive: true },
include: {
image: true,
image2: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pelayananSuratKeterangan.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch pelayanan surat keterangan with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch pelayanan surat keterangan with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}

View File

@@ -24,6 +24,7 @@ export default async function pelayananSuratKeteranganFindUnique(request: Reques
where: { id },
include: {
image: true,
image2: true,
},
});

View File

@@ -18,6 +18,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
name: t.String(),
deskripsi: t.String(),
imageId: t.String(),
image2Id: t.String(),
}),
})
.delete("/del/:id", pelayananSuratKeteranganDelete)
@@ -30,6 +31,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
name: t.String(),
deskripsi: t.String(),
imageId: t.String(),
image2Id: t.String(),
}),
})
export default PelayananSuratKeterangan;

View File

@@ -9,6 +9,7 @@ type FormUpdate = Prisma.PelayananSuratKeteranganGetPayload<{
name: true;
deskripsi: true;
imageId: true;
image2Id: true;
};
}>;
export default async function updatePelayananSuratKeterangan(context: Context) {
@@ -16,7 +17,7 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
const id = context.params?.id;
const body = (await context.body) as Omit<FormUpdate, "id">;
const { name, deskripsi, imageId } = body;
const { name, deskripsi, imageId, image2Id } = body;
if (!id) {
return new Response(JSON.stringify({
@@ -63,12 +64,28 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
}
}
if (existing.image2Id && existing.image2Id !== image2Id) {
const oldImage = existing.image;
if (oldImage) {
try {
const filePath = path.join(oldImage.path, oldImage.name);
await fs.unlink(filePath);
await prisma.fileStorage.delete({
where: { id: oldImage.id },
});
} catch (err) {
console.error("Gagal hapus gambar lama:", err);
}
}
}
const updated = await prisma.pelayananSuratKeterangan.update({
where: { id },
data: {
name,
deskripsi,
imageId,
image2Id,
},
})

View File

@@ -1,21 +1,43 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pelayananTelunjukSaktiDesaFindMany() {
export default async function pelayananTelunjukSaktiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
});
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa",
data,
};
const [data, total] = await Promise.all([
prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pelayananTelunjukSaktiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}

View File

@@ -6,7 +6,7 @@ type FormCreate = Prisma.PotensiDesaGetPayload<{
select: {
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
}
@@ -18,7 +18,7 @@ export default async function potensiDesaCreate(context: Context) {
data: {
name: body.name,
deskripsi: body.deskripsi,
kategori: body.kategori,
kategoriId: body.kategoriId,
imageId: body.imageId,
content: body.content,
},

View File

@@ -17,6 +17,7 @@ const potensiDesaDelete = async (context: Context) => {
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -1,26 +1,47 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function potensiDesaFindMany() {
export default async function potensiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.potensiDesa.findMany({
where: { isActive: true },
include: {
image: true,
},
});
return {
success: true,
message: "Success fetch potensi desa",
data,
};
const [data, total] = await Promise.all([
prisma.potensiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
include: {
image: true,
kategori: true
},
orderBy: { createdAt: 'desc' },
}),
prisma.potensiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch potensi desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch potensi desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch potensi desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}
export default potensiDesaFindMany
}

View File

@@ -25,6 +25,7 @@ export default async function findUnique(
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -14,7 +14,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),
@@ -35,7 +35,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormCreate = {
nama: string;
}
export default async function kategoriPotensiCreate(context: Context) {
const body = (await context.body) as FormCreate;
try {
const result = await prisma.kategoriPotensi.create({
data: {
nama: body.nama
},
});
return {
success: true,
message: "Berhasil membuat kategori potensi",
data: result,
};
} catch (error) {
console.error("Error creating kategori potensi:", error);
throw new Error("Gagal membuat kategori potensi: " + (error as Error).message);
}
}

View File

@@ -0,0 +1,16 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriPotensiDelete(context: Context) {
const id = context.params.id as string;
await prisma.kategoriPotensi.delete({
where: { id },
});
return {
status: 200,
success: true,
message: "Success delete kategori potensi",
};
}

View File

@@ -0,0 +1,11 @@
import prisma from "@/lib/prisma";
export default async function kategoriPotensiFindMany() {
const data = await prisma.kategoriPotensi.findMany();
return {
success: true,
message: "Success get all kategori potensi",
data,
};
}

View File

@@ -0,0 +1,46 @@
import prisma from "@/lib/prisma";
export default async function kategoriBukuFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return {
success: false,
message: "ID is required",
}
}
try {
if (typeof id !== 'string') {
return {
success: false,
message: "ID is required",
}
}
const data = await prisma.kategoriPotensi.findUnique({
where: { id },
});
if (!data) {
return {
success: false,
message: "Data not found",
}
}
return {
success: true,
message: "Success get kategori potensi",
data,
}
} catch (error) {
console.error("Find by ID error:", error);
return {
success: false,
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
}
}
}

View File

@@ -0,0 +1,33 @@
import Elysia, { t } from "elysia";
import kategoriPotensiCreate from "./create";
import kategoriPotensiDelete from "./del";
import kategoriPotensiFindMany from "./findMany";
import kategoriPotensiFindUnique from "./findUnique";
import kategoriPotensiUpdate from "./updt";
const KategoriPotensi = new Elysia({
prefix: "/kategoripotensi",
tags: ["Desa / Potensi"],
})
.post("/create", kategoriPotensiCreate, {
body: t.Object({
nama: t.String(),
}),
})
.get("/findMany", kategoriPotensiFindMany)
.get("/:id", async (context) => {
const response = await kategoriPotensiFindUnique(
new Request(context.request)
);
return response;
})
.put("/:id", kategoriPotensiUpdate, {
body: t.Object({
nama: t.String(),
}),
})
.delete("/del/:id", kategoriPotensiDelete);
export default KategoriPotensi;

View File

@@ -0,0 +1,28 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
nama: string;
}
export default async function kategoriPotensiUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.kategoriPotensi.update({
where: { id },
data: {
nama: body.nama,
},
});
return {
success: true,
message: "Berhasil mengupdate kategori potensi",
data: result,
};
} catch (error) {
console.error("Error updating kategori potensi:", error);
throw new Error("Gagal mengupdate kategori potensi: " + (error as Error).message);
}
}

View File

@@ -9,7 +9,7 @@ type FormUpdate = Prisma.PotensiDesaGetPayload<{
id: true;
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
};
@@ -23,7 +23,7 @@ export default async function potensiDesaUpdate(context: Context) {
const {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
} = body;
@@ -39,6 +39,7 @@ export default async function potensiDesaUpdate(context: Context) {
where: { id },
include: {
image: true,
kategori: true
}
});
@@ -69,7 +70,7 @@ export default async function potensiDesaUpdate(context: Context) {
data: {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
},

View File

@@ -17,7 +17,7 @@ async function apbdesFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.aPBDes.count({
where: { isActive: true },

View File

@@ -16,7 +16,7 @@ async function sdgsDesaFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { jumlah: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.sDGSDesa.count({
where: { isActive: true },

View File

@@ -11,7 +11,7 @@ const SDGSDesa = new Elysia({
})
// ✅ Find all
.get("/find-many", sdgsDesaFindMany)
.get("/findMany", sdgsDesaFindMany)
// ✅ Find by ID
.get("/:id", sdgsDesaFindUnique)

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Beda Biodata Diri
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Belum Kawin
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Box, Button, Center, Container, Image, List, ListItem, Stack, Text } from '@mantine/core';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Domisili Organisasi
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0} >
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Group, Center } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelahiran
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Surat lahir dari dokter/bidan (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP 2 orang saksi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-kelahiran-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelakuan Baik (Pengantar SKCK)
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kematian
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Surat Kematian dari rumah sakit atau dokter (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Tanggal dan Waktu Kematian</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Penghasilan
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{base:"h4", md: "h2"}} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{base:"sm", md: "h3"}}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</ListItem>
</List>
<Box py={20}>
<Text fz={{base:"h4", md: "h2"}} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tempat Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tempat-usaha-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,44 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tidak Mampu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP/KIA atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu
</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tidak-mampu-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack >
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Yatim Piatu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp, KIA atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { useParams } from 'next/navigation';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../_com/BackButto';
interface LayananData {
id: string;
name: string;
deskripsi: string;
imageId: string;
image2Id: string;
image?: {
id: string;
link: string;
};
image2?: {
id: string;
link: string;
};
}
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(true);
const [data, setData] = useState<LayananData | null>(null);
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.suratKeterangan.findUnique.load(id);
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
setData(result);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
ta="center"
fw="bold"
>
{data.name}
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
fz={{ base: "h4", md: "h2" }}
pb={20}
/>
{data.image2?.link && (
<Center>
<Image src={data.image2.link} alt={data.name} />
</Center>
)}
<Group justify='center' mt="md">
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
Ajukan Permohonan
</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,10 +0,0 @@
import { Stack } from "@mantine/core";
export default async function Page({ params }: { params: Promise<{ sub: string }> }) {
const { sub } = await params
return (
<Stack>
{sub}
</Stack>
)
}

View File

@@ -0,0 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Skeleton, Text } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPendudukNonPermanent() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananPendudukNonPermanen.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = state.pelayananPendudukNonPermanen.findById.data
return (
<Box>
{loading ? (
<Skeleton h={500} />
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{data?.name}</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"} dangerouslySetInnerHTML={{__html: data?.deskripsi || ''}} />
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
)}
</Box>
);
}
export default PelayananPendudukNonPermanent;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Button, Center, Group, Skeleton, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPerizinanBerusaha() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.pelayananPerizinanBerusaha.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
const data = state.pelayananPerizinanBerusaha.findById.data;
if (!data) {
return (
<Center>
<Text>Data tidak tersedia</Text>
</Center>
);
}
return (
<Box>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text
py={10}
ta={"justify"}
fz={{ base: "sm", md: 'h3' }}
dangerouslySetInnerHTML={{ __html: 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="https://oss.go.id/" target="_blank" rel="noopener noreferrer">(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.
</Text>
</Box>
</Box>
)}
</Box>
);
}
export default PelayananPerizinanBerusaha;

View File

@@ -0,0 +1,97 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananSuratKeterangan({ search }: { search: string }) {
const [loading, setLoading] = useState(false);
const router = useRouter()
const state = useProxy(stateLayananDesa)
const filteredData = useMemo(() => {
if (!state.suratKeterangan.findMany.data) return [];
return state.suratKeterangan.findMany.data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [state.suratKeterangan.findMany.data, search]);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.suratKeterangan.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
return (
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
filteredData.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
</Box>
);
}
export default PelayananSuratKeterangan;

View File

@@ -0,0 +1,67 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Flex, Skeleton, Text, Title } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
interface ServiceItem {
name: string;
deskripsi: string;
link: string;
}
function PelayananTelunjukSaktiDesa() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananTelunjukSaktiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
name: string;
id: string;
deskripsi: string;
link: string;
items: ServiceItem[];
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}>
return (
<Box>
<Title fz="h2" py={10} mb="md">Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. Layanan Telunjuk Sakti Desa meliputi :</Title>
{loading ? (
<Skeleton h={500} />
) : (
data.map((v, k) => {
return (
<Box key={k}>
<Box py={10}>
<Flex gap={"3"} align={"center"}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{v.name}
</Text>
<Text span fz={{ base: "h4", md: "h3" }}>
<a href={v.link} target="_blank" rel="noopener noreferrer" style={{ color: '#228be6' }}>{v.deskripsi}</a>
</Text>
</Flex>
</Box>
</Box>
)
})
)}
</Box>
);
}
export default PelayananTelunjukSaktiDesa;

View File

@@ -1,85 +1,19 @@
'use client'
import colors from "@/con/colors";
import { ActionIcon, BackgroundImage, Box, Button, Container, Divider, Flex, Group, List, ListItem, SimpleGrid, Stack, Stepper, StepperCompleted, StepperStep, Text, TextInput } from "@mantine/core";
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp, IconSearch } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
import { Box, Container, Stack, Text, TextInput } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import BackButton from "./_com/BackButto";
import PelayananPerizinanBerusaha from "./_com/pelayananPerizinanBerusaha";
import PelayananSuratKeterangan from "./_com/pelayananSuratKeterangan";
import PelayananTelunjukSaktiDesa from "./_com/pelayananTelunjukSaktiDesa";
import PelayananPendudukNonPermanent from "./_com/pelayananPendudukNonPermanent";
const data = [
{
id: 1,
images: "/api/img/test.png",
name: "Surat Keterangan Domisili Organisasi",
link: "/darmasaba/desa/layanan/surat-keterangan-domisili"
},
{
id: 2,
images: "/api/img/test-3.jpeg",
name: "Surat Keterangan Penghasilan",
link: "/darmasaba/desa/layanan/surat-keterangan-penghasilan"
},
{
id: 3,
images: "/api/img/domisili.jpeg",
name: "Surat Keterangan Tidak Mampu",
link: "/darmasaba/desa/layanan/surat-keterangan-tidak-mampu"
},
{
id: 4,
images: "/api/img/kelahiran.jpeg",
name: "Surat Keterangan Kelahiran",
link: "/darmasaba/desa/layanan/surat-keterangan-kelahiran"
},
{
id: 5,
images: "/api/img/keterangan-usaha.jpeg",
name: "Surat Keterangan Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-usaha"
},
{
id: 6,
images: "/api/img/kematian.jpeg",
name: "Surat Keterangan Kematian",
link: "/darmasaba/desa/layanan/surat-keterangan-kematian"
},
{
id: 7,
images: "/api/img/tempatusaha.jpeg",
name: "Surat Keterangan Tempat Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-tempat-usaha"
},
{
id: 8,
images: "/api/img/belumkawin.jpeg",
name: "Surat Keterangan Belum Kawin",
link: "/darmasaba/desa/layanan/surat-keterangan-belum-kawin"
},
{
id: 9,
images: "/api/img/berkelakuan-baik.jpeg",
name: "Surat Keterangan Kelakuan Baik",
link: "/darmasaba/desa/layanan/surat-keterangan-kelakuan-baik"
},
{
id: 10,
images: "/api/img/biodata.jpeg",
name: "Surat Keterangan Beda Biodata Diri",
link: "/darmasaba/desa/layanan/surat-keterangan-beda-biodata-diri"
},
{
id: 11,
images: "/api/img/yatim.jpeg",
name: "Surat Keterangan Yatim Piatu",
link: "/darmasaba/desa/layanan/surat-keterangan-yatim-piatu"
}
]
export default function Page() {
const router = useRouter()
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
const [search, setSearch] = useState("")
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -100,149 +34,20 @@ export default function Page() {
w={{ base: "70%", md: "50%" }}
placeholder="Cari Layanan"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
{/* Bagian Pelayanan Surat Keterangan */}
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
</Box>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.images}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
</SimpleGrid>
<PelayananSuratKeterangan search={search} />
{/* Bagian Pelayanan Perizinan Berusaha */}
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>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.</Text>
<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={"https://oss.go.id/"}>(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.</Text>
</Box>
<PelayananPerizinanBerusaha/>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Telunjuk Sakti Desa</Text>
</Box>
<Text fz={{ base: "sm", md: 'h3' }} py={10}>Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. layanan telunjuk sakti Desa meliputi :</Text>
<List type="ordered" pb={20}>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf">Akta Kelahiran</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf">Akta Perkawinan</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf">Akata Kematian</a>)</ListItem>
</List>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Penduduk Non-Permanent</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"}>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.</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
<PelayananTelunjukSaktiDesa/>
{/* Bagian Pelayanan Penduduk Non Permanent */}
<PelayananPendudukNonPermanent/>
</Box>
</Stack>
)

View File

@@ -0,0 +1,77 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(potensiDesaState.potensiDesa)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.name}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Informasi dan Pelayanan Administrasi Digital
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
{state.findUnique.data?.deskripsi || ''}
</Text>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,105 +1,36 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Flex, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Flex, Group, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useTransitionRouter } from 'next-view-transitions';
import BackButton from '../layanan/_com/BackButto';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
const datamap = [
{
id: 1,
images: "/api/img/tps.png",
name: "TPS3R Pudak Mesari",
kategori: "Lingkungan",
link: "/darmasaba/desa/potensi/tps3r-pudak-mesari",
},
{
id: 2,
images: "/api/img/ack.png",
name: "Bumdes Pudak Mesari",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/bumdes-pudak-mesari",
},
{
id: 3,
images: "/api/img/taman-beji.jpg",
name: "Taman Beji Cengana",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/taman-beji-cengana",
},
{
id: 4,
images: "/api/img/waterpark.png",
name: "Gumuh Sari Water Park",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/gumuh-sari-water-park",
},
{
id: 5,
images: "/api/img/pertanian.jpg",
name: "Pertanian",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/pertanian"
},
{
id: 6,
images: "/api/img/warungumkm.jpg",
name: "Kawasan Kuliner",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/kawasan-kuliner"
},
{
id: 7,
images: "/api/img/ikm.png",
name: "IKM Berbasis Pengolahan Pangan",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/ikm-berbasis-pengolahan-pangan"
},
{
id: 8,
images: "/api/img/genteng.jpeg",
name: "Genteng",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/genteng"
},
{
id: 9,
images: "/api/img/peternakanlele.jpg",
name: "Peternakan Ikan Lele",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/peternakan-ikan-lele"
},
{
id: 10,
images: "/api/img/jogging.jpg",
name: "Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa",
kategori: "Lingkungan",
link: "/darmasaba/desa/potensi/jogging-track-tegeh-aban-karang-gadon-dan-munduk-uma-desa"
},
{
id: 11,
images: "/api/img/damtanahputih.jpeg",
name: "Dam Tanah Putih",
kategori: "Wisata",
link: "/darmasaba/desa/potensi/dam-tanah-putih"
},
{
id: 12,
images: "/api/img/umkm.jpeg",
name: "UMKM",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/umkm"
},
{
id: 13,
images: "/api/img/potong-daging.png",
name: "Pemotongan Daging",
kategori: "Ekonomi",
link: "/darmasaba/desa/potensi/pemotongan-daging"
},
]
function Page() {
const router = useRouter()
const router = useTransitionRouter()
const [loading, setLoading] = useState(false)
const state = useProxy(potensiDesaState)
useEffect(()=> {
state.kategoriPotensi.findMany.load()
const loadData = async () => {
try {
setLoading(true)
await state.potensiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = state.potensiDesa.findMany.data
// const kategoriData = state.kategoriPotensi.findMany.data
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} >
<Box px={{ base: "md", md: 100 }}>
@@ -118,13 +49,17 @@ function Page() {
</Text>
</Box>
<Paper radius={"md"} px={"xl"} py={5} bg={colors["blue-button"]} >
<Flex justify={"space-evenly"} align={"center"} gap={"xl"}>
<Flex justify={"space-between"} align={"center"} gap={"xl"}>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>13</Text>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() !== 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Potensi</Text>
</Box>
<Box>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>5</Text>
<Text ta={"center"} fz={"h2"} fw={"bold"} c={"white"}>
{data?.filter(item => item.kategori?.nama.toLowerCase() === 'wisata' ).length || 0}
</Text>
<Text ta={"center"} fz={"sm"} c={"white"}>Destinasi Wisata</Text>
</Box>
</Flex>
@@ -140,11 +75,16 @@ function Page() {
sm: 3
}}
>
{datamap.map((v, k) => {
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
data?.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.images}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
@@ -162,7 +102,7 @@ function Page() {
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Group>
<Paper radius={"lg"} py={7} px={10}>
<Text>{v.kategori}</Text>
<Text>{v.kategori?.nama}</Text>
</Paper>
</Group>
<Box p={"lg"}>
@@ -176,14 +116,15 @@ function Page() {
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
onClick={() => router.push(`/darmasaba/desa/potensi/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
})
)}
</SimpleGrid>
</Box>

View File

@@ -1,47 +1,33 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ActionIcon, BackgroundImage, Box, Container, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { ActionIcon, BackgroundImage, Box, Center, Container, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
import { useProxy } from 'valtio/utils';
import { useEffect, useState } from 'react';
import { Link } from 'next-view-transitions';
const data = [
{
id: 1,
title: "Pendapatan",
image: "/api/img/pendapatan.jpeg",
value: "Rp 495M"
},
{
id: 2,
title: "Belanja",
image: "/api/img/belanja.jpeg",
value: "Rp 395M"
},
{
id: 3,
title: "Pembiayaan",
image: "/api/img/pembiayaan.jpeg",
value: "Rp 295M"
},
{
id: 4,
title: "APBDesa 2025",
image: "/api/img/apb-des.jpg",
value: "Rp 500M"
},
{
id: 5,
title: "APBDesa 2024",
image: "/api/img/apb-des.jpg",
value: "Rp 450M"
},
{
id: 6,
title: "APBDesa 2023",
image: "/api/img/apb-des.jpg",
value: "Rp 400M"
},
]
function Page() {
const state = useProxy(apbdes);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData();
}, [])
const data = state.findMany.data || [];
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
@@ -65,53 +51,59 @@ function Page() {
sm: 3,
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image}
h={350}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"1.5rem"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.title}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.value}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})}
}}>{v.jumlah}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]} component={Link} href={v.file?.link || ''}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
</Stack>
);

View File

@@ -1,73 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconClipboardText } from '@tabler/icons-react';
const data = [
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "5.1 ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "5.2 ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
}
]
function Lokal() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"} ta={"center"}>
Kearifan Lokal
</Text>
<Text
py={10}
ta={"justify"}
>
Sebagai bagian dari Program Desa Anti Korupsi, Desa Darmasaba menerapkan kearifan lokal sebagai strategi dalam menciptakan pemerintahan desa yang bersih, transparan, dan berintegritas. Kearifan lokal ini menjadi landasan moral dan budaya yang memperkuat nilai-nilai kejujuran, gotong royong, serta kepedulian sosial dalam kehidupan masyarakat. Adapun beberapa Kearifan Lokal :
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 2,
}}>
{data.map((v, k) => {
return (
<Box
key={k}
>
<Paper p={"lg"} >
<Stack h="100%">
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex direction={{ base: "column", md: "row" }} align="center" justify="center" style={{ flex: 1 }}>
<Box mb={{ base: 10, md: 0 }}>
{v.icon}
</Box>
<Stack px={{ base: 0, md: 20 }} gap={10} style={{ flex: 1 }}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Button bg={colors["blue-button"]} radius={"lg"} w="fit-content">Download</Button>
</Stack>
</Flex>
</Stack>
</Paper>
</Box>
);
})}
</SimpleGrid >
</Stack >
);
}
export default Lokal;

View File

@@ -1,88 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconClipboardText } from '@tabler/icons-react';
const data = [
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "3.3 ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "3.4 ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "3.5 ADANYA MAKLUMAT PELAYANAN",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
]
function Pelayanan() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"} ta={"center"}>
Penguatan Kualitas Pelayanan Publik
</Text>
<Text
py={10}
ta={"justify"}
>
Sebagai bagian dari Program Desa Anti Korupsi, Desa Darmasaba berkomitmen untuk meningkatkan kualitas pelayanan publik agar lebih transparan, cepat, dan bebas dari pungutan liar. Penguatan ini bertujuan untuk memastikan masyarakat mendapatkan pelayanan yang profesional, akuntabel, dan sesuai dengan standar yang telah ditetapkan. Adapun beberapa Penguatan Kualitas Pelayanan Publik :
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 2,
}}>
{data.map((v, k) => {
return (
<Box
key={k}
>
<Paper p={"lg"} h="100%">
<Stack h="100%">
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex direction={{ base: "column", md: "row" }} align="center" justify="center" style={{ flex: 1 }}>
<Box mb={{ base: 10, md: 0 }}>
{v.icon}
</Box>
<Stack px={{ base: 0, md: 20 }} gap={10} style={{ flex: 1 }}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Button bg={colors["blue-button"]} radius={"lg"} w="fit-content">Download</Button>
</Stack>
</Flex>
</Stack>
</Paper>
</Box>
);
})}
</SimpleGrid >
</Stack >
);
}
export default Pelayanan;

View File

@@ -1,75 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconClipboardText } from '@tabler/icons-react';
const data = [
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "4.2 ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
]
function Partisipasi() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"} ta={"center"}>
Penguatan Tata Laksana
</Text>
<Text
py={10}
ta={"justify"}
>
Sebagai bagian dari Program Desa Anti Korupsi, Desa Darmasaba menekankan penguatan partisipasi masyarakat agar warga dapat ikut serta dalam pengawasan, pengambilan keputusan, dan pelaksanaan pembangunan desa yang transparan dan akuntabel. Adapun beberapa Penguatan Partisipasi Masyarakat :
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 2,
}}>
{data.map((v, k) => {
return (
<Box
key={k}
>
<Paper p={"lg"} >
<Stack h="100%">
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex direction={{ base: "column", md: "row" }} align="center" justify="center" style={{ flex: 1 }}>
<Box mb={{ base: 10, md: 0 }}>
{v.icon}
</Box>
<Stack px={{ base: 0, md: 20 }} gap={10} style={{ flex: 1 }}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Button bg={colors["blue-button"]} radius={"lg"} w="fit-content">Download</Button>
</Stack>
</Flex>
</Stack>
</Paper>
</Box>
);
})}
</SimpleGrid >
</Stack >
);
}
export default Partisipasi;

View File

@@ -1,78 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconClipboardText } from '@tabler/icons-react';
const data = [
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "2.2 ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN PENGAWASAN",
deskripsi: "2.3 TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
]
function Pengawasan() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"} ta={"center"}>
Penguatan Pengawasan
</Text>
<Text
py={10}
ta={"justify"}
>
Sebagai bagian dari Program Desa Anti Korupsi, Desa Darmasaba menerapkan penguatan pengawasan untuk memastikan transparansi dan akuntabilitas dalam pengelolaan pemerintahan desa. Sistem pengawasan ini melibatkan berbagai pihak, termasuk aparat desa, masyarakat, dan lembaga terkait, agar tidak ada celah bagi praktik korupsi. Adapun beberapa Penguatan Pengawasan :
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 2,
}}>
{data.map((v, k) => {
return (
<Box
key={k}
>
<Paper p={"lg"} >
<Stack h="100%">
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex direction={{ base: "column", md: "row" }} align="center" justify="center" style={{ flex: 1 }}>
<Box mb={{ base: 10, md: 0 }}>
{v.icon}
</Box>
<Stack px={{ base: 0, md: 20 }} gap={10} style={{ flex: 1 }}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Button bg={colors["blue-button"]} radius={"lg"} w="fit-content">Download</Button>
</Stack>
</Flex>
</Stack>
</Paper>
</Box>
);
})}
</SimpleGrid >
</Stack >
);
}
export default Pengawasan;

View File

@@ -1,90 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconClipboardText } from '@tabler/icons-react';
const data = [
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.1 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.2 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.3 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.4 PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.5 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
{
judul: "PENGUATAN TATA LAKSANA",
deskripsi: "1.6 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
icon: <IconClipboardText size={100} color={colors["blue-button"]} />,
},
]
function Tatalaksana() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"} ta={"center"}>
Penguatan Tata Laksana
</Text>
<Text
py={10}
ta={"justify"}
>
Sebagai bagian dari Program Desa Anti Korupsi, Desa Darmasaba menerapkan penguatan tata laksana untuk memastikan transparansi, akuntabilitas, dan efisiensi dalam pemerintahan desa. Adapun beberapa Penguatan Tata Laksana :
</Text>
</Stack>
</Container>
<SimpleGrid
px={{ base: "md", md: 100 }}
cols={{
base: 1,
sm: 2,
}}>
{data.map((v, k) => {
return (
<Box
key={k}
>
<Paper p={"lg"} >
<Stack h="100%">
<Text fz={"lg"} ta={"center"} c={colors["blue-button"]}>{v.judul}</Text>
<Flex direction={{ base: "column", md: "row" }} align="center" justify="center" style={{ flex: 1 }}>
<Box mb={{ base: 10, md: 0 }}>
{v.icon}
</Box>
<Stack px={{ base: 0, md: 20 }} gap={10} style={{ flex: 1 }}>
<Text fz={"sm"} ta={"justify"}>{v.deskripsi}</Text>
<Button bg={colors["blue-button"]} radius={"lg"} w="fit-content">Download</Button>
</Stack>
</Flex>
</Stack>
</Paper>
</Box>
);
})}
</SimpleGrid >
</Stack >
);
}
export default Tatalaksana;

View File

@@ -0,0 +1,149 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client';
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import React, { useEffect, useState } from 'react';
import { Box, Button, Container, Flex, Paper, SimpleGrid, Stack, Text, ActionIcon } from '@mantine/core';
import { IconFile } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
function Lokal() {
const [selectedKategori, setSelectedKategori] = useState<string>('PENGUATAN TATA LAKSANA');
const [loading, setLoading] = useState(true);
const state = useProxy(korupsiState);
// Load data on component mount
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.desaAntikorupsi.findMany.load(1, 100); // Load first 100 items
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, []);
// Get data from state
const data = state.desaAntikorupsi.findMany.data || [];
// Debug: Log the complete data structure
console.log('Complete data:', JSON.parse(JSON.stringify(data)));
// Get unique categories
const categories = [...new Set(
data
.filter(item => item.kategori?.name) // Only include items with a category name
.map(item => item.kategori.name)
)];
// Filter data based on selected category
const filteredData = selectedKategori === 'PENGUATAN TATA LAKSANA'
? data
: data.filter(item => item.kategori?.name === selectedKategori);
// Debug: Log filtered data
console.log('Filtered data:', JSON.parse(JSON.stringify(filteredData)));
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap={22}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "80%" }}>
<Stack align="center" gap={0}>
<Text fz={"3.4rem"} fw={"bold"}>
Desa Anti Korupsi
</Text>
<Text
py={10}
ta={"justify"}
>
Desa antikorupsi mendorong pemerintahan jujur dan transparan. Keuangan desa dikelola terbuka dengan melibatkan warga mengawasi anggaran, sehingga digunakan tepat sasaran sesuai kebutuhan. Adapun beberapa jenis tata penguatan :
</Text>
</Stack>
</Container>
<Container size="lg" px={{ base: "md", md: "xl" }}>
{/* Category Filter Buttons */}
<Flex gap="md" justify="center" mb="xl" wrap="wrap">
{categories.map((kategori) => (
<Button
color={selectedKategori === kategori ? colors['blue-button'] : 'gray'}
key={kategori}
variant={selectedKategori === kategori ? 'filled' : 'outline'}
onClick={() => setSelectedKategori(kategori)}
size="sm"
>
{kategori}
</Button>
))}
</Flex>
{/* Loading State */}
{loading ? (
<Text ta="center">Memuat data...</Text>
) : (
<SimpleGrid
cols={{ base: 1, sm: 2, lg: 3 }}
spacing="xl"
verticalSpacing="xl"
>
{filteredData.map((item) => {
console.log('Item data:', item);
console.log('All item properties:', Object.keys(item).map(key => `${key}: ${item[key]}`).join(', '));
const handleDownload = async (e: React.MouseEvent) => {
e.stopPropagation();
if (!item?.file?.link) return;
try {
const fileUrl = item.file.link.startsWith('http')
? item.file.link
: `${window.location.origin}${item.file.link.startsWith('/') ? '' : '/'}${item.file.link}`;
window.open(fileUrl, '_blank');
} catch (error) {
console.error('Error opening file:', error);
}
};
return (
<Paper key={item.id} p="lg" shadow="sm" radius="md" withBorder>
<Stack h="100%" justify="space-between">
<div>
<Text fz="lg" fw={600} mb="sm" c={colors["blue-button"]}>
{item.name}
</Text>
</div>
{item?.file && (
<ActionIcon
variant="filled"
color={colors["blue-button"]}
size="lg"
onClick={handleDownload}
style={{ marginTop: 10 }}
title="Unduh File"
>
<IconFile size={20} />
</ActionIcon>
)}
</Stack>
</Paper>
);
})}
</SimpleGrid>
)}
</Container>
</Stack>
)
}
export default Lokal;

View File

@@ -69,7 +69,7 @@ function Page() {
<Box>
{v.icon}
</Box>
<Button fz={"h4"} color={colors["blue-button"]} onClick={() => router.push(v.link)}>Detail</Button>
<Button fz={"h4"} color={colors["blue-button"]} onClick={() => router.push("/darmasaba/desa-anti-korupsi/detail")}>Detail</Button>
</Stack>
</Box>
</Paper>

View File

@@ -1,64 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Stack, Container, Box, Text, Image, ActionIcon, Divider, Flex } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React from 'react';
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} c={colors["blue-button"]} fw={"bold"}>
Peringkat 5 Dalam Ajang Bergengsi Mangupura Award
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Tata Kelola dan Inovasi Desa
</Text>
</Box>
<Image src="/api/img/prestasi-peringkat-5.jpeg" alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Pada hari Senin, 18 November 2024, Desa Darmasaba kembali mencetak prestasi gemilang dengan meraih peringkat V dalam ajang bergengsi Mangupura Award. Penganugerahan ini berlangsung dalam rangkaian acara HUT Kota Mangupura ke-15 yang dihadiri oleh Drs. I Ketut Suiasa, S.H., Plt Bupati Badung, serta para tokoh penting lainnya.
</Text>
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Penghargaan ini bukan hanya sebuah pengakuan, tetapi juga menjadi motivasi bagi Desa Darmasaba untuk terus meningkatkan inovasi, pelayanan, pembangunan serta transformasi digital.
Mari bersama kita lanjutkan semangat kolaborasi dan inovasi untuk menjadikan Darmasaba sebagai desa yang semakin unggul dan inspiratif!
</Text>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Bersama, kita terus wujudkan Desa Darmasaba yang sadar hukum dan berprestasi!
</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={'md'}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,64 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Stack, Container, Box, Text, Image, ActionIcon, Divider, Flex } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React from 'react';
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }} c={colors["blue-button"]} fw={"bold"}>
Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Hukum dan Kesadaran Masyarakat
</Text>
</Box>
<Image src="/api/img/prestasilombahukum.png" alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Dengan bangga, KADARKUM Desa Darmasaba berhasil meraih Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024.
Prestasi ini tidak lepas dari dukungan penuh dan semangat tinggi dari Ida Bagus Surya Prabhawa Manuaba, S.H., M.H., NL.P. selaku Perbekel Darmasaba yang selalu mendampingi dari proses pelatihan hingga perlombaan, serta turut hadir perwakilan Kecamatan Abiansemal, Ketua BPD Desa Darmasaba, Ketua TP PKK Desa Darmasaba, Perangkat & Staf Desa Darmasaba, Karang Taruna Paramartha Dharma Desa Darmasaba dan seluruh Tim Sukses KADARKUM Desa Darmasaba.
</Text>
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Acara yang berlangsung pada 14 November 2024 di Kertha Gosana Lt. 3 Pusat Pemerintahan Kabupaten Badung ini menjadi bukti nyata bahwa kerja keras dan kolaborasi adalah kunci keberhasilan.
</Text>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Bersama, kita terus wujudkan Desa Darmasaba yang sadar hukum dan berprestasi!
</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={'md'}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,62 +0,0 @@
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { Stack, Container, Box, Text, Image, ActionIcon, Divider, Flex } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React from 'react';
function Page() {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} c={colors["blue-button"]} fw={"bold"}>
Juara 3 Turnamen Bola Voli Mangupura Cup 2024
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Olahraga dan Kepemudaan
</Text>
</Box>
<Image src="/api/img/prestasi-voli.jpeg" alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Selamat kepada Tim Bola Voli Putri Dharma Temaja yang berhasil meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali.
Perjuangan luar biasa yang ditunjukkan oleh tim ini merupakan bukti nyata kerja keras, kekompakan, dan semangat pantang menyerah. Dalam kompetisi yang diikuti oleh tim-tim terbaik dari seluruh Bali, Tim Dharma Temaja berhasil menunjukkan performa yang menginspirasi dan mengharumkan nama desa.
</Text>
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Terima kasih kepada seluruh pemain, pelatih, serta pendukung yang selalu memberikan dukungan penuh di setiap pertandingan. Kemenangan ini adalah awal dari perjalanan yang lebih besar, dan semoga prestasi ini dapat terus memotivasi kita untuk mencapai hasil yang lebih gemilang di masa depan.
</Text>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
Mari kita terus dukung Tim Bola Voli Putri Dharma Temaja agar semakin bersinar di ajang-ajang berikutnya
</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={'md'}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,147 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa';
import BackButton from '@/app/darmasaba/(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
import { ActionIcon, Box, Container, Divider, Flex, Image, Skeleton, Stack, Text } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import { useParams, usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function Page() {
const params = useParams();
const pathname = usePathname();
const [baseUrl, setBaseUrl] = useState('');
const state = useProxy(prestasiState.prestasiDesa);
// Get base URL for sharing
useEffect(() => {
if (typeof window !== 'undefined') {
setBaseUrl(window.location.origin);
}
}, []);
useEffect(() => {
prestasiState.kategoriPrestasi.findMany.load()
const loadData = async () => {
try {
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
}
}
loadData();
}, [])
useEffect(() => {
state.findUnique.load(params?.id as string);
}, [params?.id]);
if (!state.findUnique.data) {
return (
<Stack py={10} px={{ base: "md", md: 100 }}>
<Skeleton h={40} />
<Skeleton h={300} mt="md" />
</Stack>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
{state.findUnique.data && (
<>
<Box pb={20} key={state.findUnique.data.id}>
<Text
ta={"center"}
fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}}
c={colors["blue-button"]}
fw={"bold"}
>
{state.findUnique.data.name}
</Text>
<Text ta={"center"} fw={"bold"} fz={"1.5rem"}>
{state.findUnique.data.kategori?.name}
</Text>
</Box>
{state.findUnique.data.image?.link && (
<Image
src={state.findUnique.data.image.link}
alt={state.findUnique.data.name || 'Prestasi'}
w={"100%"}
style={{ maxHeight: '500px', objectFit: 'cover' }}
/>
)}
<Box mt="md">
<Text dangerouslySetInnerHTML={{ __html: state.findUnique.data.deskripsi || '' }} />
<Text mt="md" c="dimmed" size="sm">
Dibuat pada: {new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID')}
</Text>
</Box>
</>
)}
</Container>
<Box px={{ base: "md", md: 100 }}>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz="md">
Dibuat pada: {state.findUnique.data?.createdAt ? new Date(state.findUnique.data.createdAt).toLocaleDateString('id-ID', {
day: 'numeric',
month: 'long',
year: 'numeric'
}) : '-'}
</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon
variant="transparent"
component="a"
href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(`${baseUrl}${pathname}`)}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Facebook"
>
<IconBrandFacebook color={colors["blue-button"]} size={30} />
</ActionIcon>
<ActionIcon
variant="transparent"
component="a"
href={`https://www.instagram.com/?url=${encodeURIComponent(`${baseUrl}${pathname}`)}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Instagram"
>
<IconBrandInstagram color={colors["blue-button"]} size={30} />
</ActionIcon>
<ActionIcon
variant="transparent"
component="a"
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(`${baseUrl}${pathname}`)}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Twitter"
>
<IconBrandTwitter color={colors["blue-button"]} size={30} />
</ActionIcon>
<ActionIcon
variant="transparent"
component="a"
href={`https://wa.me/?text=${encodeURIComponent(`${baseUrl}${pathname}`)}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on WhatsApp"
>
<IconBrandWhatsapp color={colors["blue-button"]} size={30} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,34 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Container, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { BackgroundImage, Box, Button, Center, Container, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import { useRouter } from 'next/navigation';
import prestasiState from '@/app/admin/(dashboard)/_state/landing-page/prestasi-desa';
import { useProxy } from 'valtio/utils';
import { useEffect, useState } from 'react';
const data = [
{
id: 1,
title: "Olahraga dan Kepemudaan",
description: "Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali ",
image: "/api/img/prestasi-voli.jpeg",
link: "/darmasaba/prestasi-desa/tim-bola-voli-putri-dharma-temaja-meraih-juara-3-dalam-turnamen-bola-voli-mangupura-cup-2024-kategori-putri-se-bali"
},
{
id: 2,
title: "Hukum dan Kesadaran Masyarakat",
description: "Prestasi Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024",
image: "/api/img/prestasilombahukum.png",
link: "/darmasaba/prestasi-desa/prestasi-juara-3-dalam-lomba-keluarga-sadar-hukum-kabupaten-badung-tahun-2024"
},
{
id: 3,
title: "Tata Kelola dan Inovasi Desa",
description: "Peringkat 5 Dalam Ajang Bergengsi Mangupura Award",
image: "/api/img/prestasi-peringkat-5.jpeg",
link: "/darmasaba/prestasi-desa/peringkat-5-dalam-ajang-bergengsi-mangupura-award"
}
]
function Page() {
const state = useProxy(prestasiState.prestasiDesa);
const [loading, setLoading] = useState(true);
const [prestasiData, setPrestasiData] = useState<any[]>([]);
const router = useRouter();
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await prestasiState.kategoriPrestasi.findMany.load();
await state.findMany.load();
setPrestasiData(state.findMany.data || []);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, []);
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} >
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
@@ -48,12 +50,17 @@ function Page() {
md: 3,
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image}
h={350}
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
prestasiData.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link}
radius={16}
pos={"relative"}
>
@@ -70,7 +77,7 @@ function Page() {
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Group>
<Paper radius={"lg"} py={7} px={10}>
<Text>{v.title}</Text>
<Text>{v.kategori?.name}</Text>
</Paper>
</Group>
<Box p={"lg"}>
@@ -80,18 +87,27 @@ function Page() {
size={"1.8rem"}
style={{
textAlign: "center",
}}>{v.description}</Text>
lineHeight: 1.4,
minHeight: '5.4rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
lineClamp={3}
dangerouslySetInnerHTML={{ __html: v.deskripsi }}
/>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
onClick={() => router.push(`/darmasaba/prestasi-desa/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
})
)}
</SimpleGrid>
</Stack>
);

View File

@@ -1,11 +1,43 @@
import { Box, Center, Container, Image, Stack, Text } from '@mantine/core';
'use client'
import { Box, Center, Container, Image, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Prisma } from '@prisma/client';
import { useEffect, useState } from 'react';
import BackButton from '../../(pages)/desa/layanan/_com/BackButto';
import colors from '@/con/colors';
function Page() {
return (
const [sdgsDesa, setSdgsDesa] = useState<Prisma.SDGSDesaGetPayload<{ include: { image: true } }>[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSdgsDesa = async () => {
try {
const response = await fetch('/api/landingpage/sdgsdesa/findMany');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Ensure the data is an array before setting it
let data = [];
if (Array.isArray(result.data)) {
data = result.data;
} else if (Array.isArray(result)) {
// In case the API returns the array directly
data = result;
} else {
console.error('Invalid data format:', result);
}
setSdgsDesa(data);
} catch (error) {
console.error('Error fetching sdgs desa:', error);
} finally {
setLoading(false);
}
};
fetchSdgsDesa();
}, []);
return (
<Stack pos={"relative"} py={"xl"} gap={22}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
@@ -28,12 +60,70 @@ function Page() {
>
SDGs Desa sebagaimana dijabarkan dalam Permendesa Nomor 21 tahun 2020 terdiri dari 18 tujuan yang harus dicapai pada tahun 2030. Tujuan-tujuan tersebut mencakup berbagai aspek kehidupan masyarakat desa, mulai dari pengentasan kemiskinan, peningkatan kesehatan dan pendidikan, kesetaraan gender, pertumbuhan ekonomi, infrastruktur, hingga kelestarian lingkungan. Adapun SDGs Desa terdiri dari tujuan-tujuan sebagai berikut:
</Text>
<Center>
<Image src={"/api/img/sdgsdesa-18-removebg.png"} alt='' w={800} />
</Center>
</Box >
<Box py={20}>
<Box py={20} px={{ base: "md", md: 100 }}>
<Box pos="relative" style={{ minHeight: 200 }}>
<LoadingOverlay visible={loading} overlayProps={{ blur: 2 }} />
{!loading && sdgsDesa.length > 0 ? (
<SimpleGrid
cols={{ base: 1, sm: 2, md: 3, lg: 4 }}
spacing="xl"
verticalSpacing="xl"
>
{sdgsDesa.map((item) => (
<Paper
key={item.id}
p="md"
radius="md"
shadow="sm"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
transition: 'transform 0.2s',
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: '0 8px 16px rgba(0,0,0,0.1)'
}
}}
>
<Box
p="md"
style={{
backgroundColor: '#f8f9fa',
borderRadius: '8px',
width: '100%',
display: 'flex',
justifyContent: 'center',
marginBottom: '1rem',
}}
>
<Image
src={item.image?.link || '/placeholder-sdgs.png'}
alt={item.name}
width={120}
height={120}
fit="contain"
/>
</Box>
<Stack gap="xs" style={{ width: '100%' }}>
<Title order={4} ta="center" c={"dimmed"} fw={600} lineClamp={2} style={{ minHeight: '3rem' }}>
{item.name}
</Title>
<Text ta="center" fw={"bold"} c={colors['blue-button']} fz={"h2"} lineClamp={3} style={{ minHeight: '4.5rem' }}>
{item.jumlah}
</Text>
</Stack>
</Paper>
))}
</SimpleGrid>
) : !loading ? (
<Center style={{ minHeight: 200 }}>
<Text>Tidak ada data SDGs Desa yang tersedia</Text>
</Center>
) : null}
</Box>
</Box>
</Stack >
);

View File

@@ -112,7 +112,7 @@ function Footer() {
</Box>
</Paper>
</Center>
<Box py={20}>
<Box py={20} >
<SimpleGrid
p={20}
cols={{
@@ -123,10 +123,10 @@ function Footer() {
color: "white"
}}
>
<Box p={mobile ? 30 : 30}>
<Box p={mobile ? 30 : 30} style={{color: "white"}}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"}>Tentang Darmasaba</Text>
<Text fz={"xs"} >Desa Darmasaba adalah desa
<Text fz={"md"} fw={"bold"} c={"white"}>Tentang Darmasaba</Text>
<Text fz={"xs"} c={"white"}>Desa Darmasaba adalah desa
budaya yang kaya akan tradisi dan
nilai-nilai luhur masyarakat Bali.</Text>
<Box>
@@ -149,42 +149,42 @@ function Footer() {
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"}>Layanan</Text>
<Text fz={"md"} fw={"bold"} c={"white"}>Layanan</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Administrasi Kependudukan</Text>
<Text fz={"xs"} c={"white"}>Administrasi Kependudukan</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Pelayanan Sosial</Text>
<Text fz={"xs"} c={"white"}>Pelayanan Sosial</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Pengaduan Masyarakat</Text>
<Text fz={"xs"} c={"white"}>Pengaduan Masyarakat</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Informasi Publik</Text>
<Text fz={"xs"} c={"white"}>Informasi Publik</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between' gap={"xs"}>
<Text fz={"md"} fw={"bold"}>Tautan Penting</Text>
<Text fz={"md"} fw={"bold"} c={"white"}>Tautan Penting</Text>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Portal Badung</Text>
<Text fz={"xs"} c={"white"}>Portal Badung</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >E-Government</Text>
<Text fz={"xs"} c={"white"}>E-Government</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Transparansi</Text>
<Text fz={"xs"} c={"white"}>Transparansi</Text>
</Anchor>
<Anchor c={"white"} variant='transparent'>
<Text fz={"xs"} >Unduhan</Text>
<Text fz={"xs"} c={"white"}>Unduhan</Text>
</Anchor>
</Stack>
</Box>
<Box p={mobile ? 30 : 30}>
<Stack justify='space-between'>
<Text fz={"md"} fw={"bold"}>Newsletter</Text>
<Text fz={"xs"} >Dapatkan informasi terbaru
<Text fz={"md"} fw={"bold"} c={"white"}>Newsletter</Text>
<Text fz={"xs"} c={"white"}>Dapatkan informasi terbaru
tentang kegiatan dan program
desa</Text>
<TextInput

View File

@@ -1,34 +1,37 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
import colors from '@/con/colors';
import { ActionIcon, BackgroundImage, Box, Button, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { IconDownload } from '@tabler/icons-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
const data = [
{
id: 1,
title: "Pendapatan",
image: "/api/img/pendapatan.jpeg",
value: "Rp 495M"
},
{
id: 2,
title: "Belanja",
image: "/api/img/belanja.jpeg",
value: "Rp 395M"
},
{
id: 3,
title: "Pembiayaan",
image: "/api/img/pembiayaan.jpeg",
value: "Rp 295M"
},
]
function Apbdes() {
const state = useProxy(apbdes);
const [loading, setLoading] = useState(false);
const textHeading = {
title: "APBDes",
des: "Transparansi APBDes Darmasaba adalah langkah nyata menuju tata kelola pemerintahan desa yang bersih dan bertanggung jawab"
}
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.findMany.load();
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData();
}, [])
const data = (state.findMany.data || []).slice(0, 3);
return (
<>
<Stack p={"sm"} gap={"4rem"} bg={colors.Bg}>
@@ -53,53 +56,59 @@ function Apbdes() {
sm: 3,
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image}
h={350}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
{loading ? (
<Center>
<Text fz={"2.4rem"}>Memuat Data...</Text>
</Center>
) : (
data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={350}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"1.5rem"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.title}</Text>
</Box>
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
style={{
textAlign: "center",
}}>{v.value}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})}
}}>{v.jumlah}</Text>
<Group justify="center">
<ActionIcon px={70} py={20} radius={"xl"} size="md" bg={colors["blue-button"]} component={Link} href={v.file?.link || ''}>
<Flex gap={"md"}>
<IconDownload size={20} />
<Text fz={"sm"} c={"white"}>Download</Text>
</Flex>
</ActionIcon>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
<Group pb={80} justify='center'>
<Button component={Link} href="/darmasaba/apbdes" radius={"lg"} bg={colors["blue-button"]} fz={"h4"}>Lihat Semua</Button>

Some files were not shown because too many files have changed in this diff Show More