Compare commits
8 Commits
nico/1-aug
...
nico/5-aug
| Author | SHA1 | Date | |
|---|---|---|---|
| b62c4be30a | |||
| ab887c30e6 | |||
| 8e76a83d14 | |||
| a2b68ec78b | |||
| 0e55462adc | |||
| 73ae198158 | |||
| 9d14bb0c56 | |||
| 1cdff53c56 |
57
prisma/data/desa/layanan/pelayananSuratKeterangan.json
Normal file
57
prisma/data/desa/layanan/pelayananSuratKeterangan.json
Normal 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 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>"
|
||||
}
|
||||
]
|
||||
20
prisma/data/desa/layanan/pelayananTelunjukSaktiDesa.json
Normal file
20
prisma/data/desa/layanan/pelayananTelunjukSaktiDesa.json
Normal 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"
|
||||
}
|
||||
]
|
||||
22
prisma/data/landing-page/apbdes/apbdes.json
Normal file
22
prisma/data/landing-page/apbdes/apbdes.json
Normal 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"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
20
prisma/data/landing-page/prestasi-desa/prestasi-desa.json
Normal file
20
prisma/data/landing-page/prestasi-desa/prestasi-desa.json
Normal 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"
|
||||
}
|
||||
]
|
||||
114
prisma/data/landing-page/sdgs-desa/sdgs-desa.json
Normal file
114
prisma/data/landing-page/sdgs-desa/sdgs-desa.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
63
src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx
Normal file
63
src/app/admin/(dashboard)/desa/potensi/_lib/layoutTabs.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
128
src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx
Normal file
128
src/app/admin/(dashboard)/desa/potensi/kategori-potensi/page.tsx
Normal 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;
|
||||
14
src/app/admin/(dashboard)/desa/potensi/layout.tsx
Normal file
14
src/app/admin/(dashboard)/desa/potensi/layout.tsx
Normal 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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
@@ -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;
|
||||
158
src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx
Normal file
158
src/app/admin/(dashboard)/desa/potensi/list-potensi/page.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -68,7 +68,7 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={550} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={550} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -57,7 +57,7 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={550} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
<Skeleton height={550} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ function EditKolaborasiInovasi() {
|
||||
<iframe
|
||||
src={previewImage}
|
||||
width="100%"
|
||||
height="500px"
|
||||
height="250px"
|
||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -110,7 +110,7 @@ function CreateSDGsDesa() {
|
||||
<iframe
|
||||
src={previewImage}
|
||||
width="100%"
|
||||
height="500px"
|
||||
height="250px"
|
||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ export default async function pelayananSuratKeteranganFindUnique(request: Reques
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
image2: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ const potensiDesaDelete = async (context: Context) => {
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
kategori: true
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export default async function findUnique(
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
kategori: true
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -11,7 +11,7 @@ const SDGSDesa = new Elysia({
|
||||
})
|
||||
|
||||
// ✅ Find all
|
||||
.get("/find-many", sdgsDesaFindMany)
|
||||
.get("/findMany", sdgsDesaFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
.get("/:id", sdgsDesaFindUnique)
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
106
src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx
Normal file
106
src/app/darmasaba/(pages)/desa/layanan/[id]/page.tsx
Normal 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;
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
77
src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx
Normal file
77
src/app/darmasaba/(pages)/desa/potensi/[id]/page.tsx
Normal 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;
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
149
src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx
Normal file
149
src/app/darmasaba/(tambahan)/desa-anti-korupsi/detail/page.tsx
Normal 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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
147
src/app/darmasaba/(tambahan)/prestasi-desa/[id]/page.tsx
Normal file
147
src/app/darmasaba/(tambahan)/prestasi-desa/[id]/page.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 >
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user