Compare commits

..

38 Commits

Author SHA1 Message Date
7d58513e33 UI admin bagian pengumuman dan backendnya 2025-06-04 17:24:32 +08:00
f56c5b3532 Tambahan UI Admin : Dibagian Desa Sisa Profile, Potensi, Penghargaan 2025-06-04 15:08:12 +08:00
06622c49e8 Tampilan UI Admin Ekonomi & Inovasi Progress 2025-06-04 14:02:39 +08:00
a1e7fddbed UI Admin Revisi Baru Di Menu Ekonomi 2025-06-03 17:42:34 +08:00
423ad0e2ba Tambahan 2025-06-03 12:10:00 +08:00
084435500f Lanjutan UI 2025-06-03 10:21:06 +08:00
5037009c40 Kesehatan : udah fix semua, cuma dibagian kayak detail sama edit uinya belum 2025-06-02 22:23:13 +08:00
8f2b9665a9 Jum'at, 30 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu inovasi
* API Create, edit dan delete potensi
* Tampilan UI Landing Page sudah sesuai di mobile

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu lingkungan
* Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin Di Menu Pendidikan
2025-05-30 21:13:55 +08:00
77f99a7c8f Jum'at, 30 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu inovasi
* API Create, edit dan delete potensi

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu lingkungan
* Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin Di Menu Pendidikan
2025-05-30 16:57:41 +08:00
d88f168258 Jumat, 30 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu inovasi
* API Create, edit dan delete potensi

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu Lingkungan

Yang Akan Dikerjakan:
* API Menu Lain
* Tampilan UI Admin Di Menu Pendidikan
2025-05-30 11:22:31 +08:00
f9bd2cea11 tambahan 2025-05-27 16:18:23 +08:00
5734e5d9a7 Selasa, 27 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu Inovasi
* Progress API ProfilePPID

Yang Akan Dikerjakan:
* API Menu Lain
* Tampilan UI Admin Di Menu Lingkungan
* Tampilan UI Admin Di Menu Pendidikan
2025-05-27 11:23:20 +08:00
3654629bde Senin, 26 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Akan Dikerjakan:
* API ProfilePPID
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 17:15:07 +08:00
02738104b5 Senin, 26 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, dan delete berita

Yang Akan Dikerjakan:
* API Di Menu Desa : Edit Berita
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 14:26:10 +08:00
92de697ae0 Nico-25 Mei 2025:
Membuat create berita dan gambar
Membuat fungsi tombol di mana bisa menghapus konten sesuai idnya
2025-05-25 11:33:50 +08:00
cf6a5422ec Bagian Berita Di Admin Sudah Bisa Upload Di Bun Dev atau Bun Start 2025-05-23 16:30:46 +08:00
ee9368e911 UI Admin Keamanan 2025-05-23 11:33:59 +08:00
cab86eb02f Nico 21 Mei :
Yang udah dikerjakan:
Tampilan UI Menu DesaTampilan UI Menu Kesehatan

Yang Akan dikerjakan:
Tampilan UI Keamanan, dan Menu Selanjutnya
Memperbaiki eror di bagian inputan dimana saat nginput data outputnya ikut ke ganti seharusnya di submit dulu
2025-05-21 11:28:27 +08:00
d1e39ae7f9 Senin, 20 May 2025 : 2025-05-20 12:07:00 +08:00
d3a43c72ab Senin, 19 May 2025 :
Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
- API Di Menu Desa
- Tampilan UI Admin Di Menu Keamanan
2025-05-19 17:00:43 +08:00
f5d68d4982 UI Admin Menu Desa Sub Menu Profile 2025-05-15 17:47:43 +08:00
2844132ea0 Profile PPID, Visi Misi, dan Dasar Hukum 2025-05-13 17:54:23 +08:00
e5889ca903 ProfilePPID bagian Profile, VisiMisiPPD sudah ada seed dan bisa di edit 2025-05-13 11:51:48 +08:00
8a34a122d0 Tambahan Di menu PPID 2025-05-09 10:35:30 +08:00
795c79dd5f Tambahan Admin Di Menu PPID 2025-05-06 13:57:33 +08:00
a8556aacb7 Fix Eror Admin Persentase & Grafik 2025-05-02 16:08:30 +08:00
ea7de13d28 Admin Dashboard Bagian Data Kesehatan 2025-04-30 16:31:18 +08:00
e9d94cfa83 Ngerjain Tabs Jadwal Kegiatan di Data Kesehatan 2025-04-29 11:01:09 +08:00
3f5d607e83 Sudah Dibuatkan Inputan Data Di Data Kesehatan Warga Fasilitas Kesehatan 2025-04-28 10:21:59 +08:00
d575c9c792 Create & Read Data :
* Menu Desa : Berita & Pengumuman
* Kesehatan : Data Kesehatan Warga => Fasilitas Kesehatan
2025-04-25 10:24:52 +08:00
9a6f8dc7f6 Membuat database menu desa: Berita & Pengummuman 2025-04-22 21:48:01 +08:00
7b0fb9332e Dashboard Admin 2025-04-21 17:49:13 +08:00
cdfbbb412c Rabu 16 April 2025
Yang Sudah dikerjakan:
- Feature Desa :
- Pengumuman :
- Pada bagian kategori pengumuman sudah dibuatkan konten sesuai dengan kategorinya.
- Pada bagian pengumuman penting juga sudah dibuatkan kontennya
- Gallery:
- Sudah dibuatkan kontennya sesuai dengan tabnya
2025-04-17 10:11:12 +08:00
1138fe14d0 Fix responsive lading page, menu ppid, dan menu desa 2025-04-15 10:08:12 +08:00
4bd9ef6acf UI Menu Pendidikan 2025-04-10 15:31:59 +08:00
4824e4e848 UI Sub Menu Keamanan 2025-03-26 12:06:55 +08:00
8b26a91ce9 tamabah versi 2025-03-24 15:34:38 +08:00
e33db73d65 UI Menu Keamanan 2025-03-24 15:31:59 +08:00
602 changed files with 31979 additions and 1378 deletions

BIN
bun.lockb

Binary file not shown.

BIN
foldergambar/desa/name.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.0",
"version": "0.1.2",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -16,41 +16,60 @@
"@cubejs-client/core": "^0.31.0",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/eden": "^1.2.0",
"@elysiajs/static": "^1.3.0",
"@elysiajs/stream": "^1.1.0",
"@elysiajs/swagger": "^1.2.0",
"@mantine/carousel": "^7.16.2",
"@mantine/charts": "^7.17.1",
"@mantine/core": "^7.16.2",
"@mantine/core": "^7.17.4",
"@mantine/dates": "^7.17.4",
"@mantine/dropzone": "^7.17.0",
"@mantine/hooks": "^7.16.2",
"@mantine/hooks": "^7.17.4",
"@mantine/tiptap": "^7.17.4",
"@paljs/types": "^8.1.0",
"@prisma/client": "^6.3.1",
"@tabler/icons-react": "^3.30.0",
"@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-link": "^2.11.7",
"@tiptap/extension-subscript": "^2.11.7",
"@tiptap/extension-superscript": "^2.11.7",
"@tiptap/extension-text-align": "^2.11.7",
"@tiptap/extension-underline": "^2.11.7",
"@tiptap/pm": "^2.11.7",
"@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
"@types/bun": "^1.2.2",
"@types/lodash": "^4.17.15",
"@types/lodash": "^4.17.16",
"add": "^2.0.6",
"animate.css": "^4.1.1",
"bun": "^1.2.2",
"chart.js": "^4.4.8",
"dayjs": "^1.11.13",
"elysia": "^1.2.12",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^7.1.0",
"form-data": "^4.0.2",
"framer-motion": "^12.4.1",
"get-port": "^7.1.0",
"jotai": "^2.12.3",
"lodash": "^4.17.21",
"motion": "^12.4.1",
"nanoid": "^5.1.0",
"nanoid": "^5.1.5",
"next": "15.1.6",
"next-view-transitions": "^0.3.4",
"node-fetch": "^3.3.2",
"p-limit": "^6.2.0",
"prisma": "^6.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-simple-toasts": "^6.1.0",
"react-toastify": "^11.0.5",
"readdirp": "^4.1.1",
"recharts": "2",
"recharts": "^2.15.3",
"swr": "^2.3.2",
"valtio": "^2.1.3"
"uuid": "^11.1.0",
"valtio": "^2.1.3",
"zod": "^3.24.3"
},
"devDependencies": {
"@eslint/eslintrc": "^3",

View File

@@ -0,0 +1,8 @@
[
{ "name": "Sosial & Kesehatan" },
{ "name": "Ekonomi & UMKM" },
{ "name": "Pendidikan & Kepemudaan" },
{ "name": "Lingkungan & Bencana" },
{ "name": "Adat & Budaya" },
{ "name": "Digitalisasi Desa" }
]

View File

@@ -0,0 +1,9 @@
[
{
"id": "1",
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
"pengalaman": "<ul><li>2021 - 2027: Perbekel Desa Darmasaba</li><li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li><li>2020 - Sekarang: Founder Ugawa Record Music Studio</li><li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li></ul>",
"pengalamanOrganisasi": "<ul> <li>1996 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 2028: Komite Tetap Advokasi Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
"programUnggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>"
}
]

View File

@@ -0,0 +1,11 @@
[
{
"id": "1",
"sejarah" : "<p>Asal usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>",
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
"lambang" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
"maskot" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>",
"profilPerbekelId" : "1"
}
]

View File

@@ -0,0 +1,9 @@
[
{ "name": "Semua" },
{ "name": "Pemerintahan" },
{ "name": "Pembangunan" },
{ "name": "Ekonomi" },
{ "name": "Sosial" },
{ "name": "Budaya" },
{ "name": "Teknologi" }
]

View File

@@ -0,0 +1,5 @@
[
{"name": "Melihat/Membaca/Mendengarkan/Mencatat"},
{"name": "Mendapatkan Salinan Informasi (Hardcopy)"},
{"name": "Mendapatkan Salinan Informasi (Softcopy)"}
]

View File

@@ -0,0 +1,5 @@
[
{ "name": "Mengambil Langsung" },
{ "name": "Dikirim Via Post" },
{ "name": "Dikirim Via Email" }
]

View File

@@ -0,0 +1,6 @@
[
{ "name": "Keuangan Desa" },
{ "name": "Pembangunan Desa" },
{ "name": "Data Demografi" },
{ "name": "Lainnya" }
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "1",
"jenisInformasi": "Peraturan Desa",
"deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa",
"tanggal": "15 Januari 2024"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "1",
"judul": "DASAR HUKUM PEMBENTUKAN PPID DESA DARMASABA",
"content" : "<ul><li>UU Nomor 14 Tahun 2008 tentang Keterbukaan Informasi Publik</li><li>PP Nomor 61 Tahun 2010 tentang Pelaksanaan UU 14 Tahun 2008 tentang Keterbukaan Informasi Publik</li><li>Permendagri Nomor 3 Tahun 2017 tentang Pedoman Pengelolaan Pelayanan Informasi dan Dokumentasi di Lingkungan Kemendagri dan Pemerintah Daerah</li><li>Peraturan Komisi Informasi Nomor 1 Tahun 2010 tentang Standar Layanan Informasi Publik</li><li>Peraturan Komisi Informasi Nomor 1 Tahun 2010 tentang Standar Layanan Informasi Publik</li><li>Peraturan Bupati Badung No. 42 Tahun 2017 tentang Pedoman Pengelolaan Pelayanan Informasi Publik dan Dokumentasi di Lingkungan Pemerintah Kabupaten Badung</li><li>Keputusan Bupati Badung Nomor 99/049/HK/2019 tentang Pengelola Layanan Informasi dan Dokumentasi Kabupaten Badung</li><li>Keputusan Perbekel Darmasaba Nomor 101 Tahun 2019 tentang Penetapan Pelaksana Teknis/Administrasi Pengelola Layanan Informasi Dan Dokumentasi di Desa Punggul</li><li>Peraturan Perbekel Darmasaba Nomor 12 Tahun 2019 tentang Pedoman Pengelolaan Pelayanan Informasi Publik dan Dokumentasi di Lingkungan Pemerintah Desa Darmasaba</li></ul>"
}
]

View File

@@ -0,0 +1,11 @@
[
{
"id": "1",
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
"pengalaman": "<ul> <li>1996 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 2028: Komite Tetap Advokasi Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>",
"imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png"
}
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "1",
"misi": "<ol><li>Meningkatkan pengelolaan dan pelayanan informasi yang berkualitas, benar dan bertanggung jawab.</li><li>Membangun dan mengembangkan sistem penyediaan dan layanan informasi.</li><li>Meningkatkan dan mengembangkan kompetensi dan kualitas SDM dalam bidang pelayanan informasi.</li><li>Mewujudkan keterbukaan informasi Pemerintah Desa Punggul dengan proses yang cepat, tepat, mudah dan sederhana.</li></ol>",
"visi": "Memberikan pelayanan informasi yanng transparan dan akuntabel untuk memenuhi hak pemohon informasi sesuai dengan ketentuan peraturan perundang-undangan yang berlaku."
}
]

View File

@@ -0,0 +1,355 @@
-- CreateTable
CREATE TABLE "Layanan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "Layanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Potensi" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "Potensi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LandingPage_Layanan" (
"id" TEXT NOT NULL,
"deksripsi" TEXT NOT NULL,
CONSTRAINT "LandingPage_Layanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AppMenu" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"link" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "AppMenu_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AppMenuChild" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"link" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"appMenuId" TEXT,
CONSTRAINT "AppMenuChild_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Berita" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"image" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"katagoryBeritaId" TEXT,
CONSTRAINT "Berita_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KatagoryBerita" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KatagoryBerita_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pengumuman" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"categoryPengumumanId" TEXT,
CONSTRAINT "Pengumuman_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CategoryPengumuman" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "CategoryPengumuman_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Images" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL,
"label" TEXT NOT NULL DEFAULT 'null',
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Images_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Videos" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL,
"label" TEXT NOT NULL DEFAULT 'null',
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Videos_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GalleryFoto" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"image" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"imagesId" TEXT,
CONSTRAINT "GalleryFoto_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GalleryVideo" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"video" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"videosId" TEXT,
CONSTRAINT "GalleryVideo_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DataKematian_Kelahiran" (
"id" SERIAL NOT NULL,
"tahun" TEXT NOT NULL,
"kematianKasar" TEXT NOT NULL,
"kematianBayi" TEXT NOT NULL,
"kelahiranKasar" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FasilitasKesehatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "FasilitasKesehatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InformasiUmum" (
"id" TEXT NOT NULL,
"fasilitas" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"jamOperasional" TEXT NOT NULL,
"fasilitasKesehatanId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "InformasiUmum_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LayananUnggulan" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "LayananUnggulan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DokterdanTenagaMedis" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DokterdanTenagaMedis_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FasilitasPendukung" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "FasilitasPendukung_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProsedurPendaftaran" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProsedurPendaftaran_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_FasilitasKesehatanToLayananUnggulan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_FasilitasKesehatanToFasilitasPendukung" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_FasilitasKesehatanToProsedurPendaftaran" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "Layanan_name_key" ON "Layanan"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Potensi_name_key" ON "Potensi"("name");
-- CreateIndex
CREATE UNIQUE INDEX "AppMenu_name_key" ON "AppMenu"("name");
-- CreateIndex
CREATE UNIQUE INDEX "AppMenuChild_name_key" ON "AppMenuChild"("name");
-- CreateIndex
CREATE UNIQUE INDEX "KatagoryBerita_name_key" ON "KatagoryBerita"("name");
-- CreateIndex
CREATE UNIQUE INDEX "CategoryPengumuman_name_key" ON "CategoryPengumuman"("name");
-- CreateIndex
CREATE UNIQUE INDEX "GalleryFoto_imagesId_key" ON "GalleryFoto"("imagesId");
-- CreateIndex
CREATE UNIQUE INDEX "GalleryVideo_videosId_key" ON "GalleryVideo"("videosId");
-- CreateIndex
CREATE INDEX "_FasilitasKesehatanToLayananUnggulan_B_index" ON "_FasilitasKesehatanToLayananUnggulan"("B");
-- CreateIndex
CREATE INDEX "_FasilitasKesehatanToFasilitasPendukung_B_index" ON "_FasilitasKesehatanToFasilitasPendukung"("B");
-- CreateIndex
CREATE INDEX "_FasilitasKesehatanToProsedurPendaftaran_B_index" ON "_FasilitasKesehatanToProsedurPendaftaran"("B");
-- CreateIndex
CREATE INDEX "_DokterdanTenagaMedisToFasilitasKesehatan_B_index" ON "_DokterdanTenagaMedisToFasilitasKesehatan"("B");
-- AddForeignKey
ALTER TABLE "AppMenuChild" ADD CONSTRAINT "AppMenuChild_appMenuId_fkey" FOREIGN KEY ("appMenuId") REFERENCES "AppMenu"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Berita" ADD CONSTRAINT "Berita_katagoryBeritaId_fkey" FOREIGN KEY ("katagoryBeritaId") REFERENCES "KatagoryBerita"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Pengumuman" ADD CONSTRAINT "Pengumuman_categoryPengumumanId_fkey" FOREIGN KEY ("categoryPengumumanId") REFERENCES "CategoryPengumuman"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GalleryFoto" ADD CONSTRAINT "GalleryFoto_imagesId_fkey" FOREIGN KEY ("imagesId") REFERENCES "Images"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GalleryVideo" ADD CONSTRAINT "GalleryVideo_videosId_fkey" FOREIGN KEY ("videosId") REFERENCES "Videos"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InformasiUmum" ADD CONSTRAINT "InformasiUmum_fasilitasKesehatanId_fkey" FOREIGN KEY ("fasilitasKesehatanId") REFERENCES "FasilitasKesehatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" ADD CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" ADD CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_B_fkey" FOREIGN KEY ("B") REFERENCES "LayananUnggulan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" ADD CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" ADD CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_B_fkey" FOREIGN KEY ("B") REFERENCES "FasilitasPendukung"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" ADD CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" ADD CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_B_fkey" FOREIGN KEY ("B") REFERENCES "ProsedurPendaftaran"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" ADD CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_A_fkey" FOREIGN KEY ("A") REFERENCES "DokterdanTenagaMedis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" ADD CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_B_fkey" FOREIGN KEY ("B") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,582 @@
/*
Warnings:
- You are about to drop the column `image` on the `Berita` table. All the data in the column will be lost.
- You are about to drop the column `katagoryBeritaId` on the `Berita` table. All the data in the column will be lost.
- You are about to drop the column `name` on the `FasilitasPendukung` table. All the data in the column will be lost.
- You are about to drop the column `fasilitasKesehatanId` on the `InformasiUmum` table. All the data in the column will be lost.
- You are about to drop the `KatagoryBerita` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `imageId` to the `Berita` table without a default value. This is not possible if the table is not empty.
- Added the required column `jadwal` to the `DokterdanTenagaMedis` table without a default value. This is not possible if the table is not empty.
- Added the required column `specialist` to the `DokterdanTenagaMedis` table without a default value. This is not possible if the table is not empty.
- Added the required column `content` to the `FasilitasPendukung` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Berita" DROP CONSTRAINT "Berita_katagoryBeritaId_fkey";
-- DropForeignKey
ALTER TABLE "InformasiUmum" DROP CONSTRAINT "InformasiUmum_fasilitasKesehatanId_fkey";
-- AlterTable
ALTER TABLE "Berita" DROP COLUMN "image",
DROP COLUMN "katagoryBeritaId",
ADD COLUMN "imageId" TEXT NOT NULL,
ADD COLUMN "kategoriBeritaId" TEXT;
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "DokterdanTenagaMedis" ADD COLUMN "jadwal" TEXT NOT NULL,
ADD COLUMN "specialist" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "FasilitasPendukung" DROP COLUMN "name",
ADD COLUMN "content" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "InformasiUmum" DROP COLUMN "fasilitasKesehatanId";
-- DropTable
DROP TABLE "KatagoryBerita";
-- CreateTable
CREATE TABLE "FileStorage" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"realName" TEXT NOT NULL,
"path" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
"link" TEXT NOT NULL,
CONSTRAINT "FileStorage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VisiMisiPPID" (
"id" TEXT NOT NULL,
"visi" TEXT NOT NULL,
"misi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "VisiMisiPPID_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DasarHukumPPID" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DasarHukumPPID_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProfilePPID" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"biodata" TEXT NOT NULL,
"riwayat" TEXT NOT NULL,
"pengalaman" TEXT NOT NULL,
"unggulan" TEXT NOT NULL,
"imageUrl" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProfilePPID_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DaftarInformasiPublik" (
"id" TEXT NOT NULL,
"nomor" SERIAL NOT NULL,
"jenisInformasi" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"tanggal" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DaftarInformasiPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PermohonanInformasiPublik" (
"id" TEXT NOT NULL,
"nomor" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"nik" TEXT NOT NULL,
"notelp" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"email" TEXT NOT NULL,
"jenisInformasiDimintaId" TEXT,
"caraMemperolehInformasiId" TEXT,
"caraMemperolehSalinanInformasiId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PermohonanInformasiPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JenisInformasiDiminta" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "JenisInformasiDiminta_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CaraMemperolehInformasi" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "CaraMemperolehInformasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CaraMemperolehSalinanInformasi" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "CaraMemperolehSalinanInformasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FormulirPermohonanKeberatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"notelp" TEXT NOT NULL,
"alasan" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "FormulirPermohonanKeberatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "IndeksKepuasanMasyarakat" (
"id" SERIAL NOT NULL,
"label" TEXT NOT NULL,
"kepuasan" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "IndeksKepuasanMasyarakat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikBerdasarkanJenisKelamin" (
"id" TEXT NOT NULL,
"perempuan" TEXT NOT NULL,
"laki" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "GrafikBerdasarkanJenisKelamin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikBerdasarkanResponden" (
"id" TEXT NOT NULL,
"sangatbaik" TEXT NOT NULL,
"baik" TEXT NOT NULL,
"kurangbaik" TEXT NOT NULL,
"tidakbaik" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "GrafikBerdasarkanResponden_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikBerdasarkanUmur" (
"id" TEXT NOT NULL,
"remaja" TEXT NOT NULL,
"dewasa" TEXT NOT NULL,
"orangtua" TEXT NOT NULL,
"lansia" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "GrafikBerdasarkanUmur_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProfileDesa" (
"id" TEXT NOT NULL,
"sejarah" TEXT NOT NULL,
"visi" TEXT NOT NULL,
"misi" TEXT NOT NULL,
"lambang" TEXT NOT NULL,
"maskot" TEXT NOT NULL,
"profilPerbekelId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProfileDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProfilPerbekel" (
"id" TEXT NOT NULL,
"biodata" TEXT NOT NULL,
"pengalaman" TEXT NOT NULL,
"pengalamanOrganisasi" TEXT NOT NULL,
"programUnggulan" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProfilPerbekel_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KategoriBerita" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KategoriBerita_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PotensiDesa" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kategori" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PotensiDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TarifDanLayanan" (
"id" TEXT NOT NULL,
"layanan" TEXT NOT NULL,
"tarif" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "TarifDanLayanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JadwalKegiatan" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "JadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InformasiJadwalKegiatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"tanggal" TEXT NOT NULL,
"waktu" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "InformasiJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DeskripsiJadwalKegiatan" (
"id" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DeskripsiJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LayananJadwalKegiatan" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "LayananJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SyaratKetentuanJadwalKegiatan" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "SyaratKetentuanJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DokumenJadwalKegiatan" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DokumenJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PendaftaranJadwalKegiatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"tanggal" TEXT NOT NULL,
"namaOrangtua" TEXT NOT NULL,
"nomor" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"catatan" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PendaftaranJadwalKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikKepuasan" (
"id" SERIAL NOT NULL,
"label" TEXT NOT NULL,
"jumlah" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ArtikelKesehatan" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ArtikelKesehatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Introduction" (
"id" SERIAL NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Introduction_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Symptom" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Prevention" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "Prevention_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FirstAid" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "FirstAid_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MythVsFact" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"mitos" TEXT NOT NULL,
"fakta" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MythVsFact_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DoctorSign" (
"id" SERIAL NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DoctorSign_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_FasilitasKesehatanToInformasiUmum" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_FasilitasKesehatanToInformasiUmum_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_FasilitasKesehatanToTarifDanLayanan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "FileStorage_name_key" ON "FileStorage"("name");
-- CreateIndex
CREATE UNIQUE INDEX "JenisInformasiDiminta_name_key" ON "JenisInformasiDiminta"("name");
-- CreateIndex
CREATE UNIQUE INDEX "CaraMemperolehInformasi_name_key" ON "CaraMemperolehInformasi"("name");
-- CreateIndex
CREATE UNIQUE INDEX "CaraMemperolehSalinanInformasi_name_key" ON "CaraMemperolehSalinanInformasi"("name");
-- CreateIndex
CREATE UNIQUE INDEX "KategoriBerita_name_key" ON "KategoriBerita"("name");
-- CreateIndex
CREATE INDEX "_FasilitasKesehatanToInformasiUmum_B_index" ON "_FasilitasKesehatanToInformasiUmum"("B");
-- CreateIndex
CREATE INDEX "_FasilitasKesehatanToTarifDanLayanan_B_index" ON "_FasilitasKesehatanToTarifDanLayanan"("B");
-- AddForeignKey
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_jenisInformasiDimintaId_fkey" FOREIGN KEY ("jenisInformasiDimintaId") REFERENCES "JenisInformasiDiminta"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_caraMemperolehInformasiId_fkey" FOREIGN KEY ("caraMemperolehInformasiId") REFERENCES "CaraMemperolehInformasi"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_caraMemperolehSalinanInformasiId_fkey" FOREIGN KEY ("caraMemperolehSalinanInformasiId") REFERENCES "CaraMemperolehSalinanInformasi"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfileDesa" ADD CONSTRAINT "ProfileDesa_profilPerbekelId_fkey" FOREIGN KEY ("profilPerbekelId") REFERENCES "ProfilPerbekel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Berita" ADD CONSTRAINT "Berita_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Berita" ADD CONSTRAINT "Berita_kategoriBeritaId_fkey" FOREIGN KEY ("kategoriBeritaId") REFERENCES "KategoriBerita"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PotensiDesa" ADD CONSTRAINT "PotensiDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" ADD CONSTRAINT "_FasilitasKesehatanToInformasiUmum_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" ADD CONSTRAINT "_FasilitasKesehatanToInformasiUmum_B_fkey" FOREIGN KEY ("B") REFERENCES "InformasiUmum"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" ADD CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" ADD CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_B_fkey" FOREIGN KEY ("B") REFERENCES "TarifDanLayanan"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@@ -17,3 +17,601 @@ model Potensi {
name String @unique
}
model LandingPage_Layanan {
id String @id @default(cuid())
deksripsi String
}
// ========================================= APPMENU ========================================= //
model AppMenu {
id String @id @default(cuid())
name String @unique
link String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
AppMenuChild AppMenuChild[]
}
// ========================================= APPMENUCHILD ========================================= //
model AppMenuChild {
id String @id @default(cuid())
name String @unique
link String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
AppMenu AppMenu? @relation(fields: [appMenuId], references: [id])
appMenuId String?
}
// ========================================= 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
Berita Berita[]
PotensiDesa PotensiDesa[]
}
//========================================= MENU PPID ========================================= //
// ========================================= VISI MISI PPID ========================================= //
model VisiMisiPPID {
id String @id @default(cuid())
visi String @db.Text
misi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= DASAR HUKUM PPID ========================================= //
model DasarHukumPPID {
id String @id @default(cuid())
judul String @db.Text
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PROFILE PPID ========================================= //
model ProfilePPID {
id String @id @default(cuid())
name String @db.Text
biodata String @db.Text
riwayat String @db.Text
pengalaman String @db.Text
unggulan String @db.Text
imageUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
model DaftarInformasiPublik {
id String @id @default(cuid())
nomor Int @default(autoincrement())
jenisInformasi String
deskripsi String
tanggal String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//=========================================PERMOHONAN INFORMASI PUBLIK========================= //
model PermohonanInformasiPublik {
id String @id @default(cuid())
nomor Int @default(autoincrement())
name String
nik String
notelp String
alamat String
email String
jenisInformasiDiminta JenisInformasiDiminta? @relation(fields: [jenisInformasiDimintaId], references: [id])
jenisInformasiDimintaId String?
caraMemperolehInformasi CaraMemperolehInformasi? @relation(fields: [caraMemperolehInformasiId], references: [id])
caraMemperolehInformasiId String?
caraMemperolehSalinanInformasi CaraMemperolehSalinanInformasi? @relation(fields: [caraMemperolehSalinanInformasiId], references: [id])
caraMemperolehSalinanInformasiId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisInformasiDiminta {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
model CaraMemperolehInformasi {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
model CaraMemperolehSalinanInformasi {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
//=========================================PERMOHONAN INFORMASI KEBERATAN PUBLIK========================= //
model FormulirPermohonanKeberatan {
id String @id @default(cuid())
name String
email String
notelp String
alasan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= IKM ========================================= //
model IndeksKepuasanMasyarakat {
id Int @id @default(autoincrement())
label String
kepuasan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model GrafikBerdasarkanJenisKelamin {
id String @id @default(cuid())
perempuan String
laki String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model GrafikBerdasarkanResponden {
id String @id @default(cuid())
sangatbaik String
baik String
kurangbaik String
tidakbaik String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model GrafikBerdasarkanUmur {
id String @id @default(cuid())
remaja String
dewasa String
orangtua String
lansia String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= MENU DESA ========================================= //
// ========================================= PROFILE DESA ========================================= //
model ProfileDesa {
id String @id @default(cuid())
sejarah String @db.Text
visi String @db.Text
misi String @db.Text
lambang String @db.Text
maskot String @db.Text
ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id])
profilPerbekelId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ProfilPerbekel {
id String @id @default(cuid())
biodata String @db.Text
pengalaman String @db.Text
pengalamanOrganisasi String @db.Text
programUnggulan String @db.Text
ProfileDesa ProfileDesa[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= BERITA ========================================= //
model Berita {
id String @id @default(cuid())
judul String
deskripsi 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)
kategoriBerita KategoriBerita? @relation(fields: [kategoriBeritaId], references: [id])
kategoriBeritaId String?
}
model KategoriBerita {
id String @id @default(cuid())
name String @unique
beritas Berita[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= 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)
}
// ========================================= PENGUMUMAN ========================================= //
model Pengumuman {
id String @id @default(cuid())
judul String
deskripsi String
content String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
CategoryPengumuman CategoryPengumuman? @relation(fields: [categoryPengumumanId], references: [id])
categoryPengumumanId String?
}
model CategoryPengumuman {
id String @id @default(cuid())
name String @unique
pengumumans Pengumuman[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= IMAGES ========================================= //
model Images {
id String @id @default(cuid())
url String
label String @default("null")
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
GalleryFoto GalleryFoto[]
}
// ========================================= VIDEOS ========================================= //
model Videos {
id String @id @default(cuid())
url String
label String @default("null")
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
GalleryVideo GalleryVideo[]
}
// ========================================= GALLERY ========================================= //
model GalleryFoto {
id String @id @default(cuid())
name String
image String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
imagesId String? @unique
imageGalleryFoto Images? @relation(fields: [imagesId], references: [id])
}
model GalleryVideo {
id String @id @default(cuid())
name String
video String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
videosId String? @unique
videosGalleryVideo Videos? @relation(fields: [videosId], references: [id])
}
// ========================================= MENU KESEHATAN ========================================= //
// ========================================= DATA KESEHATAN WARGA ========================================= //
// ========================================= FASILITAS KESEHATAN ========================================= //
model FasilitasKesehatan {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
InformasiUmum InformasiUmum[]
LayananUnggulan LayananUnggulan[]
DokterdanTenagaMedis DokterdanTenagaMedis[]
FasilitasPendukung FasilitasPendukung[]
ProsedurPendaftaran ProsedurPendaftaran[]
TarifDanLayanan TarifDanLayanan[]
}
model InformasiUmum {
id String @id @default(cuid())
fasilitas String
alamat String
jamOperasional String
FasilitasKesehatan FasilitasKesehatan[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model LayananUnggulan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[]
}
model DokterdanTenagaMedis {
id String @id @default(cuid())
name String
specialist String
jadwal String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[]
}
model FasilitasPendukung {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[]
}
model ProsedurPendaftaran {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[]
}
model TarifDanLayanan {
id String @id @default(cuid())
layanan String
tarif String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[]
}
// ========================================= JADWAL KEGIATAN ========================================= //
model JadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model InformasiJadwalKegiatan {
id String @id @default(cuid())
name String
tanggal String
waktu String
lokasi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model DeskripsiJadwalKegiatan {
id String @id @default(cuid())
deskripsi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model LayananJadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model SyaratKetentuanJadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model DokumenJadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PendaftaranJadwalKegiatan {
id String @id @default(cuid())
name String
tanggal String
namaOrangtua String
nomor String
alamat String
catatan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
model DataKematian_Kelahiran {
id Int @id @default(autoincrement())
tahun String
kematianKasar String
kematianBayi String
kelahiranKasar String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id Int @id @default(autoincrement())
label String
jumlah String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= ARTIKEL KESEHATAN ========================================= //
model ArtikelKesehatan {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Introduction {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Symptom {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Prevention {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model FirstAid {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model MythVsFact {
id Int @id @default(autoincrement())
title String
mitos String
fakta String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model DoctorSign {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -1,48 +1,229 @@
import layanan from './data/list-layanan.json'
import potensi from './data/list-potensi.json'
import prisma from '@/lib/prisma';
; (async () => {
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
name: l.name
},
update: {
name: l.name
},
create: {
name: l.name
}
})
import prisma from "@/lib/prisma";
import categoryPengumuman from "./data/category-pengumuman.json";
import kategoriBerita from "./data/kategori-berita.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
import layanan from "./data/list-layanan.json";
import potensi from "./data/list-potensi.json";
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
import path from "path";
import fs from "fs";
import { mkdir, writeFile } from "fs/promises";
import { v4 as uuid } from "uuid";
(async () => {
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
name: l.name,
},
update: {
name: l.name,
},
create: {
name: l.name,
},
});
}
console.log("layanan success ...");
for (const p of potensi) {
await prisma.potensi.upsert({
where: {
name: p.name,
},
update: {
name: p.name,
},
create: {
name: p.name,
},
});
}
console.log("potensi success ...");
for (const k of kategoriBerita) {
await prisma.kategoriBerita.upsert({
where: {
name: k.name,
},
update: {
name: k.name,
},
create: {
name: k.name,
},
});
}
console.log("kategori berita success ...");
for (const c of categoryPengumuman) {
await prisma.categoryPengumuman.upsert({
where: {
name: c.name,
},
update: {
name: c.name,
},
create: {
name: c.name,
},
});
}
console.log("category pengumuman success ...");
for (const j of jenisInformasiDiminta) {
await prisma.jenisInformasiDiminta.upsert({
where: {
name: j.name,
},
update: {
name: j.name,
},
create: {
name: j.name,
},
});
}
console.log("jenis informasi diminta success ...");
for (const c of caraMemperolehInformasi) {
await prisma.caraMemperolehInformasi.upsert({
where: {
name: c.name,
},
update: {
name: c.name,
},
create: {
name: c.name,
},
});
}
console.log("cara memperoleh informasi success ...");
for (const c of caraMemperolehSalinanInformasi) {
await prisma.caraMemperolehSalinanInformasi.upsert({
where: {
name: c.name,
},
update: {
name: c.name,
},
create: {
name: c.name,
},
});
}
console.log("cara memperoleh salinan informasi success ...");
const seedProfilePPID = async () => {
const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid")
// Buat folder hanya jika belum ada
if (!fs.existsSync(targetDir)) {
await mkdir(targetDir, { recursive: true })
}
console.log("layanan success ...")
for (const p of potensi) {
await prisma.potensi.upsert({
where: {
name: p.name
},
update: {
name: p.name
},
create: {
name: p.name
}
})
for (const c of profilePPID) {
let finalImageUrl = c.imageUrl
if (c.imageUrl.startsWith("/uploads/seeded-images/")) {
const filename = path.basename(c.imageUrl)
const seedImagePath = path.resolve("prisma", "seed-images", filename)
const targetFilename = `${uuid()}_${filename}`
const targetPath = path.join(targetDir, targetFilename)
const buffer = fs.readFileSync(seedImagePath)
await writeFile(targetPath, buffer)
finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}`
}
await prisma.profilePPID.upsert({
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
imageUrl: finalImageUrl,
},
create: {
id: c.id,
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
imageUrl: finalImageUrl,
},
})
}
console.log("✅ profilePPID seeded from JSON with image copying")
}
await seedProfilePPID()
console.log("potensi success ...")
})().then(() => prisma.$disconnect()).catch((e) => {
console.error(e)
prisma.$disconnect()
for (const v of visiMisiPPID) {
await prisma.visiMisiPPID.upsert({
where: {
id: v.id,
},
update: {
misi: v.misi,
visi: v.visi,
},
create: {
id: v.id,
misi: v.misi,
visi: v.visi,
},
});
}
console.log("visi misi PPID success ...");
for (const v of dasarHukumPPID) {
await prisma.dasarHukumPPID.upsert({
where: {
id: v.id,
},
update: {
judul: v.judul,
content: v.content,
},
create: {
id: v.id,
judul: v.judul,
content: v.content,
},
});
}
console.log("dasar hukum PPID success ...");
})()
.then(() => prisma.$disconnect())
.catch((e) => {
console.error(e);
prisma.$disconnect();
});
process.on("exit", () => {
prisma.$disconnect();
});
process.on('exit', () => {
prisma.$disconnect()
})
process.on('SIGINT', () => {
prisma.$disconnect()
process.exit(0)
})
process.on("SIGINT", () => {
prisma.$disconnect();
process.exit(0);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
public/perbekel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -0,0 +1,85 @@
// TestEditor.tsx
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
type CreateEditorProps = {
value: string;
onChange: (content: string) => void;
};
export default function CreateEditor({ value, onChange }: CreateEditorProps) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content: value,
onUpdate: () => {
if (editor) {
onChange(editor.getHTML());
}
},
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

View File

@@ -0,0 +1,102 @@
'use client'
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
import { useEffect } from 'react';
type EditEditorProps = {
value: string;
onChange: (content: string) => void;
};
export default function EditEditor({ value, onChange }: EditEditorProps) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content: value,
// Hapus `immediatelyRender` dan `onMount`
});
// Sinkronisasi konten saat `value` berubah
useEffect(() => {
if (editor && value !== editor.getHTML()) {
editor.commands.setContent(value);
}
}, [value, editor]);
// Sinkronisasi konten ke parent saat diubah
useEffect(() => {
if (!editor) return;
const updateHandler = () => onChange(editor.getHTML());
editor.on('update', updateHandler);
return () => {
editor.off('update', updateHandler);
};
}, [editor, onChange]);
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
{/* Toolbar seperti sebelumnya */}
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Grid, GridCol, Paper, TextInput, Title } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react'; // Sesuaikan jika kamu pakai icon lain
import colors from '@/con/colors';
const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }: { title: string, placeholder?: string, searchIcon?: React.ReactNode }) => {
return (
<Grid>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>{title}</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
/>
</Paper>
</GridCol>
</Grid>
);
};
export default HeaderSearch;

View File

@@ -0,0 +1,30 @@
'use client'
import colors from '@/con/colors';
import { Grid, GridCol, Button, Text } from '@mantine/core';
import { IconCircleDashedPlus } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
const JudulList = ({ title = "", href = "#" }) => {
const router = useRouter();
const handleNavigate = () => {
router.push(href);
};
return (
<Grid align="center" mb={10}>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"xl"} fw={"bold"}>{title}</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }} ta="right">
<Button onClick={handleNavigate} bg={colors['blue-button']}>
<IconCircleDashedPlus size={25} />
</Button>
</GridCol>
</Grid>
);
};
export default JudulList;

View File

@@ -0,0 +1,41 @@
'use client'
import colors from '@/con/colors';
import { Grid, GridCol, Button, Text, Paper, TextInput } from '@mantine/core';
import { IconCircleDashedPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
const JudulListTab = ({ title = "", href = "#", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }) => {
const router = useRouter();
const handleNavigate = () => {
router.push(href);
};
return (
<Grid mb={10}>
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{base: "md", md: "xl"}} fw={"bold"}>{title}</Text>
</GridCol>
<GridCol span={{ base: 9, md: 3}} ta="right">
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
/>
</Paper>
</GridCol>
<GridCol span={{ base: 3, md: 1}} ta="right">
<Button onClick={handleNavigate} bg={colors['blue-button']}>
<IconCircleDashedPlus size={25} />
</Button>
</GridCol>
</Grid>
);
};
export default JudulListTab;

View File

@@ -0,0 +1,36 @@
// components/modal/ModalKonfirmasiHapus.tsx
import colors from "@/con/colors"
import { Modal, Text, Button, Flex } from "@mantine/core"
interface ModalKonfirmasiHapusProps {
opened: boolean
loading?: boolean
onClose: () => void
onConfirm: () => void
text: string
}
export function ModalKonfirmasiHapus({
opened,
loading = false,
onClose,
onConfirm,
text,
}: ModalKonfirmasiHapusProps) {
return (
<Modal
opened={opened}
onClose={onClose}
title="Konfirmasi Hapus"
centered
>
<Text mb="md">{text}</Text>
<Flex justify="flex-end" gap="sm">
<Button style={{color: "white"}} bg={colors['blue-button']} variant="default" onClick={onClose}>Batal</Button>
<Button color="red" onClick={onConfirm} loading={loading}>
Yakin Hapus
</Button>
</Flex>
</Modal>
)
}

View File

@@ -0,0 +1,255 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// 1. Schema validasi dengan Zod
const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
kategoriBeritaId: z.string().nonempty(),
imageId: z.string().nonempty(),
});
// 2. Default value form berita (hindari uncontrolled input)
const defaultForm = {
judul: "",
deskripsi: "",
imageId: "",
content: "",
kategoriBeritaId: "",
};
// 3. Kategori proxy
const category = proxy({
findMany: {
data: [] as Prisma.KategoriBeritaGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.desa.berita.category["find-many"].get();
if (res.status === 200) {
category.findMany.data = res.data?.data ?? [];
}
},
},
});
// 4. Berita proxy
const berita = proxy({
create: {
form: { ...defaultForm }, // ✅ ini kunci fix-nya
loading: false,
async create() {
const cek = templateForm.safeParse(berita.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
berita.create.loading = true;
const res = await ApiFetch.api.desa.berita["create"].post(
berita.create.form
);
if (res.status === 200) {
berita.findMany.load();
return toast.success("Berita berhasil disimpan!");
}
return toast.error("Gagal menyimpan berita");
} catch (error) {
console.log((error as Error).message);
} finally {
berita.create.loading = false;
}
},
resetForm() {
berita.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.berita["find-many"].get();
if (res.status === 200) {
berita.findMany.data = (res.data?.data ) ?? [];
}
},
},
findUnique: {
data: null as
| Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/berita/${id}`);
if (res.ok) {
const data = await res.json();
berita.findUnique.data = data.data ?? null;
} else {
console.error('Failed to fetch berita:', res.statusText);
berita.findUnique.data = null;
}
} catch (error) {
console.error('Error fetching berita:', error);
berita.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
berita.delete.loading = true;
const response = await fetch(`/api/desa/berita/delete/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Berita berhasil dihapus");
await berita.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus berita");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus berita");
} finally {
berita.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/berita/${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 = {
judul: data.judul,
deskripsi: data.deskripsi,
content: data.content,
kategoriBeritaId: data.kategoriBeritaId || "",
imageId: data.imageId || "",
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading berita:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(berita.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
berita.edit.loading = true;
const response = await fetch(`/api/desa/berita/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
judul: this.form.judul,
deskripsi: this.form.deskripsi,
content: this.form.content,
kategoriBeritaId: this.form.kategoriBeritaId || null,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update berita");
await berita.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update berita");
}
} catch (error) {
console.error("Error updating berita:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update berita");
return false;
} finally {
berita.edit.loading = false;
}
},
reset() {
berita.edit.id = "";
berita.edit.form = { ...defaultForm };
},
},
});
// 5. State global
const stateDashboardBerita = proxy({
category,
berita,
});
export default stateDashboardBerita;

View File

@@ -0,0 +1,246 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateFormPengumuman = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
categoryPengumumanId: z.string().nonempty(),
});
const category = proxy({
findMany: {
data: null as
| null
| Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.desa.pengumuman.category[
"find-many"
].get();
if (res.status === 200) {
category.findMany.data = (res.data?.data as any) ?? [];
}
},
},
});
type PengumumanForm = Prisma.PengumumanGetPayload<{
select: {
judul: true;
deskripsi: true;
content: true;
categoryPengumumanId: true;
};
}>;
const pengumuman = proxy({
create: {
form: {} as PengumumanForm,
loading: false,
async create() {
const cek = templateFormPengumuman.safeParse(pengumuman.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengumuman.create.loading = true;
const res = await ApiFetch.api.desa.pengumuman["create"].post(
pengumuman.create.form
);
if (res.status === 200) {
pengumuman.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
pengumuman.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
console.log(res);
if (res.status === 200) {
pengumuman.findMany.data = res.data?.data ?? [];
}
},
},
// findUnique: {
// data: null as
// | Prisma.PengumumanGetPayload<{
// include: {
// CategoryPengumuman: true;
// }
// }>
// | null,
// async load(id: string) {
// try {
// const res = await fetch(`/api/desa/pengumuman/${id}`);
// if (res.ok) {
// const data = await res.json();
// pengumuman.findUnique.data = data.data ?? null;
// } else {
// console.error('Failed to fetch pengumuman:', res.statusText);
// pengumuman.findUnique.data = null;
// }
// } catch (error) {
// console.error('Error fetching pengumuman:', error);
// pengumuman.findUnique.data = null;
// }
// },
// },
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengumuman.delete.loading = true;
const response = await fetch(`/api/desa/pengumuman/delete/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pengumuman berhasil dihapus");
await pengumuman.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengumuman");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengumuman");
} finally {
pengumuman.delete.loading = false;
}
},
},
update: {
id: "",
form: {} as PengumumanForm,
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/pengumuman/${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 = {
judul: data.judul,
deskripsi: data.deskripsi,
content: data.content,
categoryPengumumanId: data.categoryPengumumanId || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data pengumuman");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data pengumuman");
} finally {
pengumuman.update.loading = false;
}
},
async update() {
const cek = templateFormPengumuman.safeParse(pengumuman.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
pengumuman.update.loading = true;
const response = await fetch(`/api/desa/pengumuman/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
deskripsi: this.form.deskripsi,
content: this.form.content,
categoryPengumumanId: this.form.categoryPengumumanId,
}),
});
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 pengumuman");
await pengumuman.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update pengumuman");
}
} catch (error) {
console.error("Error updating pengumuman:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update pengumuman"
);
return false;
} finally {
pengumuman.update.loading = false;
}
},
},
});
const stateDesaPengumuman = proxy({
category,
pengumuman,
});
export default stateDesaPengumuman;

View File

@@ -0,0 +1,223 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(50),
kategori: 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: "",
}
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,
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

View File

@@ -0,0 +1,239 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Sejarah */
const templateFormSejarahForm = z.object({
sejarah: z.string().min(3, "Sejarah minimal 3 karakter"),
})
type SejarahForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
sejarah: true;
}
}>
const Sejarah = proxy({
findById: {
data: null as SejarahForm | null,
loading: false,
initialize() {
Sejarah.findById.data = {
id: "",
sejarah: "",
} as SejarahForm;
},
async load(id: string) {
try {
Sejarah.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
Sejarah.findById.data = {
id: id,
sejarah: res.data?.data?.sejarah ?? ""
};
} else {
toast.error("Gagal mengambil data sejarah");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data sejarah");
} finally {
Sejarah.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: SejarahForm) {
const cek = templateFormSejarahForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
Sejarah.update.loading = true;
const res = await ApiFetch.api.desa.profile.sejarah["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update sejarah");
await Sejarah.findById.load(data.id);
} else {
toast.error("Gagal update sejarah");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update sejarah");
} finally {
Sejarah.update.loading = false;
}
}
}
})
/* Visi Misi Desa */
const templateFormVisiForm = z.object({
visi: z.string().min(3, "Visi minimal 3 karakter"),
misi: z.string().min(3, "Misi minimal 3 karakter")
})
type VisiMisiDesaForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
visi: true;
misi: true;
}
}>
const VisiMisiDesa = proxy({
findById: {
data: null as VisiMisiDesaForm | null,
loading: false,
initialize() {
VisiMisiDesa.findById.data = {
id: "",
visi: "",
misi: ""
} as VisiMisiDesaForm;
},
async load(id: string) {
try {
VisiMisiDesa.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
VisiMisiDesa.findById.data = {
id: id,
visi: res.data?.data?.visi ?? "",
misi: res.data?.data?.misi ?? ""
};
} else {
toast.error("Gagal mengambil data visi misi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data visi misi");
} finally {
VisiMisiDesa.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: VisiMisiDesaForm) {
const cek = templateFormVisiForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
VisiMisiDesa.update.loading = true;
const res = await ApiFetch.api.desa.profile.visimisiDesa["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update visi misi");
await VisiMisiDesa.findById.load(data.id);
} else {
toast.error("Gagal update visi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update visi misi");
} finally {
VisiMisiDesa.update.loading = false;
}
}
}
})
/* Lambang Desa */
const templateFormLambangDesaForm = z.object({
lambang: z.string().min(3, "Lambang minimal 3 karakter"),
})
type LambangDesaForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
lambang: true;
}
}>
const LambangDesa = proxy({
findById: {
data: null as LambangDesaForm | null,
loading: false,
initialize() {
LambangDesa.findById.data = {
id: "",
lambang: "",
} as LambangDesaForm;
},
async load(id: string) {
try {
LambangDesa.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
LambangDesa.findById.data = {
id: id,
lambang: res.data?.data?.lambang ?? ""
};
} else {
toast.error("Gagal mengambil data lambang desa");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data lambang desa");
} finally {
LambangDesa.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: LambangDesaForm) {
const cek = templateFormLambangDesaForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
LambangDesa.update.loading = true;
const res = await ApiFetch.api.desa.profile.lambangDesa["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update lambang desa");
await LambangDesa.findById.load(data.id);
} else {
toast.error("Gagal update lambang desa");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update lambang desa");
} finally {
LambangDesa.update.loading = false;
}
}
}
});
const stateProfileDesa = {
Sejarah,
VisiMisiDesa,
LambangDesa,
};
export default stateProfileDesa;

View File

@@ -0,0 +1,339 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Introduction */
const templateIntroduction = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Introduction = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
const introduction = proxy({
create: {
form: {} as Introduction,
loading: false,
async create() {
const cek = templateIntroduction.safeParse(introduction.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
introduction.create.loading = true;
const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form);
if (res.status === 200) {
introduction.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
introduction.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IntroductionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.introduction["find-many"].get();
if (res.status === 200) {
introduction.findMany.data = res.data?.data ?? [];
}
}
}
});
/* ======================================================================= */
/* symptom */
const templateSymptom = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Symptom = Prisma.SymptomGetPayload<{
select: {
title: true;
content: true;
};
}>;
const symptom = proxy({
create: {
form: {} as Symptom,
loading: false,
async create() {
const cek = templateSymptom.safeParse(symptom.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
symptom.create.loading = true;
const res = await ApiFetch.api.kesehatan.symptom["create"].post(symptom.create.form);
if (res.status === 200) {
symptom.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
symptom.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.symptom["find-many"].get();
if (res.status === 200) {
symptom.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Prevention */
const templatePrevention = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Prevention = Prisma.PreventionGetPayload<{
select: {
title: true;
content: true;
};
}>;
const prevention = proxy({
create: {
form: {} as Prevention,
loading: false,
async create() {
const cek = templatePrevention.safeParse(prevention.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prevention.create.loading = true;
const res = await ApiFetch.api.kesehatan.prevention["create"].post(prevention.create.form);
if (res.status === 200) {
prevention.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prevention.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PreventionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.prevention["find-many"].get();
if (res.status === 200) {
prevention.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* First Aid */
const templateFirstAid = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FirstAid = Prisma.FirstAidGetPayload<{
select: {
title: true;
content: true;
};
}>;
const firstAid = proxy({
create: {
form: {} as FirstAid,
loading: false,
async create() {
const cek = templateFirstAid.safeParse(firstAid.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
firstAid.create.loading = true;
const res = await ApiFetch.api.kesehatan.firstaid["create"].post(firstAid.create.form);
if (res.status === 200) {
firstAid.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
firstAid.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FirstAidGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.firstaid["find-many"].get();
if (res.status === 200) {
firstAid.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Myth vs Fact */
const templateMythFact = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
mitos: z.string().min(3, "Mitos minimal 3 karakter"),
fakta: z.string().min(3, "Fakta minimal 3 karakter"),
})
type MythFact = Prisma.MythVsFactGetPayload<{
select: {
title: true;
mitos: true;
fakta: true;
};
}>;
const mythFact = proxy({
create: {
form: {} as MythFact,
loading: false,
async create() {
const cek = templateMythFact.safeParse(mythFact.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
mythFact.create.loading = true;
const res = await ApiFetch.api.kesehatan.mythvsfact["create"].post(mythFact.create.form);
if (res.status === 200) {
mythFact.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
mythFact.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.MythVsFactGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.mythvsfact["find-many"].get();
if (res.status === 200) {
mythFact.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Doctor Sign */
const templateDoctorSign = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type DoctorSign = Prisma.DoctorSignGetPayload<{
select: {
content: true
}
}>
const doctorSign = proxy({
create: {
form: {} as DoctorSign,
loading: false,
async create() {
const cek = templateDoctorSign.safeParse(doctorSign.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
doctorSign.create.loading = true;
const res = await ApiFetch.api.kesehatan.doctor_sign["create"].post(doctorSign.create.form);
if (res.status === 200) {
doctorSign.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
doctorSign.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DoctorSignGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.doctor_sign["find-many"].get();
if (res.status === 200) {
doctorSign.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
const stateArtikelKesehatan = proxy({
introduction,
symptom,
prevention,
firstAid,
mythFact,
doctorSign
})
export default stateArtikelKesehatan

View File

@@ -0,0 +1,333 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Informasi Umum */
const templateInformasiUmum = z.object({
fasilitas: z.string().min(3, "Fasilitas minimal 3 karakter"),
alamat: z.string().min(3, "Alamat minimal 3 karakter"),
jamOperasional: z.string().min(3, "Jam Operasional minimal 3 karakter"),
});
type InfromasiUmum = Prisma.InformasiUmumGetPayload<{
select: {
fasilitas: true;
alamat: true;
jamOperasional: true;
};
}>;
const informasiumum = proxy({
create: {
form: {} as InfromasiUmum,
loading: false,
async create() {
const cek = templateInformasiUmum.safeParse(informasiumum.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
informasiumum.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiumum["create"].post(
informasiumum.create.form
);
if (res.status === 200) {
informasiumum.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
informasiumum.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.InformasiUmumGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
informasiumum["find-many"].get();
if (res.status === 200) {
informasiumum.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Layanan Unggulan */
const templateLayananUnggulanForm = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type LayananUnggulan = Prisma.LayananUnggulanGetPayload<{
select: {
content: true;
};
}>;
const layananunggulan = proxy({
create: {
form: {} as LayananUnggulan,
loading: false,
async create() {
const cek = templateLayananUnggulanForm.safeParse(layananunggulan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
layananunggulan.create.loading = true;
const res = await ApiFetch.api.kesehatan.layananunggulan["create"].post(
layananunggulan.create.form
);
if (res.status === 200) {
layananunggulan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
layananunggulan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.LayananUnggulanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
layananunggulan["find-many"].get();
if (res.status === 200) {
layananunggulan.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Dokter dan Tenaga Medis */
const templateDokterdanTenagaMedis = z.object({
name: z.string().min(3, "Name minimal 3 karakter"),
specialist: z.string().min(3, "Specialist minimal 3 karakter"),
jadwal: z.string().min(3, "Jadwal minimal 3 karakter"),
})
type DokterdanTenagaMedis = Prisma.DokterdanTenagaMedisGetPayload<{
select: {
name: true;
specialist: true;
jadwal: true;
};
}>;
const dokterdantenagamedis = proxy({
create: {
form: {} as DokterdanTenagaMedis,
loading: false,
async create() {
const cek = templateDokterdanTenagaMedis.safeParse(dokterdantenagamedis.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
dokterdantenagamedis.create.loading = true;
const res = await ApiFetch.api.kesehatan.dokterdantenagamedis["create"].post(dokterdantenagamedis.create.form);
if (res.status === 200) {
dokterdantenagamedis.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
dokterdantenagamedis.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DokterdanTenagaMedisGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.dokterdantenagamedis["find-many"].get();
if (res.status === 200) {
dokterdantenagamedis.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Fasilitas Pendukung */
const templateFasilitasPendukung = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FasilitasPendukung = Prisma.FasilitasPendukungGetPayload<{
select: {
content: true;
};
}>;
const fasilitaspendukung = proxy({
create: {
form: {} as FasilitasPendukung,
loading: false,
async create() {
const cek = templateFasilitasPendukung.safeParse(fasilitaspendukung.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
fasilitaspendukung.create.loading = true;
const res = await ApiFetch.api.kesehatan.fasilitaspendukung["create"].post(fasilitaspendukung.create.form);
if (res.status === 200) {
fasilitaspendukung.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
fasilitaspendukung.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FasilitasPendukungGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
fasilitaspendukung["find-many"].get();
if (res.status === 200) {
fasilitaspendukung.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Tarif dan Layanan */
const templateTarifDanLayanan = z.object({
layanan: z.string().min(3, "Layanan minimal 3 karakter"),
tarif: z.string().min(3, "Tarif minimal 3 karakter"),
})
const tarifdanlayanan = proxy({
create: {
form: {} as Prisma.TarifDanLayananGetPayload<{ select: { layanan: true; tarif: true } }>,
loading: false,
async create() {
const cek = templateTarifDanLayanan.safeParse(tarifdanlayanan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tarifdanlayanan.create.loading = true;
const res = await ApiFetch.api.kesehatan.tarifdanlayanan["create"].post(tarifdanlayanan.create.form);
if (res.status === 200) {
tarifdanlayanan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
tarifdanlayanan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.TarifDanLayananGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
tarifdanlayanan["find-many"].get();
if (res.status === 200) {
tarifdanlayanan.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Prosedur Pendaftaran */
const templateProsedurPendaftaran = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
const prosedurpendaftaran = proxy({
create: {
form: {} as Prisma.ProsedurPendaftaranGetPayload<{ select: { content: true } }>,
loading: false,
async create() {
const cek = templateProsedurPendaftaran.safeParse(prosedurpendaftaran.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prosedurpendaftaran.create.loading = true;
const res = await ApiFetch.api.kesehatan.prosedurpendaftaran["create"].post(prosedurpendaftaran.create.form);
if (res.status === 200) {
prosedurpendaftaran.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prosedurpendaftaran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.ProsedurPendaftaranGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
prosedurpendaftaran["find-many"].get();
if (res.status === 200) {
prosedurpendaftaran.findMany.data = res.data?.data ?? [];
}
},
},
})
const stateFasilitasKesehatan = proxy({
informasiumum,
layananunggulan,
dokterdantenagamedis,
fasilitaspendukung,
tarifdanlayanan,
prosedurpendaftaran
})
export default stateFasilitasKesehatan

View File

@@ -0,0 +1,76 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"),
jumlah: z.string().min(2, "Jumlah harus diisi"),
});
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
select: {
label: true;
jumlah: true;
};
}>;
const defaultForm: GrafikKepuasan = {
label: "",
jumlah: ""
};
const grafikkepuasan = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikkepuasan.create.loading = true;
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
grafikkepuasan.create.form
);
if (res.status === 200) {
grafikkepuasan.create.form = {
label: "",
jumlah: ""
};
grafikkepuasan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikkepuasan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
"find-many"
].get();
if (res.status === 200) {
grafikkepuasan.findMany.data = res.data?.data ?? [];
}
},
},
});
const stategrafikKepuasan = proxy({
grafikkepuasan,
});
export default stategrafikKepuasan;

View File

@@ -0,0 +1,366 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Informasi Kegiatan */
const templateInformasiKegiatan = z.object({
name: z.string().min(3, "Name minimal 3 karakter"),
tanggal: z.string().min(3, "Tanggal minimal 3 karakter"),
waktu: z.string().min(3, "Waktu minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
});
type InformasiKegiatan = Prisma.InformasiJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
waktu: true;
lokasi: true;
};
}>;
const informasiKegiatan = proxy({
create: {
form: {} as InformasiKegiatan,
loading: false,
async create() {
const cek = templateInformasiKegiatan.safeParse(
informasiKegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
informasiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"create"
].post(informasiKegiatan.create.form);
if (res.status === 200) {
informasiKegiatan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
informasiKegiatan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.InformasiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"find-many"
].get();
if (res.status === 200) {
informasiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Deskripsi Kegiatan */
const templateDeskripsiKegiatan = z.object({
deskripsi: z.string().min(3, "Content minimal 3 karakter"),
});
type DeskripsiKegiatan = Prisma.DeskripsiJadwalKegiatanGetPayload<{
select: { deskripsi: true };
}>;
const deskripsiKegiatan = proxy({
create: {
form: {} as DeskripsiKegiatan,
loading: false,
async create() {
const cek = templateDeskripsiKegiatan.safeParse(
deskripsiKegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
deskripsiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"create"
].post(deskripsiKegiatan.create.form);
if (res.status === 200) {
deskripsiKegiatan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
deskripsiKegiatan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DeskripsiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"find-many"
].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Layanan Tersedia */
const templateLayananTersedia = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type LayananTersedia = Prisma.LayananJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const layanantersedia = proxy({
create: {
form: {} as LayananTersedia,
loading: false,
async create() {
const cek = templateLayananTersedia.safeParse(
layanantersedia.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
layanantersedia.create.loading = true;
const res = await ApiFetch.api.kesehatan.layanantersedia["create"].post(
layanantersedia.create.form
);
if (res.status === 200) {
layanantersedia.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
layanantersedia.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.LayananJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.layanantersedia[
"find-many"
].get();
if (res.status === 200) {
layanantersedia.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Syarat dan Ketentuan */
const templateSyaratKetentuan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type SyaratKetentuan = Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const syaratketentuan = proxy({
create: {
form: {} as SyaratKetentuan,
loading: false,
async create() {
const cek = templateSyaratKetentuan.safeParse(
syaratketentuan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
syaratketentuan.create.loading = true;
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"create"
].post(syaratketentuan.create.form);
if (res.status === 200) {
syaratketentuan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
syaratketentuan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"find-many"
].get();
if (res.status === 200) {
syaratketentuan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Dokumen Yang Diperlukan */
const templateDokumenDiperlukan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type DokumenDiperlukan = Prisma.DokumenJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const dokumenjadwalkegiatan = proxy({
create: {
form: {} as DokumenDiperlukan,
loading: false,
async create() {
const cek = templateDokumenDiperlukan.safeParse(
dokumenjadwalkegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
dokumenjadwalkegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"create"
].post(dokumenjadwalkegiatan.create.form);
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
dokumenjadwalkegiatan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DokumenJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"find-many"
].get();
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Pendaftaran */
const templatePendaftaran = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
tanggal: z.string().min(1),
namaOrangtua: z.string().min(3, "Nama minimal 3 karakter"),
nomor: z.string().min(9, "Nama minimal 9 karakter"),
alamat: z.string().min(7, "Alamat minimal 7 karakter"),
catatan: z.string().min(3, "Catatan minimal 3 karakter"),
})
type Pendaftaran = Prisma.PendaftaranJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
namaOrangtua: true;
nomor: true;
alamat: true;
catatan: true;
}
}>
const pendaftaranjadwal = proxy({
create: {
form: {} as Pendaftaran,
loading: false,
async create() {
const cek = templatePendaftaran.safeParse(pendaftaranjadwal.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pendaftaranjadwal.create.loading = true;
const res = await ApiFetch.api.kesehatan.pendaftaranJadwalKegiatan["create"].post(
pendaftaranjadwal.create.form);
if (res.status === 200) {
pendaftaranjadwal.findMany.load();
return toast.success("success create")
}
return toast.error("failed create")
} catch (error) {
console.log((error as Error).message)
} finally {
pendaftaranjadwal.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PendaftaranJadwalKegiatanGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
pendaftaranJadwalKegiatan["find-many"].get();
if(res.status === 200) {
pendaftaranjadwal.findMany.data = res.data?.data ?? [];
}
}
}
})
const stateJadwalKegiatan = proxy({
informasiKegiatan,
deskripsiKegiatan,
layanantersedia,
syaratketentuan,
dokumenjadwalkegiatan,
pendaftaranjadwal
});
export default stateJadwalKegiatan;

View File

@@ -0,0 +1,84 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templatePersentase = z.object({
tahun: z.string().min(4, "Tahun harus diisi"),
kematianKasar: z.string().min(2, "Kematian kasar harus diisi"),
kelahiranKasar: z.string().min(2, "Kelahiran kasar harus diisi"),
kematianBayi: z.string().min(2, "Kematian bayi harus diisi"),
});
type Persentase = Prisma.DataKematian_KelahiranGetPayload<{
select: {
tahun: true;
kematianKasar: true;
kelahiranKasar: true;
kematianBayi: true;
};
}>;
const defaultForm: Persentase = {
tahun: "",
kematianKasar: "",
kelahiranKasar: "",
kematianBayi: "",
};
const persentasekelahiran = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templatePersentase.safeParse(persentasekelahiran.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
persentasekelahiran.create.loading = true;
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"create"
].post(persentasekelahiran.create.form);
if (res.status === 200) {
persentasekelahiran.create.form = {
tahun: "",
kematianKasar: "",
kelahiranKasar: "",
kematianBayi: "",
};
persentasekelahiran.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
persentasekelahiran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DataKematian_KelahiranGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"find-many"
].get();
if (res.status === 200) {
persentasekelahiran.findMany.data = res.data?.data ?? [];
}
},
},
});
const statePersentase = proxy({
persentasekelahiran,
});
export default statePersentase;

View File

@@ -0,0 +1,65 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateDaftarInformasi = z.object({
jenisInformasi: z.string().min(3, "Jenis Informasi minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
tanggal: z.string().min(3, "Tanggal minimal 3 karakter"),
})
type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{
select: {
jenisInformasi: true;
deskripsi: true;
tanggal: true;
};
}>;
const daftarInformasi = proxy({
create: {
form: {} as DaftarInformasi,
loading: false,
async create() {
const cek = templateDaftarInformasi.safeParse(daftarInformasi.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
daftarInformasi.create.loading = true;
const res = await ApiFetch.api.ppid.daftarinformasipublik["create"].post(daftarInformasi.create.form);
if (res.status === 200) {
daftarInformasi.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
daftarInformasi.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get();
if (res.status === 200) {
daftarInformasi.findMany.data = res.data?.data ?? [];
}
}
}
});
const stateDaftarInformasiPublik = proxy({
daftarInformasi
})
export default stateDaftarInformasiPublik;

View File

@@ -0,0 +1,82 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
});
type DasarHukumForm = Prisma.DasarHukumPPIDGetPayload<{
select: {
id: true;
judul: true;
content: true;
};
}>;
const stateDasarHukumPPID = proxy({
findById: {
data: null as DasarHukumForm | null,
loading: false,
initialize() {
stateDasarHukumPPID.findById.data = {
id: '',
judul: '',
content: '',
} as DasarHukumForm;
},
async load(id: string) {
try {
stateDasarHukumPPID.findById.loading = true;
const res = await ApiFetch.api.ppid.dasarhukumppid["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateDasarHukumPPID.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data dasar hukum");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data dasar hukum");
} finally {
stateDasarHukumPPID.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: DasarHukumForm) {
const cek = templateForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateDasarHukumPPID.update.loading = true;
const res = await ApiFetch.api.ppid.dasarhukumppid["update"].post(data);
if (res.status === 200) {
toast.success("Data dasar hukum berhasil diubah");
await stateDasarHukumPPID.findById.load(data.id);
} else {
toast.error("Gagal mengubah data dasar hukum");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data dasar hukum");
} finally {
stateDasarHukumPPID.update.loading = false;
}
},
},
});
export default stateDasarHukumPPID;

View File

@@ -0,0 +1,77 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikJenisKelamin = z.object({
laki: z.string().min(2, "Data laki-laki harus diisi"),
perempuan: z.string().min(2, "Data perempuan harus diisi"),
});
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
select: {
laki: true;
perempuan: true;
};
}>;
const defaultForm: GrafikJenisKelamin = {
laki: "",
perempuan: "",
};
const grafikBerdasarkanJenisKelamin = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikJenisKelamin.safeParse(
grafikBerdasarkanJenisKelamin.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanJenisKelamin.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"create"
].post(grafikBerdasarkanJenisKelamin.create.form);
if (res.status === 200) {
grafikBerdasarkanJenisKelamin.create.form = defaultForm;
grafikBerdasarkanJenisKelamin.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanJenisKelamin.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanJenisKelamin.findMany.data = res.data?.data ?? [];
}
},
},
});
const stateGrafikBerdasarkanJenisKelamin = proxy({
grafikBerdasarkanJenisKelamin,
});
export default stateGrafikBerdasarkanJenisKelamin;

View File

@@ -0,0 +1,84 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikResponden = z.object({
sangatbaik: z.string().min(1, "Data sangat baik harus diisi"),
baik: z.string().min(1, "Data baik harus diisi"),
kurangbaik: z.string().min(1, "Data kurang baik harus diisi"),
tidakbaik: z.string().min(1, "Data tidak baik harus diisi"),
});
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
select: {
sangatbaik: true;
baik: true;
kurangbaik: true;
tidakbaik: true;
};
}>;
const defaultForm: GrafikResponden = {
sangatbaik: "",
baik: "",
kurangbaik: "",
tidakbaik: "",
};
const grafikBerdasarkanResponden = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikResponden.safeParse(
grafikBerdasarkanResponden.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanResponden.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"create"
].post(grafikBerdasarkanResponden.create.form);
if (res.status === 200) {
grafikBerdasarkanResponden.create.form = defaultForm;
grafikBerdasarkanResponden.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanResponden.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanRespondenGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanResponden.findMany.data = res.data?.data ?? [];
}
},
},
});
const stateGrafikResponden = proxy({
grafikBerdasarkanResponden,
});
export default stateGrafikResponden;

View File

@@ -0,0 +1,84 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikUmur = z.object({
remaja: z.string().min(2, "Data remaja harus diisi"),
dewasa: z.string().min(2, "Data dewasa harus diisi"),
orangtua: z.string().min(2, "Data orangtua harus diisi"),
lansia: z.string().min(2, "Data lansia harus diisi"),
});
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
select: {
remaja: true;
dewasa: true;
orangtua: true;
lansia: true;
};
}>;
const defaultForm: GrafikUmur = {
remaja: "",
dewasa: "",
orangtua: "",
lansia: "",
};
const grafikBerdasarkanUmur = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikUmur.safeParse(
grafikBerdasarkanUmur.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanUmur.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"create"
].post(grafikBerdasarkanUmur.create.form);
if (res.status === 200) {
grafikBerdasarkanUmur.create.form = defaultForm;
grafikBerdasarkanUmur.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanUmur.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
}
},
},
})
const stateGrafikBerdasarkanUmur = proxy({
grafikBerdasarkanUmur,
})
export default stateGrafikBerdasarkanUmur;

View File

@@ -0,0 +1,76 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikHasilKepuasanMasyarakat = z.object({
label: z.string().min(2, "Label harus diisi"),
kepuasan: z.string().min(2, "Kepuasan harus diisi"),
});
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
select: {
label: true;
kepuasan: true;
};
}>;
const defaultForm: GrafikHasilKepuasanMasyarakat = {
label: "",
kepuasan: "",
};
const grafikHasilKepuasanMasyarakat = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(
grafikHasilKepuasanMasyarakat.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikHasilKepuasanMasyarakat.create.loading = true;
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(
grafikHasilKepuasanMasyarakat.create.form
);
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.create.form = {
label: "",
kepuasan: ""
};
grafikHasilKepuasanMasyarakat.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikHasilKepuasanMasyarakat.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get();
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
}
}
}
});
const stateGrafikHasilKepuasanMasyarakat = proxy({
grafikHasilKepuasanMasyarakat,
});
export default stateGrafikHasilKepuasanMasyarakat;

View File

@@ -0,0 +1,125 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
nik: z.string().min(3, "NIK minimal 3 karakter"),
notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"),
alamat: z.string().min(3, "Alamat minimal 3 karakter"),
email: z.string().min(3, "Email minimal 3 karakter"),
jenisInformasiDimintaId: z.string().nonempty(),
caraMemperolehInformasiId: z.string().nonempty(),
caraMemperolehSalinanInformasiId: z.string().nonempty(),
})
const jenisInformasiDiminta = proxy({
findMany: {
data: null as
| null
| Prisma.JenisInformasiDimintaGetPayload<{ omit: { isActive: true } }>[],
async load(){
const res = await ApiFetch.api.ppid.permohonaninformasipublik.jenisInformasi["find-many"].get();
if (res.status === 200) {
jenisInformasiDiminta.findMany.data = res.data?.data ?? [];
}
}
}
})
const caraMemperolehInformasi = proxy({
findMany: {
data: null as
| null
| Prisma.CaraMemperolehInformasiGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik.memperolehInformasi["find-many"].get();
if (res.status === 200) {
caraMemperolehInformasi.findMany.data = res.data?.data ?? [];
}
}
}
})
const caraMemperolehSalinanInformasi = proxy({
findMany: {
data: null as
| null
| Prisma.CaraMemperolehSalinanInformasiGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik.salinanInformasi["find-many"].get();
if (res.status === 200) {
caraMemperolehSalinanInformasi.findMany.data = res.data?.data ?? [];
}
}
}
})
console.log(caraMemperolehSalinanInformasi)
type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{
select: {
name: true;
nik: true;
notelp: true;
alamat: true;
email: true;
jenisInformasiDimintaId: true;
caraMemperolehInformasiId: true;
caraMemperolehSalinanInformasiId: true;
};
}>;
const statepermohonanInformasiPublik = proxy({
create: {
form: {} as PermohonanInformasiPublikForm,
loading: false,
async create(){
const cek = templateForm.safeParse(statepermohonanInformasiPublik.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
statepermohonanInformasiPublik.create.loading = true;
const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(statepermohonanInformasiPublik.create.form);
if (res.status === 200) {
statepermohonanInformasiPublik.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
statepermohonanInformasiPublik.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PermohonanInformasiPublikGetPayload<{ include: {
caraMemperolehSalinanInformasi: true,
jenisInformasiDiminta: true,
caraMemperolehInformasi: true,
} }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get();
if (res.status === 200) {
statepermohonanInformasiPublik.findMany.data = res.data?.data ?? [];
}
}
}
})
const statepermohonanInformasiPublikForm = proxy({
statepermohonanInformasiPublik,
jenisInformasiDiminta,
caraMemperolehInformasi,
caraMemperolehSalinanInformasi,
})
export default statepermohonanInformasiPublikForm;

View File

@@ -0,0 +1,64 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
email: z.string().min(3, "Email minimal 3 karakter"),
notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"),
alasan: z.string().min(3, "Alasan minimal 3 karakter"),
})
type PermohonanKeberatanInformasiForm = Prisma.FormulirPermohonanKeberatanGetPayload<{
select: {
name: true;
email: true;
notelp: true;
alasan: true;
};
}>;
const permohonanKeberatanInformasi = proxy({
create: {
form: {} as PermohonanKeberatanInformasiForm,
loading: false,
async create(){
const cek = templateForm.safeParse(permohonanKeberatanInformasi.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
permohonanKeberatanInformasi.create.loading = true;
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasi.create.form);
if (res.status === 200) {
permohonanKeberatanInformasi.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
permohonanKeberatanInformasi.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FormulirPermohonanKeberatanGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get();
if (res.status === 200) {
permohonanKeberatanInformasi.findMany.data = res.data?.data ?? [];
}
}
}
});
export default permohonanKeberatanInformasi;

View File

@@ -0,0 +1,172 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/**
* Schema validasi form ProfilePPID menggunakan Zod.
*/
const templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
biodata: z.string().min(3, "Biodata minimal 3 karakter"),
riwayat: z.string().min(3, "Riwayat minimal 3 karakter"),
pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"),
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
});
/**
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
*/
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
select: {
id: true;
name: true;
biodata: true;
riwayat: true;
pengalaman: true;
unggulan: true;
imageUrl: true;
};
}>;
/**
* State utama ProfilePPID yang mencakup fitur:
* - Ambil data berdasarkan ID
* - Update data
* - Upload gambar
*/
const stateProfilePPID = proxy({
/**
* Bagian untuk ambil data berdasarkan ID
*/
findById: {
data: null as ProfilePPIDForm | null,
loading: false,
/**
* Inisialisasi data kosong ke dalam state.
*/
initialize() {
stateProfilePPID.findById.data = {
id: '',
name: '',
biodata: '',
riwayat: '',
pengalaman: '',
unggulan: '',
imageUrl: ''
} as ProfilePPIDForm;
},
/**
* Mengambil data profil berdasarkan ID.
* @param {string} id - ID dari profile yang ingin diambil.
*/
async load(id: string) {
try {
stateProfilePPID.findById.loading = true;
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateProfilePPID.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data profile");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data profile");
} finally {
stateProfilePPID.findById.loading = false;
}
},
},
/**
* Bagian untuk update data profile
*/
update: {
loading: false,
/**
* Melakukan validasi dan menyimpan perubahan data profile ke server.
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
*/
async save(data: ProfilePPIDForm) {
const cek = templateForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateProfilePPID.update.loading = true;
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update profile");
await stateProfilePPID.findById.load(data.id);
} else {
toast.error("Gagal update profile");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update profile");
} finally {
stateProfilePPID.update.loading = false;
}
},
},
/**
* Bagian untuk upload gambar profil
*/
uploadImage: {
loading: false,
/**
* Mengunggah gambar profil berdasarkan ID.
* @param {File} file - File gambar yang akan diunggah.
* @param {string} id - ID dari profil yang akan diperbarui gambarnya.
*/
async save(file: File, id: string) {
if (!file || !id) {
toast.error("File atau ID harus disertakan");
return;
}
try {
stateProfilePPID.uploadImage.loading = true;
const form = new FormData();
form.append("file", file);
form.append("id", id);
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
if (res.status === 200) {
toast.success("Berhasil mengunggah gambar");
await stateProfilePPID.findById.load(id);
} else {
toast.error("Gagal mengunggah gambar");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengunggah gambar");
} finally {
stateProfilePPID.uploadImage.loading = false;
}
},
},
});
/**
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
*/
export default stateProfilePPID;

View File

@@ -0,0 +1,81 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
misi: z.string().min(3, "Misi minimal 3 karakter"),
visi: z.string().min(3, "Visi minimal 3 karakter"),
});
type VisiMisiPPIDForm = Prisma.VisiMisiPPIDGetPayload<{
select: {
id: true;
misi: true;
visi: true;
};
}>;
const stateVisiMisiPPID = proxy({
findById: {
data: null as VisiMisiPPIDForm | null,
loading: false,
initialize() {
stateVisiMisiPPID.findById.data = {
id: "",
misi: "",
visi: "",
} as VisiMisiPPIDForm;
},
async load(id: string) {
try {
stateVisiMisiPPID.findById.loading = true;
const res = await ApiFetch.api.ppid.visimisippid["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateVisiMisiPPID.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data visi misi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data visi misi");
} finally {
stateVisiMisiPPID.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: VisiMisiPPIDForm) {
const cek = templateForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateVisiMisiPPID.update.loading = true;
const res = await ApiFetch.api.ppid.visimisippid["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update visi misi");
await stateVisiMisiPPID.findById.load(data.id);
} else {
toast.error("Gagal update visi misi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update visi misi");
} finally {
stateVisiMisiPPID.update.loading = false;
}
},
},
});
export default stateVisiMisiPPID;

View File

@@ -0,0 +1,93 @@
'use client'
import colors from '@/con/colors';
import { Button, Stack } from '@mantine/core';
import { Link, RichTextEditor } from '@mantine/tiptap';
import Highlight from '@tiptap/extension-highlight';
import SubScript from '@tiptap/extension-subscript';
import Superscript from '@tiptap/extension-superscript';
import TextAlign from '@tiptap/extension-text-align';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
const content =
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup>&lt;sup /&gt;</sup> and <sub>&lt;sub /&gt;</sub> tags)</li><li>Ordered and bullet lists</li><li>Text align&nbsp;</li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
export function DesaEditor({showSubmit = true} : {
showSubmit: boolean
}) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
immediatelyRender: false,
content,
});
return (
<Stack>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
{showSubmit && (
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
)}
</Stack>
);
}

View File

@@ -0,0 +1,95 @@
'use client'
import { Button, Stack } from '@mantine/core';
import { Link, RichTextEditor } from '@mantine/tiptap';
import Highlight from '@tiptap/extension-highlight';
import SubScript from '@tiptap/extension-subscript';
import Superscript from '@tiptap/extension-superscript';
import TextAlign from '@tiptap/extension-text-align';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
function DesaEditorText({ onSubmit, onChange, showSubmit = true, initialContent = '', }: {
onSubmit?: (val: string) => void,
onChange: (val: string) => void,
showSubmit?: boolean,
initialContent?: string }) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
immediatelyRender: false,
content: initialContent,
onUpdate : ({editor}) => {
onChange(editor.getHTML())
}
});
return (
<Stack>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
{showSubmit && (
<Button onClick={() => {
if (!editor) return
onSubmit?.(editor?.getHTML())
}}>Submit</Button>
)}
</Stack>
);
}
export default DesaEditorText;

View File

@@ -0,0 +1,241 @@
"use client";
import {
Box,
Button,
Center,
Image,
Paper,
Select,
Skeleton,
Stack,
Text,
TextInput,
Title,
} from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
import { FileInput } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { Prisma } from "@prisma/client";
import stateDashboardBerita from "../../../../_state/desa/berita";
function EditBerita() {
const beritaState = useProxy(stateDashboardBerita);
const router = useRouter();
const params = useParams();
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [formData, setFormData] = useState({
judul: beritaState.berita.edit.form.judul || '',
deskripsi: beritaState.berita.edit.form.deskripsi || '',
kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || '',
content: beritaState.berita.edit.form.content || '',
imageId: beritaState.berita.edit.form.imageId || ''
});
// Load berita by id saat pertama kali
useEffect(() => {
const loadBerita = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await stateDashboardBerita.berita.edit.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
judul: data.judul || '',
deskripsi: data.deskripsi || '',
kategoriBeritaId: data.kategoriBeritaId || '',
content: data.content || '',
imageId: data.imageId || '',
});
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
}
} catch (error) {
console.error("Error loading berita:", error);
toast.error("Gagal memuat data berita");
}
};
loadBerita();
}, [params?.id]); // ✅ hapus beritaState dari dependency
const handleSubmit = async () => {
try {
// Update global state with form data
beritaState.berita.edit.form = {
...beritaState.berita.edit.form,
judul: formData.judul,
deskripsi: formData.deskripsi,
content: formData.content,
kategoriBeritaId: formData.kategoriBeritaId || '',
imageId: formData.imageId // Keep existing imageId if not changed
};
// Jika ada file baru, upload
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
// Update imageId in global state
beritaState.berita.edit.form.imageId = uploaded.id;
}
await beritaState.berita.edit.update();
toast.success("Berita berhasil diperbarui!");
router.push("/admin/desa/berita");
} catch (error) {
console.error("Error updating berita:", error);
toast.error("Terjadi kesalahan saat memperbarui berita");
}
};
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 Berita</Title>
<TextInput
value={formData.judul}
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
placeholder="masukkan judul"
/>
<SelectCategory
value={formData.kategoriBeritaId}
onChange={(val) => {
setFormData({
...formData,
kategoriBeritaId: val?.id || ''
});
}}
/>
<TextInput
value={formData.deskripsi}
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
beritaState.berita.edit.form.content = htmlContent;
}}
/>
</Box>
<Button onClick={handleSubmit}>Edit Berita</Button>
</Stack>
</Paper>
</Box>
);
}
interface SelectCategoryProps {
onChange: (value: Prisma.KategoriBeritaGetPayload<{
select: {
name: true;
id: true;
};
}> | null) => void;
value?: string | null;
defaultValue?: string | null;
}
function SelectCategory({
onChange,
value,
defaultValue,
}: SelectCategoryProps) {
const categoryState = useProxy(stateDashboardBerita.category);
useShallowEffect(() => {
categoryState.findMany.load().then(() => {
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
});
}, []);
if (!categoryState.findMany.data) {
return <Skeleton height={38} />;
}
const selectedValue = value || defaultValue;
return (
<Select
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
placeholder="Pilih kategori"
data={categoryState.findMany.data.map((item) => ({
label: item.name,
value: item.id,
}))}
value={selectedValue || null}
onChange={(val: string | null) => {
if (val) {
const selected = categoryState.findMany.data?.find((item) => item.id === val);
if (selected) {
onChange(selected);
}
} else {
onChange(null);
}
}}
searchable
clearable
nothingFoundMessage="Tidak ditemukan"
/>
);
}
export default EditBerita;

View File

@@ -0,0 +1,120 @@
'use client'
import { useProxy } from 'valtio/utils';
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import colors from '@/con/colors';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import stateDashboardBerita from '../../../_state/desa/berita';
function DetailBerita() {
const beritaState = useProxy(stateDashboardBerita)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
beritaState.berita.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
beritaState.berita.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/desa/berita")
}
}
if (!beritaState.berita.findUnique.data) {
return (
<Stack py={10}>
{Array.from({ length: 10 }).map((_, k) => (
<Skeleton key={k} h={40} />
))}
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Berita</Text>
{beritaState.berita.findUnique.data ? (
<Paper key={beritaState.berita.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Kategori</Text>
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.kategoriBerita?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul</Text>
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.judul}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} >{beritaState.berita.findUnique.data?.deskripsi}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={beritaState.berita.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Konten</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: beritaState.berita.findUnique.data?.content }} />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
setSelectedId(beritaState.berita.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={beritaState.berita.delete.loading || !beritaState.berita.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (beritaState.berita.findUnique.data) {
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
}
}}
disabled={!beritaState.berita.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus berita ini?'
/>
</Box>
);
}
export default DetailBerita;

View File

@@ -0,0 +1,143 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
import { Button, Stack } from '@mantine/core';
import { useEffect, useState } from 'react';
// const content =
// '<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup>&lt;sup /&gt;</sup> and <sub>&lt;sub /&gt;</sub> tags)</li><li>Ordered and bullet lists</li><li>Text align&nbsp;</li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
export function BeritaEditor({
onEditorReady,
showSubmit = true,
onSubmit,
initialContent = '',
onUpdate,
}: {
onEditorReady?: (editor: any | null) => void;
onSubmit?: (val: string) => void;
showSubmit?: boolean;
initialContent?: string;
onUpdate?: (content: string) => void;
}) {
const [mounted, setMounted] = useState(false);
const [isReady, setIsReady] = useState(false);
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content: initialContent || '<p></p>',
onUpdate: ({ editor }) => {
if (onUpdate) {
onUpdate(editor.getHTML());
}
},
editorProps: {
attributes: {
class: 'prose max-w-none',
},
},
onSelectionUpdate: () => {
if (!isReady && editor) {
setIsReady(true);
onEditorReady?.(editor);
}
},
immediatelyRender: false
});
useEffect(() => {
if (editor) {
// Set initial content when component mounts
editor.commands.setContent(initialContent || '<p></p>');
// Mark as mounted and notify parent
if (!mounted) {
setMounted(true);
onEditorReady?.(editor);
}
}
return () => {
if (editor) {
editor.destroy();
}
};
}, [editor, initialContent, mounted, onEditorReady]);
if (!editor) return <div>Loading editor...</div>;
return (
<Stack>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
{showSubmit && (
<Button onClick={() => {
if (!editor) return
onSubmit?.(editor?.getHTML())
}}>Submit</Button>
)}
</Stack>
);
}

View File

@@ -0,0 +1,168 @@
'use client'
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Select, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import CreateEditor from '../../../_com/createEditor';
import stateDashboardBerita from '../../../_state/desa/berita';
export default function CreateBerita() {
const beritaState = useProxy(stateDashboardBerita);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter()
const resetForm = () => {
// Reset state di valtio
beritaState.berita.create.form = {
judul: "",
deskripsi: "",
kategoriBeritaId: "",
imageId: "",
content: "",
};
// Reset state lokal
setPreviewImage(null);
setFile(null);
};
const handleSubmit = async () => {
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
}
// Upload gambar dulu
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");
}
// Simpan ID gambar ke form
beritaState.berita.create.form.imageId = uploaded.id;
// Submit data berita
await beritaState.berita.create.create();
// Reset form setelah submit
resetForm();
router.push("/admin/desa/berita")
};
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 Berita</Title>
<TextInput
value={beritaState.berita.create.form.judul}
onChange={(val) => {
beritaState.berita.create.form.judul = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
placeholder="masukkan judul"
/>
<SelectCategory
onChange={(val) => {
beritaState.berita.create.form.kategoriBeritaId = val.id;
}}
/>
<TextInput
value={beritaState.berita.create.form.deskripsi}
onChange={(val) => {
beritaState.berita.create.form.deskripsi = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<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={beritaState.berita.create.form.content}
onChange={(htmlContent) => {
beritaState.berita.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
</Stack>
</Paper>
</Box>
);
function SelectCategory({
onChange,
}: {
onChange: (value: Prisma.KategoriBeritaGetPayload<{
select: {
name: true;
id: true;
};
}>) => void;
}) {
const categoryState = useProxy(stateDashboardBerita.category);
useShallowEffect(() => {
categoryState.findMany.load();
}, []);
if (!categoryState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<Select
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
placeholder="Pilih kategori"
data={categoryState.findMany.data.map((item) => ({
label: item.name,
value: item.id,
}))}
onChange={(val) => {
const selected = categoryState.findMany.data?.find((item) => item.id === val);
if (selected) {
onChange(selected);
}
}}
searchable
nothingFoundMessage="Tidak ditemukan"
/>
);
}
}

View File

@@ -0,0 +1,123 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
import stateDashboardBerita from '../../_state/desa/berita';
function Page() {
return (
<Box>
<Grid>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>Berita</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius={"lg"}
placeholder='pencarian'
leftSection={<IconSearch size={20} />}
/>
</Paper>
</GridCol>
</Grid>
<BeritaList />
</Box>
);
}
function BeritaList() {
const beritaState = useProxy(stateDashboardBerita)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
const router = useRouter()
const handleHapus = () => {
if (selectedId) {
beritaState.berita.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
}
}
if (!beritaState.berita.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push("/admin/desa/berita/create")} bg={colors['blue-button']}>
<IconCircleDashedPlus size={25} />
</Button>
</GridCol>
</Grid>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh w={250}>Judul</TableTh>
<TableTh w={250}>Kategori</TableTh>
<TableTh w={250}>Image</TableTh>
<TableTh w={200}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody >
{beritaState.berita.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd >
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
</Box>
</TableTd>
<TableTd >{item.kategoriBerita?.name}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
<TableTd>
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table> </Box>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus berita ini?'
/>
</Box>
)
}
export default Page;

View File

@@ -0,0 +1,46 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function CreateFoto() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Foto</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
placeholder='Masukkan tanggal foto'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateFoto;

View File

@@ -0,0 +1,62 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailFoto() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Foto</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"}>Judul Foto</Text>
<Text>Foto 1</Text>
</Box>
<Box>
<Text fw={"bold"}>Tanggal Foto</Text>
<Text>2022-01-01</Text>
</Box>
<Box>
<Text fw={"bold"}>Deskripsi Foto</Text>
<Text>Deskripsi Foto 1</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/desa/gallery/foto/edit')} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus potensi ini?"
/> */}
</Box>
);
}
export default DetailFoto;

View File

@@ -0,0 +1,46 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function EditFoto() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Foto</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
placeholder='Masukkan tanggal foto'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditFoto;

View File

@@ -0,0 +1,46 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
function Foto() {
const router = useRouter();
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Foto'
href='/admin/desa/gallery/foto/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Judul Foto</TableTh>
<TableTh>Tanggal Foto</TableTh>
<TableTh>Deskripsi Foto</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Foto 1</TableTd>
<TableTd>2022-01-01</TableTd>
<TableTd>Deskripsi Foto 1</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/desa/gallery/foto/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default Foto;

View File

@@ -0,0 +1,35 @@
import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconPhoto, IconVideo } from '@tabler/icons-react';
import Foto from './foto/page';
import Video from './video/page';
function Gallery() {
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Gallery</Title>
<Tabs color={colors['blue-button']} variant="pills" defaultValue="foto">
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="foto" leftSection={<IconPhoto size={12} />}>
Foto
</TabsTab>
<TabsTab value="video" leftSection={<IconVideo size={12} />}>
Video
</TabsTab>
</TabsList>
<TabsPanel value="foto">
<Foto/>
</TabsPanel>
<TabsPanel value="video">
<Video/>
</TabsPanel>
</Tabs>
</Stack>
</Box>
);
}
export default Gallery;

View File

@@ -0,0 +1,46 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function CreateVideo() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Video</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
placeholder='Masukkan judul video'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
placeholder='Masukkan tanggal video'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateVideo;

View File

@@ -0,0 +1,62 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailVideo() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Video</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"}>Judul Video</Text>
<Text>Video 1</Text>
</Box>
<Box>
<Text fw={"bold"}>Tanggal Video</Text>
<Text>2022-01-01</Text>
</Box>
<Box>
<Text fw={"bold"}>Deskripsi Video</Text>
<Text>Deskripsi Video 1</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/desa/gallery/video/edit')} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus potensi ini?"
/> */}
</Box>
);
}
export default DetailVideo;

View File

@@ -0,0 +1,46 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function EditVideo() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Video</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
placeholder='Masukkan judul video'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Video</Text>}
placeholder='Masukkan tanggal video'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditVideo;

View File

@@ -0,0 +1,46 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
function Video() {
const router = useRouter();
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Video'
href='/admin/desa/gallery/video/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Judul Video</TableTh>
<TableTh>Tanggal Video</TableTh>
<TableTh>Deskripsi Video</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Video 1</TableTd>
<TableTd>2022-01-01</TableTd>
<TableTd>Deskripsi Video 1</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/desa/gallery/video/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default Video;

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