Compare commits

..

20 Commits

Author SHA1 Message Date
02462b2c19 API 2 Menu Keamanan 2025-06-29 02:46:17 +08:00
41181d4cb3 Fix Dibagian Data Kesehatan Warga 2025-06-28 00:10:45 +08:00
6d5b8dcf64 UI & API Admin Menu Kesehatan Done 2025-06-27 23:52:00 +08:00
924be5b11b Sisa 1 Tabs aja yang data kesehatan warga 2025-06-26 17:24:57 +08:00
4f6cc66b7c Kebutuhan Deploy 2025-06-26 11:00:15 +08:00
4683034cd7 UI & API Admin Kesehatan Menu Data Kesehatan Warga Sisa 2 Tabs 2025-06-25 15:50:24 +08:00
37de71a75a UI & API Admin Kesehatan Menu Data Kesehatan Warga Sisa 2 Tabs 2025-06-25 15:47:05 +08:00
27fa7ac0fc UI & API Data Kesehatan warga sisa 3 tabs 2025-06-24 17:25:43 +08:00
fc08b2e790 UI & API Admin Kesehatan Done 2025-06-20 08:11:31 +08:00
4a5524ce88 API & UI Kesehatan Sudah Sampai Di Penanganan Darurat 2025-06-20 00:08:13 +08:00
899883ca2a Fix UI & API Admin Kesehatan Puskesmas 2025-06-19 15:40:27 +08:00
10ecc13ad7 API All Kesehatan 2025-06-19 14:12:57 +08:00
58f538425c UI & API Admin Menu Kesehatan 2025-06-19 10:24:50 +08:00
d2f53ff69b Keperluan Deploy 2025-06-18 16:37:08 +08:00
40f0294595 Tambahan buat deploy 2025-06-18 16:32:19 +08:00
6ed0246cea API Profile Desa Udah Clear, API Menu desa udah clear
API & UI Profile Desa Clear
2025-06-18 15:32:06 +08:00
af726043bd API Profile Desa aman, tinggal Profil Perbekel
UI Profile Desa aman, tinggal Profil Perbekel
2025-06-18 14:08:02 +08:00
f4888b53ab API Profile Desa Menu Desa
Fix Eror gallery bagian tabs video
Next UI Profile Desa
2025-06-17 17:30:47 +08:00
f7437708c0 Menu Desa, Sub Menu Yang Tersisa Tinga Tinggal Profile Aja 2025-06-16 17:46:25 +08:00
7bf5ee69d5 Api Admin Menu Desa Sub Menu Layanan Tabs 3 Pelayanan Done 2025-06-16 15:01:15 +08:00
323 changed files with 18732 additions and 5836 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -23,7 +23,7 @@
"@mantine/charts": "^7.17.1",
"@mantine/core": "^7.17.4",
"@mantine/dates": "^8.1.0",
"@mantine/dropzone": "^7.17.0",
"@mantine/dropzone": "^8.1.1",
"@mantine/form": "^8.1.0",
"@mantine/hooks": "^7.17.4",
"@mantine/tiptap": "^7.17.4",

View File

@@ -0,0 +1,7 @@
[
{
"id": "1",
"name": "Pelayanan Penduduk Non-Permanent",
"deskripsi": "<p>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</p>"
}
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "1",
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
"deskripsi": "<p>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</p>",
"link" : "https://oss.go.id/"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Lambang Desa",
"deskripsi" : "<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>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Maskot Desa",
"deskripsi" : "<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>"
}
]

View File

@@ -1,6 +1,6 @@
[
{
"id": "1",
"id": "edit",
"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>",

View File

@@ -1,11 +0,0 @@
[
{
"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,7 @@
[
{
"id": "edit",
"judul": "Sejarah Desa",
"deskripsi": "<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>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id" : "edit",
"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>"
}
]

View File

@@ -1,6 +1,6 @@
[
{
"id": "1",
"id": "edit",
"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>",

View File

@@ -0,0 +1,193 @@
/*
Warnings:
- You are about to drop the column `nomor` on the `DaftarInformasiPublik` table. All the data in the column will be lost.
- You are about to drop the column `image` on the `GalleryFoto` table. All the data in the column will be lost.
- You are about to drop the column `video` on the `GalleryVideo` table. All the data in the column will be lost.
- You are about to drop the column `videosId` on the `GalleryVideo` table. All the data in the column will be lost.
- The primary key for the `IndeksKepuasanMasyarakat` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `profilPerbekelId` on the `ProfileDesa` table. All the data in the column will be lost.
- You are about to drop the column `imageUrl` on the `ProfilePPID` table. All the data in the column will be lost.
- You are about to drop the `Images` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Videos` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `deskripsi` to the `GalleryFoto` table without a default value. This is not possible if the table is not empty.
- Added the required column `deskripsi` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
- Added the required column `linkVideo` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "GalleryFoto" DROP CONSTRAINT "GalleryFoto_imagesId_fkey";
-- DropForeignKey
ALTER TABLE "GalleryVideo" DROP CONSTRAINT "GalleryVideo_videosId_fkey";
-- DropForeignKey
ALTER TABLE "ProfileDesa" DROP CONSTRAINT "ProfileDesa_profilPerbekelId_fkey";
-- DropIndex
DROP INDEX "GalleryVideo_videosId_key";
-- AlterTable
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "nomor";
-- AlterTable
ALTER TABLE "GalleryFoto" DROP COLUMN "image",
ADD COLUMN "deskripsi" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "GalleryVideo" DROP COLUMN "video",
DROP COLUMN "videosId",
ADD COLUMN "deskripsi" TEXT NOT NULL,
ADD COLUMN "linkVideo" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "IndeksKepuasanMasyarakat" DROP CONSTRAINT "IndeksKepuasanMasyarakat_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "IndeksKepuasanMasyarakat_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "IndeksKepuasanMasyarakat_id_seq";
-- AlterTable
ALTER TABLE "ProfileDesa" DROP COLUMN "profilPerbekelId";
-- AlterTable
ALTER TABLE "ProfilePPID" DROP COLUMN "imageUrl",
ADD COLUMN "imageId" TEXT;
-- DropTable
DROP TABLE "Images";
-- DropTable
DROP TABLE "Videos";
-- CreateTable
CREATE TABLE "StrukturPPID" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"imageId" 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 "StrukturPPID_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProfileDesaImage" (
"id" TEXT NOT NULL,
"label" TEXT NOT NULL,
"profileDesaId" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
CONSTRAINT "ProfileDesaImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananSuratKeterangan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "PelayananSuratKeterangan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananTelunjukSaktiDesa" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" 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 "PelayananTelunjukSaktiDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananPerizinanBerusaha" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" 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 "PelayananPerizinanBerusaha_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananPendudukNonPermanen" (
"id" TEXT NOT NULL,
"name" 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 "PelayananPendudukNonPermanen_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Penghargaan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"juara" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "Penghargaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Posyandu" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"nomor" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "Posyandu_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "StrukturPPID" ADD CONSTRAINT "StrukturPPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfilePPID" ADD CONSTRAINT "ProfilePPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_profileDesaId_fkey" FOREIGN KEY ("profileDesaId") REFERENCES "ProfileDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GalleryFoto" ADD CONSTRAINT "GalleryFoto_imagesId_fkey" FOREIGN KEY ("imagesId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PelayananSuratKeterangan" ADD CONSTRAINT "PelayananSuratKeterangan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Penghargaan" ADD CONSTRAINT "Penghargaan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Posyandu" ADD CONSTRAINT "Posyandu_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,78 @@
/*
Warnings:
- You are about to drop the column `profileDesaId` on the `ProfileDesaImage` table. All the data in the column will be lost.
- You are about to drop the `ProfileDesa` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `maskotDesaId` to the `ProfileDesaImage` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "ProfileDesaImage" DROP CONSTRAINT "ProfileDesaImage_profileDesaId_fkey";
-- AlterTable
ALTER TABLE "ProfilPerbekel" ADD COLUMN "imageId" TEXT;
-- AlterTable
ALTER TABLE "ProfileDesaImage" DROP COLUMN "profileDesaId",
ADD COLUMN "maskotDesaId" TEXT NOT NULL;
-- DropTable
DROP TABLE "ProfileDesa";
-- CreateTable
CREATE TABLE "SejarahDesa" (
"id" TEXT NOT NULL,
"judul" 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 "SejarahDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VisiMisiDesa" (
"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 "VisiMisiDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LambangDesa" (
"id" TEXT NOT NULL,
"judul" 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 "LambangDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MaskotDesa" (
"id" TEXT NOT NULL,
"judul" 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 "MaskotDesa_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_maskotDesaId_fkey" FOREIGN KEY ("maskotDesaId") REFERENCES "MaskotDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfilPerbekel" ADD CONSTRAINT "ProfilPerbekel_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,139 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "DataKematian_Kelahiran_id_seq";
-- AlterTable
ALTER TABLE "ProfilePPID" ADD COLUMN "imageUrl" TEXT;
-- CreateTable
CREATE TABLE "Puskesmas" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"jamId" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"kontakId" 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 "Puskesmas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JamOperasional" (
"id" TEXT NOT NULL,
"workDays" TEXT NOT NULL,
"weekDays" TEXT NOT NULL,
"holiday" 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 "JamOperasional_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakPuskesmas" (
"id" TEXT NOT NULL,
"kontakPuskesmas" TEXT NOT NULL,
"email" TEXT NOT NULL,
"facebook" TEXT NOT NULL,
"kontakUGD" 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 "KontakPuskesmas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKesehatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsiSingkat" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "ProgramKesehatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PenangananDarurat" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "PenangananDarurat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakDarurat" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" 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 "KontakDarurat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InfoWabahPenyakit" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsiSingkat" TEXT NOT NULL,
"deskripsiLengkap" TEXT NOT NULL,
"imageId" 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 "InfoWabahPenyakit_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_jamId_fkey" FOREIGN KEY ("jamId") REFERENCES "JamOperasional"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_kontakId_fkey" FOREIGN KEY ("kontakId") REFERENCES "KontakPuskesmas"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramKesehatan" ADD CONSTRAINT "ProgramKesehatan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PenangananDarurat" ADD CONSTRAINT "PenangananDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakDarurat" ADD CONSTRAINT "KontakDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InfoWabahPenyakit" ADD CONSTRAINT "InfoWabahPenyakit_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "GrafikKepuasan_id_seq";

View File

@@ -0,0 +1,96 @@
/*
Warnings:
- You are about to drop the `_DokterdanTenagaMedisToFasilitasKesehatan` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToFasilitasPendukung` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToInformasiUmum` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToLayananUnggulan` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToProsedurPendaftaran` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToTarifDanLayanan` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `dokterdanTenagaMedisId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `fasilitasPendukungId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `informasiUmumId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `layananUnggulanId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `prosedurPendaftaranId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `tarifDanLayananId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" DROP CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_A_fkey";
-- DropForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" DROP CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" DROP CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" DROP CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" DROP CONSTRAINT "_FasilitasKesehatanToInformasiUmum_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" DROP CONSTRAINT "_FasilitasKesehatanToInformasiUmum_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" DROP CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" DROP CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" DROP CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" DROP CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" DROP CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" DROP CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_B_fkey";
-- AlterTable
ALTER TABLE "FasilitasKesehatan" ADD COLUMN "dokterdanTenagaMedisId" TEXT NOT NULL,
ADD COLUMN "fasilitasPendukungId" TEXT NOT NULL,
ADD COLUMN "informasiUmumId" TEXT NOT NULL,
ADD COLUMN "layananUnggulanId" TEXT NOT NULL,
ADD COLUMN "prosedurPendaftaranId" TEXT NOT NULL,
ADD COLUMN "tarifDanLayananId" TEXT NOT NULL;
-- DropTable
DROP TABLE "_DokterdanTenagaMedisToFasilitasKesehatan";
-- DropTable
DROP TABLE "_FasilitasKesehatanToFasilitasPendukung";
-- DropTable
DROP TABLE "_FasilitasKesehatanToInformasiUmum";
-- DropTable
DROP TABLE "_FasilitasKesehatanToLayananUnggulan";
-- DropTable
DROP TABLE "_FasilitasKesehatanToProsedurPendaftaran";
-- DropTable
DROP TABLE "_FasilitasKesehatanToTarifDanLayanan";
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_informasiUmumId_fkey" FOREIGN KEY ("informasiUmumId") REFERENCES "InformasiUmum"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_layananUnggulanId_fkey" FOREIGN KEY ("layananUnggulanId") REFERENCES "LayananUnggulan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_dokterdanTenagaMedisId_fkey" FOREIGN KEY ("dokterdanTenagaMedisId") REFERENCES "DokterdanTenagaMedis"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_fasilitasPendukungId_fkey" FOREIGN KEY ("fasilitasPendukungId") REFERENCES "FasilitasPendukung"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_prosedurPendaftaranId_fkey" FOREIGN KEY ("prosedurPendaftaranId") REFERENCES "ProsedurPendaftaran"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_tarifDanLayananId_fkey" FOREIGN KEY ("tarifDanLayananId") REFERENCES "TarifDanLayanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,32 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `DataKematian_Kelahiran` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `GrafikKepuasan` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- A unique constraint covering the columns `[uuid]` on the table `DataKematian_Kelahiran` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[uuid]` on the table `GrafikKepuasan` will be added. If there are existing duplicate values, this will fail.
- The required column `uuid` was added to the `DataKematian_Kelahiran` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
- The required column `uuid` was added to the `GrafikKepuasan` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
*/
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
ADD COLUMN "uuid" TEXT NOT NULL,
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
ADD COLUMN "uuid" TEXT NOT NULL,
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
-- CreateIndex
CREATE UNIQUE INDEX "DataKematian_Kelahiran_uuid_key" ON "DataKematian_Kelahiran"("uuid");
-- CreateIndex
CREATE UNIQUE INDEX "GrafikKepuasan_uuid_key" ON "GrafikKepuasan"("uuid");

View File

@@ -0,0 +1,92 @@
/*
Warnings:
- The primary key for the `ArtikelKesehatan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `DoctorSign` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `FirstAid` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Introduction` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `MythVsFact` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Prevention` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Symptom` table will be changed. If it partially fails, the table could be left without primary key constraint.
- Added the required column `deskripsiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `dokumenJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `informasiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `layananJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `pendaftaranJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `syaratKetentuanJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "ArtikelKesehatan" DROP CONSTRAINT "ArtikelKesehatan_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "ArtikelKesehatan_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "ArtikelKesehatan_id_seq";
-- AlterTable
ALTER TABLE "DoctorSign" DROP CONSTRAINT "DoctorSign_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "DoctorSign_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "DoctorSign_id_seq";
-- AlterTable
ALTER TABLE "FirstAid" DROP CONSTRAINT "FirstAid_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "FirstAid_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "FirstAid_id_seq";
-- AlterTable
ALTER TABLE "Introduction" DROP CONSTRAINT "Introduction_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Introduction_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Introduction_id_seq";
-- AlterTable
ALTER TABLE "JadwalKegiatan" ADD COLUMN "deskripsiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "dokumenJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "informasiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "layananJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "pendaftaranJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "syaratKetentuanJadwalKegiatanId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "MythVsFact" DROP CONSTRAINT "MythVsFact_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "MythVsFact_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "MythVsFact_id_seq";
-- AlterTable
ALTER TABLE "Prevention" DROP CONSTRAINT "Prevention_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Prevention_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Prevention_id_seq";
-- AlterTable
ALTER TABLE "Symptom" DROP CONSTRAINT "Symptom_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Symptom_id_seq";
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_informasiJadwalKegiatanId_fkey" FOREIGN KEY ("informasiJadwalKegiatanId") REFERENCES "InformasiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_deskripsiJadwalKegiatanId_fkey" FOREIGN KEY ("deskripsiJadwalKegiatanId") REFERENCES "DeskripsiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_layananJadwalKegiatanId_fkey" FOREIGN KEY ("layananJadwalKegiatanId") REFERENCES "LayananJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_syaratKetentuanJadwalKegiatanId_fkey" FOREIGN KEY ("syaratKetentuanJadwalKegiatanId") REFERENCES "SyaratKetentuanJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_dokumenJadwalKegiatanId_fkey" FOREIGN KEY ("dokumenJadwalKegiatanId") REFERENCES "DokumenJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_pendaftaranJadwalKegiatanId_fkey" FOREIGN KEY ("pendaftaranJadwalKegiatanId") REFERENCES "PendaftaranJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,68 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `DataKematian_Kelahiran` table. All the data in the column will be lost.
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `GrafikKepuasan` table. All the data in the column will be lost.
- A unique constraint covering the columns `[id]` on the table `DataKematian_Kelahiran` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[id]` on the table `GrafikKepuasan` will be added. If there are existing duplicate values, this will fail.
- Added the required column `doctorSignId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `firstAidId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `introductionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `mythVsFactId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `preventionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `symptomId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "DataKematian_Kelahiran_uuid_key";
-- DropIndex
DROP INDEX "GrafikKepuasan_uuid_key";
-- AlterTable
ALTER TABLE "ArtikelKesehatan" ADD COLUMN "doctorSignId" TEXT NOT NULL,
ADD COLUMN "firstAidId" TEXT NOT NULL,
ADD COLUMN "introductionId" TEXT NOT NULL,
ADD COLUMN "mythVsFactId" TEXT NOT NULL,
ADD COLUMN "preventionId" TEXT NOT NULL,
ADD COLUMN "symptomId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "DataKematian_Kelahiran_id_seq";
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "GrafikKepuasan_id_seq";
-- CreateIndex
CREATE UNIQUE INDEX "DataKematian_Kelahiran_id_key" ON "DataKematian_Kelahiran"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GrafikKepuasan_id_key" ON "GrafikKepuasan"("id");
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_introductionId_fkey" FOREIGN KEY ("introductionId") REFERENCES "Introduction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_symptomId_fkey" FOREIGN KEY ("symptomId") REFERENCES "Symptom"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_preventionId_fkey" FOREIGN KEY ("preventionId") REFERENCES "Prevention"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_firstAidId_fkey" FOREIGN KEY ("firstAidId") REFERENCES "FirstAid"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_mythVsFactId_fkey" FOREIGN KEY ("mythVsFactId") REFERENCES "MythVsFact"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_doctorSignId_fkey" FOREIGN KEY ("doctorSignId") REFERENCES "DoctorSign"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -50,25 +50,34 @@ model AppMenuChild {
// ========================================= FILE STORAGE ========================================= //
model FileStorage {
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
link String
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
ProfilePPID ProfilePPID[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
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[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
PelayananSuratKeterangan PelayananSuratKeterangan[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
}
//========================================= MENU PPID ========================================= //
@@ -115,6 +124,7 @@ model ProfilePPID {
riwayat String @db.Text
pengalaman String @db.Text
unggulan String @db.Text
imageUrl String?
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
@@ -246,32 +256,68 @@ model GrafikBerdasarkanUmur {
// ========================================= 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 SejarahDesa {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model VisiMisiDesa {
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)
}
model LambangDesa {
id String @id @default(cuid())
judul String
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model MaskotDesa {
id String @id @default(cuid())
judul String
deskripsi String @db.Text
images ProfileDesaImage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ProfileDesaImage {
id String @id @default(cuid())
label String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
MaskotDesa MaskotDesa @relation(fields: [maskotDesaId], references: [id])
maskotDesaId String
}
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)
id String @id @default(cuid())
biodata String @db.Text
pengalaman String @db.Text
pengalamanOrganisasi String @db.Text
programUnggulan String @db.Text
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= BERITA ========================================= //
@@ -365,45 +411,86 @@ model GalleryVideo {
// ========================================= LAYANAN DESA ========================================= //
model PelayananSuratKeterangan {
id String @id @default(cuid())
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PelayananTelunjukSaktiDesa {
id String @id @default(cuid())
name String
deskripsi String @db.Text
link String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PelayananPerizinanBerusaha {
id String @id @default(cuid())
name String
deskripsi String @db.Text
link String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PelayananPendudukNonPermanen {
id String @id @default(cuid())
name String
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PENGHARGAAN ========================================= //
model Penghargaan {
id String @id @default(cuid())
name String
juara String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= MENU KESEHATAN ========================================= //
// ========================================= 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[]
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
informasiUmumId String
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
layananUnggulanId String
dokterdantenagamedis DokterdanTenagaMedis @relation(fields: [dokterdanTenagaMedisId], references: [id])
dokterdanTenagaMedisId String
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
fasilitasPendukungId String
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
prosedurPendaftaranId String
tarifdanlayanan TarifDanLayanan @relation(fields: [tarifDanLayananId], references: [id])
tarifDanLayananId String
}
model InformasiUmum {
@@ -411,10 +498,10 @@ model InformasiUmum {
fasilitas String
alamat String
jamOperasional String
FasilitasKesehatan FasilitasKesehatan[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
FasilitasKesehatan FasilitasKesehatan[]
isActive Boolean @default(true)
}
@@ -473,33 +560,47 @@ model TarifDanLayanan {
// ========================================= 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)
id String @id @default(cuid())
content String
informasijadwalkegiatan InformasiJadwalKegiatan @relation(fields: [informasiJadwalKegiatanId], references: [id])
informasiJadwalKegiatanId String
deskripsijadwalkegiatan DeskripsiJadwalKegiatan @relation(fields: [deskripsiJadwalKegiatanId], references: [id])
deskripsiJadwalKegiatanId String
layananjadwalkegiatan LayananJadwalKegiatan @relation(fields: [layananJadwalKegiatanId], references: [id])
layananJadwalKegiatanId String
syaratketentuanjadwalkegiatan SyaratKetentuanJadwalKegiatan @relation(fields: [syaratKetentuanJadwalKegiatanId], references: [id])
syaratKetentuanJadwalKegiatanId String
dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id])
dokumenJadwalKegiatanId String
pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan @relation(fields: [pendaftaranJadwalKegiatanId], references: [id])
pendaftaranJadwalKegiatanId 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)
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)
JadwalKegiatan JadwalKegiatan[]
}
model DeskripsiJadwalKegiatan {
id String @id @default(cuid())
deskripsi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
deskripsi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model LayananJadwalKegiatan {
@@ -509,6 +610,8 @@ model LayananJadwalKegiatan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model SyaratKetentuanJadwalKegiatan {
@@ -518,34 +621,38 @@ model SyaratKetentuanJadwalKegiatan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model DokumenJadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
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)
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)
JadwalKegiatan JadwalKegiatan[]
}
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
model DataKematian_Kelahiran {
id Int @id @default(autoincrement())
id String @unique @default(cuid())
tahun String
kematianKasar String
kematianBayi String
@@ -558,7 +665,7 @@ model DataKematian_Kelahiran {
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id Int @id @default(autoincrement())
id String @unique @default(cuid())
label String
jumlah String
createdAt DateTime @default(now())
@@ -569,56 +676,74 @@ model GrafikKepuasan {
// ========================================= 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)
id String @id @default(cuid())
title String
content String
introduction Introduction @relation(fields: [introductionId], references: [id])
introductionId String
symptom Symptom @relation(fields: [symptomId], references: [id])
symptomId String
prevention Prevention @relation(fields: [preventionId], references: [id])
preventionId String
firstaid FirstAid @relation(fields: [firstAidId], references: [id])
firstAidId String
mythvsfact MythVsFact @relation(fields: [mythVsFactId], references: [id])
mythVsFactId String
doctorsign DoctorSign @relation(fields: [doctorSignId], references: [id])
doctorSignId 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)
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
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)
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model Prevention {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model FirstAid {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model MythVsFact {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
mitos String
fakta String
@@ -626,15 +751,19 @@ model MythVsFact {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model DoctorSign {
id Int @id @default(autoincrement())
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
// ========================================= POSYANDU ========================================= //
@@ -650,3 +779,141 @@ model Posyandu {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PUSKESMAS ========================================= //
model Puskesmas {
id String @id @default(cuid())
name String
alamat String
jam JamOperasional @relation(fields: [jamId], references: [id])
jamId String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
kontak KontakPuskesmas @relation(fields: [kontakId], references: [id])
kontakId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JamOperasional {
id String @id @default(cuid())
workDays String
weekDays String
holiday String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
Puskesmas Puskesmas[]
}
model KontakPuskesmas {
id String @id @default(cuid())
kontakPuskesmas String
email String
facebook String
kontakUGD String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
Puskesmas Puskesmas[]
}
// ========================================= PROGRAM KESSEHATAN ========================================= //
model ProgramKesehatan {
id String @id @default(cuid())
name String
deskripsiSingkat String
deskripsi String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PENANGANAN DARURAT ========================================= //
model PenangananDarurat {
id String @id @default(cuid())
name String
deskripsi String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= KONTAK DARURAT ========================================= //
model KontakDarurat {
id String @id @default(cuid())
name String
deskripsi String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= INFO WABAH PENYAKIT ========================================= //
model InfoWabahPenyakit {
id String @id @default(cuid())
name String
deskripsiSingkat String
deskripsiLengkap String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= MENU KEAMANAN ========================================= //
// ========================================= KEAMANAN LINGKUNGAN ========================================= //
model KeamananLingkungan {
id String @id @default(cuid())
name String @db.Text
deskripsi String @db.Text
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= POLSEK TERDEKAT ========================================= //
model PolsekTerdekat {
id String @id @default(uuid())
nama String
jarakKeDesa String
alamat String
nomorTelepon String
jamOperasional String
embedMapUrl String
namaTempatMaps String
alamatMaps String
linkPetunjukArah String
layananPolsek LayananPolsek @relation(fields: [layananPolsekId], references: [id])
layananPolsekId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model LayananPolsek {
id String @id @default(uuid())
nama String // contoh: "Pelayanan SKCK", "Laporan Kriminal"
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PolsekTerdekat PolsekTerdekat[]
}

View File

@@ -10,6 +10,13 @@ import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
import lambangDesa from "./data/desa/profile/lambang_desa.json";
import maskotDesa from "./data/desa/profile/maskot_desa.json";
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
(async () => {
for (const l of layanan) {
@@ -28,6 +35,143 @@ import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
console.log("layanan success ...");
for (const l of sejarahDesa) {
await prisma.sejarahDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("sejarah desa success ...");
for (const l of maskotDesa) {
await prisma.maskotDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("maskot desa success ...");
for (const l of lambangDesa) {
await prisma.lambangDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("lambang desa success ...");
for (const c of profilPerbekel) {
await prisma.profilPerbekel.upsert({
where: { id: c.id },
update: {
biodata: c.biodata,
pengalaman: c.pengalaman,
pengalamanOrganisasi: c.pengalamanOrganisasi,
programUnggulan: c.programUnggulan,
// imageId tidak di-update
},
create: {
id: c.id,
biodata: c.biodata,
pengalaman: c.pengalaman,
pengalamanOrganisasi: c.pengalamanOrganisasi,
programUnggulan: c.programUnggulan,
// imageId tidak di-create
},
});
}
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
for (const l of visiMisiDesa) {
await prisma.visiMisiDesa.upsert({
where: {
id: l.id,
},
update: {
visi: l.visi,
misi: l.misi,
},
create: {
id: l.id,
visi: l.visi,
misi: l.misi,
},
});
}
console.log("visi misi desa success ...");
for (const l of pelayananPerizinanBerusaha) {
await prisma.pelayananPerizinanBerusaha.upsert({
where: {
id: l.id,
},
update: {
name: l.name,
deskripsi: l.deskripsi,
link: l.link,
},
create: {
id: l.id,
name: l.name,
deskripsi: l.deskripsi,
link: l.link,
},
});
}
console.log("pelayanan perizinan berusaha success ...");
for (const l of pelayananPendudukNonPermanen) {
await prisma.pelayananPendudukNonPermanen.upsert({
where: {
id: l.id,
},
update: {
name: l.name,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
name: l.name,
deskripsi: l.deskripsi,
},
});
}
console.log("pelayanan penduduk non permanen success ...");
for (const s of strukturPPID) {
await prisma.strukturPPID.upsert({
where: {

BIN
public/bungapudak.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
public/darmasaba-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/klimakstari.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
public/pohonpudak.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

BIN
public/tarisekar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

@@ -16,6 +16,60 @@ const suratKeteranganForm = {
imageId: "",
};
const telunjukSaktiDesaForm = {
name: "",
deskripsi: "",
link: "",
};
const templateTelunjukSaktiDesaForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
const templatePelayananPerizinanBerusaha = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
link: z.string().min(3, "Link minimal 3 karakter"),
});
type pelayananPerizinanBerusahaForm =
Prisma.PelayananPerizinanBerusahaGetPayload<{
select: {
id: true;
name: true;
deskripsi: true;
link: true;
};
}>;
const pelayananPerizinanBerusahaForm = {
name: "",
deskripsi: "",
link: "",
};
const templatePelayananPendudukNonPermanen = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type pelayananPendudukNonPermanenForm =
Prisma.PelayananPendudukNonPermanenGetPayload<{
select: {
id: true;
name: true;
deskripsi: true;
};
}>;
const pelayananPendudukNonPermanenForm = {
name: "",
deskripsi: "",
};
const suratKeterangan = proxy({
create: {
form: { ...suratKeteranganForm },
@@ -173,20 +227,25 @@ const suratKeterangan = proxy({
}
try {
suratKeterangan.edit.loading = true;
const response = await fetch(`/api/desa/layanan/pelayanansuratketerangan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
const response = await fetch(
`/api/desa/layanan/pelayanansuratketerangan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
@@ -194,11 +253,17 @@ const suratKeterangan = proxy({
await suratKeterangan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate surat keterangan");
throw new Error(
result.message || "Gagal mengupdate surat keterangan"
);
}
} catch (error) {
console.error("Error updating surat keterangan:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update surat keterangan");
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update surat keterangan"
);
return false;
} finally {
suratKeterangan.edit.loading = false;
@@ -207,9 +272,447 @@ const suratKeterangan = proxy({
},
});
const pelayananTelunjukSaktiDesa = proxy({
create: {
form: { ...telunjukSaktiDesaForm },
loading: false,
async create() {
const cek = templateTelunjukSaktiDesaForm.safeParse(
pelayananTelunjukSaktiDesa.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pelayananTelunjukSaktiDesa.create.loading = true;
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"create"
].post(pelayananTelunjukSaktiDesa.create.form);
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.load();
return toast.success("Telunjuk Sakti Desa berhasil disimpan!");
}
return toast.error("Gagal menyimpan telunjuk sakti desa");
} catch (error) {
console.log((error as Error).message);
} finally {
pelayananTelunjukSaktiDesa.create.loading = false;
}
},
resetForm() {
pelayananTelunjukSaktiDesa.create.form = { ...telunjukSaktiDesaForm };
},
},
findMany: {
data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get();
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananTelunjukSaktiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch telunjuk sakti desa:", res.statusText);
pelayananTelunjukSaktiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching telunjuk sakti desa:", error);
pelayananTelunjukSaktiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pelayananTelunjukSaktiDesa.delete.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus");
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
} else {
toast.error(result.message || "Gagal menghapus telunjuk sakti desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus telunjuk sakti desa");
} finally {
pelayananTelunjukSaktiDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...telunjukSaktiDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
link: data.link,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching telunjuk sakti desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateTelunjukSaktiDesaForm.safeParse(
pelayananTelunjukSaktiDesa.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pelayananTelunjukSaktiDesa.edit.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
link: this.form.link,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate");
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate telunjuk sakti desa"
);
}
} catch (error) {
console.error("Error updating telunjuk sakti desa:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update telunjuk sakti desa"
);
return false;
} finally {
pelayananTelunjukSaktiDesa.edit.loading = false;
}
},
},
})
const pelayananPerizinanBerusaha = proxy({
findById: {
data: null as pelayananPerizinanBerusahaForm | null,
loading: false,
initialize() {
pelayananPerizinanBerusaha.findById.data = {
id: "",
name: "",
deskripsi: "",
link: "",
} as pelayananPerizinanBerusahaForm;
},
async load(id: string) {
try {
pelayananPerizinanBerusaha.findById.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananPerizinanBerusaha.findById.data = data.data ?? null;
} else {
console.error(
"Failed to fetch pelayanan perizinan berusaha:",
res.statusText
);
pelayananPerizinanBerusaha.findById.data = null;
}
} catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error);
pelayananPerizinanBerusaha.findById.data = null;
}
},
},
update: {
id: "",
form: { ...pelayananPerizinanBerusahaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak boleh kosong");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
pelayananPerizinanBerusaha.update.id = data.id;
pelayananPerizinanBerusaha.update.form = {
name: data.name,
deskripsi: data.deskripsi,
link: data.link,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal memuat data"
);
return null;
}
},
async update(data: pelayananPerizinanBerusahaForm) {
const cek = templatePelayananPerizinanBerusaha.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
pelayananPerizinanBerusaha.update.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${data.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
if (res.ok) {
toast.success("Pelayanan perizinan berusaha berhasil diupdate");
await pelayananPerizinanBerusaha.findById.load(data.id);
} else {
toast.error("Gagal mengupdate pelayanan perizinan berusaha");
}
} catch (error) {
console.error("Error updating pelayanan perizinan berusaha:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pelayanan perizinan berusaha"
);
} finally {
pelayananPerizinanBerusaha.update.loading = false;
}
},
},
});
const pelayananPendudukNonPermanen = proxy({
findById: {
data: null as pelayananPendudukNonPermanenForm | null,
loading: false,
initialize() {
pelayananPendudukNonPermanen.findById.data = {
id: "",
name: "",
deskripsi: "",
} as pelayananPendudukNonPermanenForm;
},
async load(id: string) {
try {
pelayananPendudukNonPermanen.findById.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananPendudukNonPermanen.findById.data = data.data ?? null;
} else {
console.error(
"Failed to fetch pelayanan penduduk non permanen:",
res.statusText
);
pelayananPendudukNonPermanen.findById.data = null;
}
} catch (error) {
console.error("Error fetching pelayanan penduduk non permanen:", error);
pelayananPendudukNonPermanen.findById.data = null;
}
},
},
update: {
id: "",
form: { ...pelayananPendudukNonPermanenForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak boleh kosong");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
pelayananPendudukNonPermanen.update.id = data.id;
pelayananPendudukNonPermanen.update.form = {
name: data.name,
deskripsi: data.deskripsi,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching pelayanan penduduk non permanen:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal memuat data"
);
return null;
}
},
async update(data: pelayananPendudukNonPermanenForm) {
const cek = templatePelayananPendudukNonPermanen.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
pelayananPendudukNonPermanen.update.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${data.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
if (res.ok) {
toast.success("Pelayanan penduduk non permanen berhasil diupdate");
await pelayananPendudukNonPermanen.findById.load(data.id);
} else {
toast.error("Gagal mengupdate pelayanan penduduk non permanen");
}
} catch (error) {
console.error("Error updating pelayanan penduduk non permanen:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pelayanan penduduk non permanen"
);
} finally {
pelayananPendudukNonPermanen.update.loading = false;
}
},
},
});
const stateLayananDesa = proxy({
suratKeterangan,
pelayananPerizinanBerusaha,
pelayananTelunjukSaktiDesa,
pelayananPendudukNonPermanen,
});
export default stateLayananDesa;

View File

@@ -0,0 +1,221 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
juara: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
});
const defaultForm = {
name: "",
juara: "",
deskripsi: "",
imageId: "",
};
const penghargaanState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(penghargaanState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
penghargaanState.create.loading = true;
const res = await ApiFetch.api.desa.penghargaan["create"].post(
penghargaanState.create.form
);
if (res.status === 200) {
penghargaanState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
penghargaanState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PenghargaanGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.penghargaan["find-many"].get();
if (res.status === 200) {
penghargaanState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PenghargaanGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/penghargaan/${id}`);
if (res.ok) {
const data = await res.json();
penghargaanState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
penghargaanState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading penghargaan:", error);
penghargaanState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
penghargaanState.delete.loading = true;
const response = await fetch(`/api/desa/penghargaan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Penghargaan berhasil dihapus");
await penghargaanState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus penghargaan");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus penghargaan");
} finally {
penghargaanState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/penghargaan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
juara: data.juara,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading penghargaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(penghargaanState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
penghargaanState.edit.loading = true;
const response = await fetch(
`/api/desa/penghargaan/${penghargaanState.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
juara: this.form.juara,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update penghargaan");
await penghargaanState.findMany.load();
return true;
} else {
throw new Error(result?.message || "Gagal update penghargaan");
}
} catch (error) {
console.error("Error updating penghargaan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update penghargaan"
);
return false;
} finally {
penghargaanState.edit.loading = false;
}
},
reset() {
penghargaanState.edit.id = "";
penghargaanState.edit.form = { ...defaultForm };
},
},
});
export default penghargaanState;

View File

@@ -6,7 +6,7 @@ import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
kategori: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),

File diff suppressed because it is too large Load Diff

View File

@@ -1,339 +1,306 @@
/* 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";
/* Introduction */
const templateIntroduction = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
const templateForm = z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
introduction: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
symptom: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
prevention: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
firstAid: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
mythVsFact: z.object({
title: z.string().min(1, "Judul harus diisi"),
mitos: z.string().min(1, "Mitos harus diisi"),
fakta: z.string().min(1, "Fakta harus diisi"),
}),
doctorSign: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
});
type Introduction = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
const defaultForm = {
title: "",
content: "",
introduction: {
content: "",
},
symptom: {
title: "",
content: "",
},
prevention: {
title: "",
content: "",
},
firstAid: {
title: "",
content: "",
},
mythVsFact: {
title: "",
mitos: "",
fakta: "",
},
doctorSign: {
content: "",
},
};
const introduction = proxy({
const artikelKesehatanState = proxy({
create: {
form: {} as Introduction,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateIntroduction.safeParse(introduction.create.form);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
introduction.create.loading = true;
const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"artikel-kesehatan"
].create.post(payload);
if (res.status === 200) {
introduction.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan artikel kesehatan");
this.resetForm();
await artikelKesehatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
introduction.create.loading = false;
this.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;
}
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[]
| Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.kesehatan.symptom["find-many"].get();
if (res.status === 200) {
symptom.findMany.data = res.data?.data ?? [];
try {
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)["artikel-kesehatan"][
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data artikel kesehatan");
}
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}> | null,
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
artikelKesehatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data artikel kesehatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data artikel kesehatan");
return;
}
const result = await res.json();
const data = result.data;
artikelKesehatanState.edit.id = data.id;
artikelKesehatanState.edit.form = {
title: data.title,
content: data.content,
introduction: {
content: data.introduction.content,
},
symptom: {
title: data.symptom.title,
content: data.symptom.content,
},
prevention: {
title: data.prevention.title,
content: data.prevention.content,
},
firstAid: {
title: data.firstaid.title,
content: data.firstaid.content,
},
mythVsFact: {
title: data.mythvsfact.title,
mitos: data.mythvsfact.mitos,
fakta: data.mythvsfact.fakta,
},
doctorSign: {
content: data.doctorsign.content,
},
};
},
async submit() {
const cek = templateForm.safeParse(artikelKesehatanState.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
artikelKesehatanState.edit.loading = true;
const payload = {
title: artikelKesehatanState.edit.form.title,
content: artikelKesehatanState.edit.form.content,
introduction: {
content: artikelKesehatanState.edit.form.introduction.content,
},
symptom: {
title: artikelKesehatanState.edit.form.symptom.title,
content: artikelKesehatanState.edit.form.symptom.content,
},
prevention: {
title: artikelKesehatanState.edit.form.prevention.title,
content: artikelKesehatanState.edit.form.prevention.content,
},
firstAid: {
title: artikelKesehatanState.edit.form.firstAid.title,
content: artikelKesehatanState.edit.form.firstAid.content,
},
mythVsFact: {
title: artikelKesehatanState.edit.form.mythVsFact.title,
mitos: artikelKesehatanState.edit.form.mythVsFact.mitos,
fakta: artikelKesehatanState.edit.form.mythVsFact.fakta,
},
doctorSign: {
content: artikelKesehatanState.edit.form.doctorSign.content,
},
};
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/${artikelKesehatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update artikel kesehatan");
await artikelKesehatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
artikelKesehatanState.edit.loading = false;
}
},
resetForm() {
artikelKesehatanState.edit.id = "";
artikelKesehatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
try {
artikelKesehatanState.delete.loading = true;
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result.success) {
toast.success("Artikel kesehatan berhasil dihapus");
await artikelKesehatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
artikelKesehatanState.delete.loading = false;
}
},
},
});
/* ======================================================================= */
/* 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
export default artikelKesehatanState;

View File

@@ -1,333 +1,308 @@
/* 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";
/* 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"),
// Validasi form
const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"),
informasiUmum: z.object({
fasilitas: z.string().min(1, "Fasilitas harus diisi"),
alamat: z.string().min(1, "Alamat harus diisi"),
jamOperasional: z.string().min(1, "Jam operasional harus diisi"),
}),
layananUnggulan: z.object({
content: z.string().min(1, "Layanan unggulan harus diisi"),
}),
dokterdanTenagaMedis: z.object({
name: z.string().min(1, "Nama dokter harus diisi"),
specialist: z.string().min(1, "Spesialis harus diisi"),
jadwal: z.string().min(1, "Jadwal harus diisi"),
}),
fasilitasPendukung: z.object({
content: z.string().min(1, "Fasilitas pendukung harus diisi"),
}),
prosedurPendaftaran: z.object({
content: z.string().min(1, "Prosedur pendaftaran harus diisi"),
}),
tarifDanLayanan: z.object({
layanan: z.string().min(1, "Layanan harus diisi"),
tarif: z.string().min(1, "Tarif harus diisi"),
}),
});
type InfromasiUmum = Prisma.InformasiUmumGetPayload<{
select: {
fasilitas: true;
alamat: true;
jamOperasional: true;
};
}>;
// Default form kosong
const defaultForm = {
name: "",
informasiUmum: {
fasilitas: "",
alamat: "",
jamOperasional: "",
},
layananUnggulan: {
content: "",
},
dokterdanTenagaMedis: {
name: "",
specialist: "",
jadwal: "",
},
fasilitasPendukung: {
content: "",
},
prosedurPendaftaran: {
content: "",
},
tarifDanLayanan: {
layanan: "",
tarif: "",
},
};
const informasiumum = proxy({
const fasilitasKesehatanState = proxy({
create: {
form: {} as InfromasiUmum,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateInformasiUmum.safeParse(informasiumum.create.form);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
informasiumum.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiumum["create"].post(
informasiumum.create.form
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"fasilitas-kesehatan"
].create.post(payload);
if (res.status === 200) {
toast.success("Berhasil menambahkan fasilitas kesehatan");
this.resetForm();
await fasilitasKesehatanState.findMany.load();
return res.data;
}
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
this.loading = false;
}
},
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.FasilitasKesehatanGetPayload<{
include: {
informasiumum: true;
layananunggulan: true;
dokterdantenagamedis: true;
fasilitaspendukung: true;
prosedurpendaftaran: true;
tarifdanlayanan: true;
};
}>[]
| null,
loading: false,
async load() {
try {
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)[
"fasilitas-kesehatan"
]["find-many"].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data fasilitas kesehatan");
}
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.FasilitasKesehatanGetPayload<{
include: {
informasiumum: true;
layananunggulan: true;
dokterdantenagamedis: true;
fasilitaspendukung: true;
prosedurpendaftaran: true;
tarifdanlayanan: true;
};
}> | null,
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
fasilitasKesehatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data fasilitas kesehatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data fasilitas kesehatan");
return;
}
const result = await res.json();
const data = result.data;
fasilitasKesehatanState.edit.id = data.id;
fasilitasKesehatanState.edit.form = {
name: data.name,
informasiUmum: {
fasilitas: data.informasiumum.fasilitas,
alamat: data.informasiumum.alamat,
jamOperasional: data.informasiumum.jamOperasional,
},
layananUnggulan: {
content: data.layananunggulan.content,
},
dokterdanTenagaMedis: {
name: data.dokterdantenagamedis.name,
specialist: data.dokterdantenagamedis.specialist,
jadwal: data.dokterdantenagamedis.jadwal,
},
fasilitasPendukung: {
content: data.fasilitaspendukung.content,
},
prosedurPendaftaran: {
content: data.prosedurpendaftaran.content,
},
tarifDanLayanan: {
layanan: data.tarifdanlayanan.layanan,
tarif: data.tarifdanlayanan.tarif,
},
};
},
async submit() {
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
fasilitasKesehatanState.edit.loading = true;
const payload = {
name: fasilitasKesehatanState.edit.form.name,
informasiUmum: {
fasilitas:
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
jamOperasional:
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional,
},
layananUnggulan: {
content: fasilitasKesehatanState.edit.form.layananUnggulan.content,
},
dokterdanTenagaMedis: {
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name,
specialist:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist,
jadwal:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: {
content:
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
},
prosedurPendaftaran: {
content:
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
},
tarifDanLayanan: {
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif,
},
};
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
if (res.status === 200) {
informasiumum.findMany.load();
return toast.success("success create");
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
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
toast.success("Berhasil update fasilitas kesehatan");
await fasilitasKesehatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
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);
return false;
} finally {
layananunggulan.create.loading = false;
fasilitasKesehatanState.edit.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 ?? [];
}
resetForm() {
fasilitasKesehatanState.edit.id = "";
fasilitasKesehatanState.edit.form = { ...defaultForm };
},
},
})
/* ======================================================================= */
/* 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,
delete: {
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);
}
async byId(id: string){
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");
fasilitasKesehatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Fasilitas kesehatan berhasil dihapus");
await fasilitasKesehatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
dokterdantenagamedis.create.loading = false;
fasilitasKesehatanState.delete.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
export default fasilitasKesehatanState;

View File

@@ -6,7 +6,7 @@ import { z } from "zod";
const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"),
jumlah: z.string().min(2, "Jumlah harus diisi"),
jumlah: z.string().min(1, "Jumlah harus diisi"),
});
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
@@ -31,24 +31,30 @@ const grafikkepuasan = proxy({
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
toast.error(err);
return null;
}
try {
grafikkepuasan.create.loading = true;
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
grafikkepuasan.create.form
);
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");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.findMany.load();
return id;
}
}
return toast.error("failed create");
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
grafikkepuasan.create.loading = false;
}
@@ -67,10 +73,110 @@ const grafikkepuasan = proxy({
}
},
},
findUnique: {
data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true }
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
if (res.ok) {
const data = await res.json();
grafikkepuasan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch grafikkepuasan:", res.statusText);
grafikkepuasan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching grafikkepuasan:", error);
grafikkepuasan.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultForm},
loading: false,
async byId() {
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
this.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
await grafikkepuasan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik kepuasan");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik kepuasan berhasil dihapus"
);
await grafikkepuasan.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus grafik kepuasan"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
}
}
});
const stategrafikKepuasan = proxy({
grafikkepuasan,
});
export default stategrafikKepuasan;
export default grafikkepuasan;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,362 +6,307 @@ 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"),
const templateForm = z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
informasiJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
waktu: z.string().min(1, "Waktu minimal 1 karakter"),
lokasi: z.string().min(1, "Lokasi minimal 1 karakter"),
}),
deskripsiJadwalKegiatan: z.object({
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
}),
layananJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
syaratKetentuanJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
dokumenJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
pendaftaranJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
namaOrangtua: z.string().min(1, "Nama Orangtua minimal 1 karakter"),
nomor: z.string().min(1, "Nomor minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
catatan: z.string().min(1, "Catatan minimal 1 karakter"),
}),
});
type InformasiKegiatan = Prisma.InformasiJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
waktu: true;
lokasi: true;
};
}>;
const defaultForm = {
content: "",
informasiJadwalKegiatan: {
name: "",
tanggal: "",
waktu: "",
lokasi: "",
},
deskripsiJadwalKegiatan: {
deskripsi: "",
},
layananJadwalKegiatan: {
content: "",
},
syaratKetentuanJadwalKegiatan: {
content: "",
},
dokumenJadwalKegiatan: {
content: "",
},
pendaftaranJadwalKegiatan: {
name: "",
tanggal: "",
namaOrangtua: "",
nomor: "",
alamat: "",
catatan: "",
},
};
const informasiKegiatan = proxy({
const jadwalkegiatanState = proxy({
create: {
form: {} as InformasiKegiatan,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateInformasiKegiatan.safeParse(
informasiKegiatan.create.form
);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
informasiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"create"
].post(informasiKegiatan.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
].create.post(payload);
if (res.status === 200) {
informasiKegiatan.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan jadwal kegiatan");
this.resetForm();
await jadwalkegiatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
informasiKegiatan.create.loading = false;
this.loading = false;
}
},
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.InformasiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: 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);
}
async load() {
try {
deskripsiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"create"
].post(deskripsiKegiatan.create.form);
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
]["find-many"].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.load();
return toast.success("success create");
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data jadwal kegiatan");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
deskripsiKegiatan.create.loading = false;
this.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,
findUnique: {
data: null as Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
syaratketentuanjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
};
}> | null,
loading: false,
async create() {
const cek = templateLayananTersedia.safeParse(
layanantersedia.create.form
);
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (res.ok) {
const data = await res.json();
jadwalkegiatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data jadwal kegiatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data jadwal kegiatan");
return;
}
const result = await res.json();
const data = result.data;
jadwalkegiatanState.edit.id = data.id;
jadwalkegiatanState.edit.form = {
content: data.content,
informasiJadwalKegiatan: {
name: data.informasijadwalkegiatan.name,
tanggal: data.informasijadwalkegiatan.tanggal,
waktu: data.informasijadwalkegiatan.waktu,
lokasi: data.informasijadwalkegiatan.lokasi,
},
layananJadwalKegiatan: {
content: data.layananjadwalkegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: data.deskripsijadwalkegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: data.syaratketentuanjadwalkegiatan.content,
},
dokumenJadwalKegiatan: {
content: data.dokumenjadwalkegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: data.pendaftaranjadwalkegiatan.name,
tanggal: data.pendaftaranjadwalkegiatan.tanggal,
namaOrangtua: data.pendaftaranjadwalkegiatan.namaOrangtua,
nomor: data.pendaftaranjadwalkegiatan.nomor,
alamat: data.pendaftaranjadwalkegiatan.alamat,
catatan: data.pendaftaranjadwalkegiatan.catatan,
},
};
},
async submit() {
const cek = templateForm.safeParse(jadwalkegiatanState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
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 ?? [];
}
},
},
});
/* ======================================================================= */
jadwalkegiatanState.edit.loading = true;
const payload = {
content: jadwalkegiatanState.edit.form.content,
informasiJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
lokasi: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
},
layananJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan.content,
},
dokumenJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
namaOrangtua: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.namaOrangtua,
nomor: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
alamat: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
catatan: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
},
};
/* 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")
const res = await fetch(
`/api/kesehatan/jadwal-kegiatan/${jadwalkegiatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
return toast.error("failed create")
} catch (error) {
console.log((error as Error).message)
);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update jadwal kegiatan");
await jadwalkegiatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
pendaftaranjadwal.create.loading = false;
jadwalkegiatanState.edit.loading = false;
}
},
resetForm() {
jadwalkegiatanState.edit.id = "";
jadwalkegiatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string){
try {
jadwalkegiatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Jadwal kegiatan berhasil dihapus");
await jadwalkegiatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
jadwalkegiatanState.delete.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;
export default jadwalkegiatanState;

View File

@@ -6,9 +6,9 @@ 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"),
kematianKasar: z.string().min(1, "Kematian kasar harus diisi"),
kelahiranKasar: z.string().min(1, "Kelahiran kasar harus diisi"),
kematianBayi: z.string().min(1, "Kematian bayi harus diisi"),
});
type Persentase = Prisma.DataKematian_KelahiranGetPayload<{
@@ -34,51 +34,157 @@ const persentasekelahiran = proxy({
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);
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
toast.error(err);
return null;
}
try {
persentasekelahiran.create.loading = true;
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"create"
].post(persentasekelahiran.create.form);
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");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
persentasekelahiran.create.form = { ...defaultForm };
persentasekelahiran.findMany.load();
return id;
}
}
return toast.error("failed create");
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
persentasekelahiran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DataKematian_KelahiranGetPayload<{ omit: { isActive: true } }>[]
| null,
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}>[] | null,
async load() {
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"find-many"
].get();
const res = await ApiFetch.api.kesehatan.persentasekelahiran["find-many"].get();
if (res.status === 200) {
persentasekelahiran.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/persentasekelahiran/${id}`);
if (res.ok) {
const data = await res.json();
persentasekelahiran.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch persentasekelahiran:", res.statusText);
persentasekelahiran.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching persentasekelahiran:", error);
persentasekelahiran.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
tahun: this.form.tahun,
kematianKasar: this.form.kematianKasar,
kelahiranKasar: this.form.kelahiranKasar,
kematianBayi: this.form.kematianBayi,
};
const cek = templatePersentase.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/persentasekelahiran/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await persentasekelahiran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data persentase kelahiran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
persentasekelahiran.delete.loading = true;
const response = await fetch(`/api/kesehatan/persentasekelahiran/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Persentase kelahiran berhasil dihapus");
await persentasekelahiran.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus persentase kelahiran");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus persentase kelahiran");
} finally {
persentasekelahiran.delete.loading = false;
}
},
},
});
const statePersentase = proxy({
persentasekelahiran,
});
export default statePersentase;
export default persentasekelahiran;

View File

@@ -0,0 +1,214 @@
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, "Judul minimal 3 karakter"),
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
deskripsiLengkap: z.string().min(3, "Deskripsi lengkap minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsiSingkat: "",
deskripsiLengkap: "",
imageId: "",
};
const infoWabahPenyakit = proxy({
findMany: {
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"find-many"
].get();
if (res.status === 200) {
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(infoWabahPenyakit.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoWabahPenyakit.create.loading = true;
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"create"
].post(infoWabahPenyakit.create.form);
if (res.status === 200) {
infoWabahPenyakit.findMany.load();
return toast.success("Info wabah penyakit berhasil disimpan!");
}
return toast.error("Gagal menyimpan info wabah penyakit");
} catch (error) {
console.log((error as Error).message);
} finally {
infoWabahPenyakit.create.loading = false;
}
},
resetForm() {
infoWabahPenyakit.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/infowabahpenyakit/${id}`);
if (res.ok) {
const data = await res.json();
infoWabahPenyakit.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch info wabah penyakit:", res.statusText);
infoWabahPenyakit.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching info wabah penyakit:", error);
infoWabahPenyakit.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
infoWabahPenyakit.delete.loading = true;
const response = await fetch(`/api/kesehatan/infowabahpenyakit/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Info wabah penyakit berhasil dihapus");
await infoWabahPenyakit.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus info wabah penyakit");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus info wabah penyakit");
} finally {
infoWabahPenyakit.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/infowabahpenyakit/${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,
deskripsiSingkat: data.deskripsiSingkat,
deskripsiLengkap: data.deskripsiLengkap,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching info wabah penyakit:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(infoWabahPenyakit.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoWabahPenyakit.edit.loading = true;
const response = await fetch(`/api/kesehatan/infowabahpenyakit/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsiLengkap: this.form.deskripsiLengkap,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Info wabah penyakit berhasil diupdate");
await infoWabahPenyakit.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update info wabah penyakit");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate info wabah penyakit");
return false;
} finally {
infoWabahPenyakit.edit.loading = false;
}
},
reset() {
infoWabahPenyakit.edit.id = "";
infoWabahPenyakit.edit.form = { ...defaultForm };
},
},
});
export default infoWabahPenyakit;

View File

@@ -0,0 +1,208 @@
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, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const kontakDarurat = proxy({
findMany: {
data: [] as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get();
if (res.status === 200) {
kontakDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"create"
].post(kontakDarurat.create.form);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.delete.loading = false;
}
}
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
});
export default kontakDarurat

View File

@@ -0,0 +1,208 @@
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, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const penangananDarurat = proxy({
findMany: {
data: [] as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"find-many"
].get();
if (res.status === 200) {
penangananDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(penangananDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
penangananDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"create"
].post(penangananDarurat.create.form);
if (res.status === 200) {
penangananDarurat.findMany.load();
return toast.success("Penanganan Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan penanganan darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
penangananDarurat.create.loading = false;
}
},
resetForm() {
penangananDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/penanganandarurat/${id}`);
if (res.ok) {
const data = await res.json();
penangananDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
penangananDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
penangananDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
penangananDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/penanganandarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Penanganan darurat berhasil dihapus");
await penangananDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus penanganan darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus penanganan darurat");
} finally {
penangananDarurat.delete.loading = false;
}
}
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/penanganandarurat/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching penanganan darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(penangananDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
penangananDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/penanganandarurat/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Penanganan darurat berhasil diupdate");
await penangananDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update penanganan darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate penanganan darurat");
return false;
} finally {
penangananDarurat.edit.loading = false;
}
},
reset() {
penangananDarurat.edit.id = "";
penangananDarurat.edit.form = { ...defaultForm };
},
},
});
export default penangananDarurat

View File

@@ -0,0 +1,217 @@
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, { message: "Name is required" }),
nomor: z.string().min(1, { message: "Nomor is required" }),
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
nomor: "",
deskripsi: "",
imageId: "",
};
const posyandustate = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(posyandustate.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
posyandustate.create.loading = true;
const res = await ApiFetch.api.kesehatan.posyandu["create"].post(posyandustate.create.form);
if (res.status === 200) {
posyandustate.findMany.load();
return toast.success("Posyandu berhasil disimpan!");
}
return toast.error("Gagal menyimpan posyandu");
} catch (error) {
console.log((error as Error).message);
} finally {
posyandustate.create.loading = false;
}
},
resetForm(){
posyandustate.create.form = { ...defaultForm };
}
},
findMany: {
data: null as
| Prisma.PosyanduGetPayload<{
include: {
image: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get();
if (res.status === 200) {
posyandustate.findMany.data = res.data?.data ?? [];
}
}
},
findUnique: {
data: null as
| Prisma.PosyanduGetPayload<{
include: {
image: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/posyandu/${id}`);
if (res.ok) {
const data = await res.json();
posyandustate.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch posyandu:", res.statusText);
posyandustate.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching posyandu:", error);
posyandustate.findUnique.data = null;
}
}
},
delete: {
loading: false,
async byId(id: string) {
try {
posyandustate.delete.loading = true;
const response = await fetch(`/api/kesehatan/posyandu/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Posyandu berhasil dihapus");
await posyandustate.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus posyandu");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus posyandu");
} finally {
posyandustate.delete.loading = false;
}
}
},
edit: {
id: "",
form: {...defaultForm},
loading: false,
async load(id: string) {
if(!id){
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/posyandu/${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,
nomor: data.nomor,
deskripsi: data.deskripsi,
imageId: data.imageId || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching posyandu:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(posyandustate.edit.form);
if(!cek.success){
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
posyandustate.edit.loading = true;
const response = await fetch(`/api/kesehatan/posyandu/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: this.form.name,
nomor: this.form.nomor,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if(!response.ok){
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if(result.success){
toast.success(result.message || "Posyandu berhasil diperbarui");
await posyandustate.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching posyandu:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return false;
} finally {
posyandustate.edit.loading = false;
}
},
reset() {
posyandustate.edit.id = "";
posyandustate.edit.form = {...defaultForm};
}
}
})
export default posyandustate;

View File

@@ -0,0 +1,214 @@
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, "Judul minimal 3 karakter"),
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsiSingkat: "",
deskripsi: "",
imageId: "",
};
const programKesehatan = proxy({
findMany: {
data: [] as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get();
if (res.status === 200) {
programKesehatan.findMany.data = res.data?.data ?? [];
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKesehatan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.programkesehatan[
"create"
].post(programKesehatan.create.form);
if (res.status === 200) {
programKesehatan.findMany.load();
return toast.success("Program Kesehatan berhasil disimpan!");
}
return toast.error("Gagal menyimpan program kesehatan");
} catch (error) {
console.log((error as Error).message);
} finally {
programKesehatan.create.loading = false;
}
},
resetForm() {
programKesehatan.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/programkesehatan/${id}`);
if (res.ok) {
const data = await res.json();
programKesehatan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch program kesehatan:", res.statusText);
programKesehatan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
programKesehatan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKesehatan.delete.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program kesehatan berhasil dihapus");
await programKesehatan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kesehatan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kesehatan");
} finally {
programKesehatan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/programkesehatan/${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,
deskripsiSingkat: data.deskripsiSingkat,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.edit.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Program kesehatan berhasil diupdate");
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
return false;
} finally {
programKesehatan.edit.loading = false;
}
},
reset() {
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
},
},
});
export default programKesehatan;

View File

@@ -0,0 +1,298 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// Validasi form
const templateForm = z.object({
name: z.string().min(1),
alamat: z.string().min(1),
imageId: z.string().min(1),
jam: z.object({
workDays: z.string().min(1),
weekDays: z.string().min(1),
holiday: z.string().min(1),
}),
kontak: z.object({
kontakPuskesmas: z.string().min(1),
email: z.string().min(1),
facebook: z.string().min(1),
kontakUGD: z.string().min(1),
}),
});
// Default form
const defaultForm = {
name: "",
alamat: "",
imageId: "",
jam: {
workDays: "",
weekDays: "",
holiday: "",
},
kontak: {
kontakPuskesmas: "",
email: "",
facebook: "",
kontakUGD: "",
},
image: undefined,
};
const puskesmasState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async submit() {
const cek = templateForm.safeParse(puskesmasState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
return toast.error(err);
}
try {
puskesmasState.create.loading = true;
console.log('Form data:', puskesmasState.create.form);
interface ErrorResponse {
message?: string;
error?: string;
errors?: Record<string, string[]>;
}
const payload = {
name: puskesmasState.create.form.name,
alamat: puskesmasState.create.form.alamat,
imageId: puskesmasState.create.form.imageId,
jam: {
workDays: puskesmasState.create.form.jam.workDays,
weekDays: puskesmasState.create.form.jam.weekDays,
holiday: puskesmasState.create.form.jam.holiday,
},
kontak: {
kontakPuskesmas: puskesmasState.create.form.kontak.kontakPuskesmas,
email: puskesmasState.create.form.kontak.email,
facebook: puskesmasState.create.form.kontak.facebook,
kontakUGD: puskesmasState.create.form.kontak.kontakUGD,
},
};
console.log('Sending payload:', JSON.stringify(payload, null, 2));
try {
const res = await ApiFetch.api.kesehatan.puskesmas.create.post(payload);
console.log('API Response:', res);
if (res.status === 200) {
await puskesmasState.findMany.load();
toast.success("Berhasil menambahkan puskesmas");
return res;
} else {
console.error('API Error Response:', {
status: res.status,
data: res.data
});
const errorData = res.data as ErrorResponse;
let errorMessage = 'Gagal menambahkan puskesmas';
if (errorData?.message) {
errorMessage = errorData.message;
} else if (errorData?.error) {
errorMessage = errorData.error;
} else if (errorData?.errors) {
errorMessage = Object.entries(errorData.errors)
.map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
.join('; ');
}
console.error('Extracted error message:', errorMessage);
toast.error(errorMessage);
throw new Error(errorMessage);
}
} catch (error) {
console.error('Error in API call:', {
error,
errorString: String(error),
jsonError: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : 'Not an Error instance'
});
throw error;
}
} catch (error) {
console.error("Error in puskesmas submit:", {
error,
errorString: String(error),
errorType: typeof error,
isErrorInstance: error instanceof Error,
errorDetails: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack,
cause: error.cause
} : null
});
let errorMessage = "Terjadi kesalahan saat menambahkan puskesmas";
if (error instanceof Error) {
errorMessage = error.message || errorMessage;
} else if (error && typeof error === 'object' && 'message' in error) {
errorMessage = String((error as { message: unknown }).message);
} else if (typeof error === 'string') {
errorMessage = error;
}
console.error('Displaying error to user:', errorMessage);
toast.error(errorMessage);
throw error;
} finally {
puskesmasState.create.loading = false;
}
},
resetForm() {
puskesmasState.create.form = { ...defaultForm };
}
},
findMany: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}>[] | null,
async load() {
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
if (res.status === 200) {
puskesmasState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}> | null,
async load(id: string) {
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
if (res.ok) {
const data = await res.json();
puskesmasState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data puskesmas");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
if (!res.ok) {
toast.error("Gagal memuat data");
return;
}
const result = await res.json();
const data = result.data;
puskesmasState.edit.id = data.id;
puskesmasState.edit.form = {
name: data.name,
alamat: data.alamat,
imageId: data.imageId,
jam: {
workDays: data.jam.workDays,
weekDays: data.jam.weekDays,
holiday: data.jam.holiday,
},
kontak: {
kontakPuskesmas: data.kontak.kontakPuskesmas,
email: data.kontak.email,
facebook: data.kontak.facebook,
kontakUGD: data.kontak.kontakUGD,
},
image: data.image,
};
},
async submit() {
const cek = templateForm.safeParse(puskesmasState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
toast.error(err);
return false;
}
try {
puskesmasState.edit.loading = true;
const payload = {
name: puskesmasState.edit.form.name,
alamat: puskesmasState.edit.form.alamat,
imageId: puskesmasState.edit.form.imageId,
jam: { ...puskesmasState.edit.form.jam },
kontak: { ...puskesmasState.edit.form.kontak }
};
const res = await fetch(`/api/kesehatan/puskesmas/${puskesmasState.edit.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update puskesmas");
await puskesmasState.findMany.load();
return true;
} catch (err) {
toast.error(err instanceof Error ? err.message : "Terjadi kesalahan saat update");
return false;
} finally {
puskesmasState.edit.loading = false;
}
},
reset() {
puskesmasState.edit.id = "";
puskesmasState.edit.form = { ...defaultForm };
}
},
delete: {
loading: false,
async byId(id: string) {
try {
puskesmasState.delete.loading = true;
const res = await fetch(`/api/kesehatan/puskesmas/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Puskesmas berhasil dihapus");
await puskesmasState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
puskesmasState.delete.loading = false;
}
}
}
});
export default puskesmasState;

View File

@@ -5,9 +5,9 @@ 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"),
});
laki: z.string().min(1, "Data laki-laki harus diisi"),
perempuan: z.string().min(1, "Data perempuan harus diisi"),
});
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
select: {

View File

@@ -5,10 +5,10 @@ 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"),
remaja: z.string().min(1, "Data remaja harus diisi"),
dewasa: z.string().min(1, "Data dewasa harus diisi"),
orangtua: z.string().min(1, "Data orangtua harus diisi"),
lansia: z.string().min(1, "Data lansia harus diisi"),
});
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{

View File

@@ -6,7 +6,7 @@ import { z } from "zod";
const templateGrafikHasilKepuasanMasyarakat = z.object({
label: z.string().min(2, "Label harus diisi"),
kepuasan: z.string().min(2, "Kepuasan harus diisi"),
kepuasan: z.string().min(1, "Kepuasan harus diisi"),
});
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{

View File

@@ -125,16 +125,6 @@ function EditBerita() {
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 })}
@@ -174,6 +164,16 @@ function EditBerita() {
/>
</Box>
<SelectCategory
value={formData.kategoriBeritaId}
onChange={(val) => {
setFormData({
...formData,
kategoriBeritaId: val?.id || ''
});
}}
/>
<Button onClick={handleSubmit}>Edit Berita</Button>
</Stack>
</Paper>

View File

@@ -63,9 +63,9 @@ export default function CreateBerita() {
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
<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"}>
@@ -79,8 +79,9 @@ export default function CreateBerita() {
placeholder="masukkan judul"
/>
<SelectCategory
value={beritaState.berita.create.form.kategoriBeritaId}
onChange={(val) => {
beritaState.berita.create.form.kategoriBeritaId = val.id;
beritaState.berita.create.form.kategoriBeritaId = val?.id || "";
}}
/>
<TextInput
@@ -114,10 +115,10 @@ export default function CreateBerita() {
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<CreateEditor
value={beritaState.berita.create.form.content}
onChange={(htmlContent) => {
beritaState.berita.create.form.content = htmlContent;
}}
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>
@@ -126,26 +127,37 @@ export default function CreateBerita() {
</Box>
);
function SelectCategory({
onChange,
}: {
interface SelectCategoryProps {
onChange: (value: Prisma.KategoriBeritaGetPayload<{
select: {
name: true;
id: true;
};
}>) => void;
}) {
}> | null) => void;
value?: string | null;
defaultValue?: string | null;
}
function SelectCategory({
onChange,
value,
defaultValue,
}: SelectCategoryProps) {
const categoryState = useProxy(stateDashboardBerita.category);
useShallowEffect(() => {
categoryState.findMany.load();
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>}
@@ -154,13 +166,19 @@ export default function CreateBerita() {
label: item.name,
value: item.id,
}))}
onChange={(val) => {
const selected = categoryState.findMany.data?.find((item) => item.id === val);
if (selected) {
onChange(selected);
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"
/>
);

View File

@@ -0,0 +1,31 @@
export function convertYoutubeUrlToEmbed(url: string) {
const videoIdMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
return videoIdMatch ? `https://www.youtube.com/embed/${videoIdMatch[1]}` : null;
}
// (url: string): string | null {
// const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
// const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
// const matchWatch = url.match(watchRegex);
// const matchShort = url.match(shortRegex);
// if (matchWatch) {
// return `https://www.youtube.com/embed/${matchWatch[1]}`;
// }
// if (matchShort) {
// return `https://www.youtube.com/embed/${matchShort[1]}`;
// }
// return null;
// }

View File

@@ -0,0 +1,33 @@
// components/YoutubeEmbed.tsx
"use client";
import { Box, Text } from "@mantine/core";
type YoutubeEmbedProps = {
url?: string;
showRawUrl?: boolean; // opsional, buat nampilin URL mentahnya
};
export default function YoutubeEmbed({ url, showRawUrl = false }: YoutubeEmbedProps) {
if (!url || !url.includes("embed")) {
return <Text c="red">Link embed Youtube tidak valid</Text>;
}
return (
<Box>
<Box
component="iframe"
src={url}
width="100%"
height={300}
allowFullScreen
style={{ borderRadius: 8 }}
/>
{showRawUrl && (
<Text fz="sm" c="dimmed" mt={5}>
{url}
</Text>
)}
</Box>
);
}

View File

@@ -3,25 +3,24 @@
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Flex, Group, Image, Modal, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
function EditVideo() {
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false);
const router = useRouter();
const videoState = useProxy(stateGallery.video)
const params = useParams()
const [formData, setFormData] = useState({
name: videoState.findUnique.data?.name || '',
deskripsi: videoState.findUnique.data?.deskripsi || '',
linkVideo: videoState.findUnique.data?.linkVideo || '',
})
name: '',
deskripsi: '',
linkVideo: '',
});
useEffect(() => {
const loadVideo = async () => {
@@ -31,7 +30,7 @@ function EditVideo() {
const data = await videoState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
name: data.name || '',
deskripsi: data.deskripsi || '',
linkVideo: data.linkVideo || '',
});
@@ -44,7 +43,15 @@ function EditVideo() {
loadVideo();
}, [params?.id]);
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
const handleSubmit = async () => {
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
if (!converted) {
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
return;
}
try {
videoState.update.form = {
...videoState.update.form,
@@ -72,83 +79,54 @@ function EditVideo() {
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Edit Video</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
placeholder='Masukkan judul video'
value={formData.name}
onChange={(val) => {
setFormData({
...formData,
name: val.target.value,
})
setFormData({ ...formData, name: val.target.value });
}}
/>
<Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Link Video Youtube <Text span c="red" inherit>*</Text></Text>}
placeholder='Masukkan link video youtube'
label="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
value={formData.linkVideo}
onChange={(val) => {
setFormData({
...formData,
linkVideo: val.target.value,
})
onChange={(e) => {
setFormData({ ...formData, linkVideo: e.currentTarget.value });
}}
required
/>
<Flex pt={5} align={"center"} gap={"xs"}>
<Text c={"dimmed"} fw={"bold"} fz={"xs"}>Cara mendapatkan link video youtube</Text>
<ActionIcon size={"xs"} radius={"xl"} color='black' variant='filled' onClick={() => setModalHapus(true)}>
<Text fw={"bold"} fz={"xs"} c="white">?</Text>
</ActionIcon>
</Flex>
{embedLink && (
<iframe
className="rounded"
width="100%"
height="200"
src={embedLink}
title="Preview Video"
allowFullScreen
></iframe>
)}
</Box>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
setFormData({
...formData,
deskripsi: val,
})
setFormData({ ...formData, deskripsi: val });
}}
/>
</Box>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<Modal
opened={modalHapus}
onClose={() => setModalHapus(false)}
title={<Text fw={"bold"} fz={"xl"}>Cara mendapatkan link video youtube</Text>}
>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 1</Text>
<Text fz={"sm"}>Buka video youtube yang ingin Anda bagikan lalu klik icon titik tiga</Text>
<Image src="/video.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 2</Text>
<Text fz={"sm"}>Klik bagikan</Text>
<Image src="/Share.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 3</Text>
<Text fz={"sm"}>Klik dibagian sematkan</Text>
<Image src="/bagikanPostingan.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 4</Text>
<Text fz={"sm"}>Lalu copy pada bagaian srcnya aja</Text>
<Image src="/sematkan.png" w={300} alt="" />
</Box>
</Stack>
</Modal>
</Box>
);
}

View File

@@ -9,6 +9,7 @@ import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function DetailVideo() {
const videoState = useProxy(stateGallery.video)
const [modalHapus, setModalHapus] = useState(false);
@@ -55,9 +56,17 @@ function DetailVideo() {
<Text fz={"lg"}>{videoState.findUnique.data?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Link Video</Text>
<Text fz={"lg"}>{videoState.findUnique.data?.linkVideo}</Text>
<Text fw={"bold"} fz={"lg"}>Video</Text>
<Box component="iframe"
src={convertToEmbedUrl(videoState.findUnique.data?.linkVideo)}
width="100%"
height={300}
allowFullScreen
style={{ borderRadius: 8 }}
/>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Tanggal Video</Text>
<Text fz={"lg"}>{new Date(videoState.findUnique.data?.createdAt).toDateString()}</Text>
@@ -105,7 +114,20 @@ function DetailVideo() {
text='Apakah anda yakin ingin menghapus berita ini?'
/>
</Box>
);
function convertToEmbedUrl(youtubeUrl: string): string {
try {
const url = new URL(youtubeUrl);
const videoId = url.searchParams.get("v");
if (!videoId) return youtubeUrl;
return `https://www.youtube.com/embed/${videoId}`;
} catch (err) {
console.error("Error converting YouTube URL to embed:", err);
return youtubeUrl;
}
}
}
export default DetailVideo;

View File

@@ -2,18 +2,21 @@
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Flex, Group, Image, Modal, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertYoutubeUrlToEmbed } from '../../lib/youtube-utils';
function CreateVideo() {
const videoState = useProxy(stateGallery.video)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false);
const [link, setLink] = useState("");
const embedLink = convertYoutubeUrlToEmbed(link);
const resetForm = () => {
videoState.create.form = {
@@ -22,12 +25,18 @@ function CreateVideo() {
linkVideo: "",
};
};
const handleSubmit = async () => {
if (!embedLink) {
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
return;
}
videoState.create.form.linkVideo = embedLink; // pastikan diset di sini juga (jaga-jaga)
await videoState.create.create();
resetForm();
router.push("/admin/desa/gallery/video")
router.push("/admin/desa/gallery/video");
};
return (
<Box>
@@ -49,20 +58,26 @@ function CreateVideo() {
}}
/>
<Box>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Link Video Youtube <Text span c="red" inherit>*</Text></Text>}
placeholder='Masukkan link video youtube'
value={videoState.create.form.linkVideo}
onChange={(val) => {
videoState.create.form.linkVideo = val.target.value;
}}
/>
<Flex pt={5} align={"center"} gap={"xs"}>
<Text c={"dimmed"} fw={"bold"} fz={"xs"}>Cara mendapatkan link video youtube</Text>
<ActionIcon size={"xs"} radius={"xl"} color='black' variant='filled' onClick={() => setModalHapus(true)}>
<Text fw={"bold"} fz={"xs"} c="white">?</Text>
</ActionIcon>
</Flex>
<Stack gap={"xs"}>
<TextInput
label="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
value={link}
onChange={(e) => {
setLink(e.currentTarget.value);
}}
required
/>
{embedLink && (
<iframe
style={{ borderRadius: 10, width: "100%", height: 400 }}
src={embedLink}
title="Preview Video"
allowFullScreen
></iframe>
)}
</Stack>
</Box>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
@@ -78,36 +93,6 @@ function CreateVideo() {
</Group>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<Modal
opened={modalHapus}
onClose={() => setModalHapus(false)}
title={<Text fw={"bold"} fz={"xl"}>Cara mendapatkan link video youtube</Text>}
>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 1</Text>
<Text fz={"sm"}>Buka video youtube yang ingin Anda bagikan lalu klik icon titik tiga</Text>
<Image src="/video.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 2</Text>
<Text fz={"sm"}>Klik bagikan</Text>
<Image src="/Share.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 3</Text>
<Text fz={"sm"}>Klik dibagian sematkan</Text>
<Image src="/bagikanPostingan.png" w={300} alt="" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Langkah 4</Text>
<Text fz={"sm"}>Lalu copy pada bagaian srcnya aja</Text>
<Image src="/sematkan.png" w={300} alt="" />
</Box>
</Stack>
</Modal>
</Box>
);
}

View File

@@ -0,0 +1,103 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPelayananPendudukNonPermanent() {
const router = useRouter();
const params = useParams()
const statePendudukNonPermanent = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
const [formData, setFormData] = useState({
name: statePendudukNonPermanent.findById.data?.name || '',
deskripsi: statePendudukNonPermanent.findById.data?.deskripsi || '',
})
useEffect(() => {
const loadPelayananPerizinan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await statePendudukNonPermanent.update.load(id);
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
});
}
} catch (error) {
console.error("Error loading pelayanan perizinan berusaha:", error);
toast.error("Gagal memuat data pelayanan perizinan berusaha");
}
};
loadPelayananPerizinan();
}, [params?.id]);
const handleSubmit = async () => {
if (statePendudukNonPermanent.findById.data) {
statePendudukNonPermanent.findById.data.name = formData.name;
statePendudukNonPermanent.findById.data.deskripsi = formData.deskripsi;
statePendudukNonPermanent.update.update(statePendudukNonPermanent.findById.data)
}
router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent')
}
return (
<Box>
<Stack gap={'xs'}>
<Box>
<Button
variant={'subtle'}
onClick={() => router.back()}
>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Title order={3}>Edit Pelayanan Penduduk Non Permanent</Title>
<Text fw={"bold"}>Judul</Text>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({
...formData,
name: val.target.value,
})
}}
/>
<Text fw={"bold"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
setFormData({
...formData,
deskripsi: val,
})
}}
/>
<Group>
<Button
bg={colors['blue-button']}
onClick={handleSubmit}
loading={statePendudukNonPermanent.update.loading}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
</Stack>
</Box>
);
}
export default EditPelayananPendudukNonPermanent;

View File

@@ -1,40 +1,48 @@
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function SuratKeterangan() {
const router = useRouter()
const pelayananPendudukNonPermanen = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
useShallowEffect(() => {
pelayananPendudukNonPermanen.findById.load('1')
}, [])
if (!pelayananPendudukNonPermanen.findById.data) {
return (
<Stack>
<Skeleton radius={10} h={800} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Pelayanan Penduduk Non-Permanent'
href='/admin/desa/layanan/pelayanan_penduduk_non_permanent/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Pelayanan Penduduk Non-Permanent</TableTd>
<TableTd>Deskripsi Pelayanan Penduduk Non-Permanent</TableTd>
<TableTd>
<Text>
<Button>
<IconDeviceImac size={20} />
<Paper bg={colors['BG-trans']} p={'md'}>
<Box py={15}>
<Stack gap={"xs"}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"h4"} fw={"bold"}>Preview Pelayanan Perizinan Berusaha</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent/edit')}>
<IconEdit size={16} />
</Button>
</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</GridCol>
</Grid>
</Stack>
</Box>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>{pelayananPendudukNonPermanen.findById.data.name}</Text>
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }} dangerouslySetInnerHTML={{ __html: pelayananPendudukNonPermanen.findById.data.deskripsi }} />
</Paper>
</Paper>
</Box>
);

View File

@@ -0,0 +1,116 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPelayananPerizinanBerusaha() {
const router = useRouter();
const params = useParams()
const statePerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
const [formData, setFormData] = useState({
name: statePerizinanBerusaha.findById.data?.name || '',
deskripsi: statePerizinanBerusaha.findById.data?.deskripsi || '',
link: statePerizinanBerusaha.findById.data?.link || '',
})
useEffect(() => {
const loadPelayananPerizinan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await statePerizinanBerusaha.update.load(id);
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
link: data.link || '',
});
}
} catch (error) {
console.error("Error loading pelayanan perizinan berusaha:", error);
toast.error("Gagal memuat data pelayanan perizinan berusaha");
}
};
loadPelayananPerizinan();
}, [params?.id]);
const handleSubmit = async () => {
if (statePerizinanBerusaha.findById.data) {
statePerizinanBerusaha.findById.data.name = formData.name;
statePerizinanBerusaha.findById.data.deskripsi = formData.deskripsi;
statePerizinanBerusaha.findById.data.link = formData.link;
statePerizinanBerusaha.update.update(statePerizinanBerusaha.findById.data)
}
router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha')
}
return (
<Box>
<Stack gap={'xs'}>
<Box>
<Button
variant={'subtle'}
onClick={() => router.back()}
>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Title order={3}>Edit Pelayanan Perizinan Berusaha</Title>
<Text fw={"bold"}>Judul</Text>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({
...formData,
name: val.target.value,
})
}}
/>
<Text fw={"bold"}>Link</Text>
<TextInput
value={formData.link}
onChange={(val) => {
setFormData({
...formData,
link: val.target.value,
})
}}
/>
<Text fw={"bold"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
setFormData({
...formData,
deskripsi: val,
})
}}
/>
<Group>
<Button
bg={colors['blue-button']}
onClick={handleSubmit}
loading={statePerizinanBerusaha.update.loading}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
</Stack>
</Box>
);
}
export default EditPelayananPerizinanBerusaha;

View File

@@ -1,40 +1,94 @@
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { Box, Button, Grid, GridCol, Group, Paper, Skeleton, Stack, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useProxy } from 'valtio/utils';
import { useShallowEffect } from '@mantine/hooks';
function PerizinanBerusaha() {
const router = useRouter()
const pelayananPerizinanBerusaha = useProxy(stateLayananDesa.pelayananPerizinanBerusaha)
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
useShallowEffect(() => {
pelayananPerizinanBerusaha.findById.load('1')
}, [])
if(!pelayananPerizinanBerusaha.findById.data) {
return (
<Stack>
<Skeleton radius={10} h={800} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Pelayanan Perizinan Berusaha'
href='/admin/desa/layanan/pelayanan_perizinan_berusaha/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Pelayanan Perizinan Berusaha</TableTd>
<TableTd>Deskripsi Pelayanan Perizinan Berusaha</TableTd>
<TableTd>
<Text>
<Button>
<IconDeviceImac size={20} />
<Paper bg={colors['BG-trans']} p={'md'}>
<Box py={15}>
<Stack gap={"xs"}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"h4"} fw={"bold"}>Preview Pelayanan Perizinan Berusaha</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button bg={colors['blue-button']} onClick={() => router.push('/admin/desa/layanan/pelayanan_perizinan_berusaha/edit')}>
<IconEdit size={16} />
</Button>
</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</GridCol>
</Grid>
</Stack>
</Box>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>{pelayananPerizinanBerusaha.findById.data.name}</Text>
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }} dangerouslySetInnerHTML={{__html: pelayananPerizinanBerusaha.findById.data.deskripsi}} />
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }} >
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI ">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted >
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href={pelayananPerizinanBerusaha.findById.data.link}>{pelayananPerizinanBerusaha.findById.data.link}</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.</Text>
</Box>
</Paper>
</Paper>
</Box>
);

View File

@@ -0,0 +1,103 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPelayananTelunjukSakti() {
const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
const params = useParams()
const [formData, setFormData] = useState({
name: stateTelunjukDesa.edit.form.name,
deskripsi: stateTelunjukDesa.edit.form.deskripsi,
link: stateTelunjukDesa.edit.form.link,
})
useEffect(() => {
const loadPelayananTelunjukSakti = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await stateTelunjukDesa.edit.load(id);
if (data) {
setFormData({
name: data.name,
deskripsi: data.deskripsi,
link: data.link,
});
}
} catch (error) {
console.error("Error loading pelayanan telunjuk sakti:", error);
toast.error("Gagal memuat data pelayanan telunjuk sakti");
}
};
loadPelayananTelunjukSakti();
}, [params?.id]);
const handleSubmit = async () => {
try {
stateTelunjukDesa.edit.form = {
...stateTelunjukDesa.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
link: formData.link,
}
await stateTelunjukDesa.edit.update()
toast.success("Pelayanan telunjuk sakti berhasil diperbarui!")
router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa")
} catch (error) {
console.error("Error updating pelayanan telunjuk sakti:", error);
toast.error("Terjadi kesalahan saat memperbarui pelayanan telunjuk sakti");
}
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit Surat Keterangan</Title>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({ ...formData, name: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
placeholder="masukkan nama surat keterangan"
/>
<TextInput
value={formData.link}
onChange={(val) => {
setFormData({ ...formData, link: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData({ ...formData, deskripsi: htmlContent });
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditPelayananTelunjukSakti;

View File

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

View File

@@ -0,0 +1,70 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreatePelayananTelunjukDesa() {
const stateTelunjukDesa = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
const resetForm = () => {
stateTelunjukDesa.create.form = {
name: "",
deskripsi: "",
link: "",
}
}
const handleSubmit = async () => {
await stateTelunjukDesa.create.create()
resetForm()
router.push("/admin/desa/layanan/pelayanan_telunjuk_sakti_desa")
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Create Pelayanan Telunjuk Sakti Desa</Title>
<TextInput
value={stateTelunjukDesa.create.form.name}
onChange={(val) => {
stateTelunjukDesa.create.form.name = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Nama Pelayanan Telunjuk Sakti Desa</Text>}
placeholder="masukkan nama pelayanan telunjuk sakti desa"
/>
<TextInput
value={stateTelunjukDesa.create.form.link}
onChange={(val) => {
stateTelunjukDesa.create.form.link = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<CreateEditor
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(htmlContent) => {
stateTelunjukDesa.create.form.deskripsi = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePelayananTelunjukDesa;

View File

@@ -1,9 +1,29 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
function PelayananTelunjukSakti() {
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
useShallowEffect(() => {
telunjukSaktiState.findMany.load()
}, [])
if (!telunjukSaktiState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
function SuratKeterangan() {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -22,17 +42,19 @@ function SuratKeterangan() {
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Pelayanan Telunjuk Sakti Desa</TableTd>
<TableTd>Deskripsi Pelayanan Telunjuk Sakti Desa</TableTd>
{telunjukSaktiState.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd><Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /></TableTd>
<TableTd>
<Text>
<Button>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
@@ -40,4 +62,4 @@ function SuratKeterangan() {
);
}
export default SuratKeterangan;
export default PelayananTelunjukSakti;

View File

@@ -0,0 +1,147 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPenghargaan() {
const statePenghargaan = useProxy(penghargaanState)
const router = useRouter()
const params = useParams()
const [previewImage, setPreviewImage] = useState<string | null>(null)
const [file, setFile] = useState<File | null>(null)
const [formData, setFormData] = useState({
name: statePenghargaan.findUnique.data?.name || '',
juara: statePenghargaan.findUnique.data?.juara || '',
deskripsi: statePenghargaan.findUnique.data?.deskripsi || '',
imageId: statePenghargaan.findUnique.data?.imageId || '',
})
useEffect(() => {
const loadPenghargaan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await statePenghargaan.edit.load(id);
if (data) {
setFormData({
name: data.name || '',
juara: data.juara || '',
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
});
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
}
} catch (error) {
console.error("Error loading penghargaan:", error);
toast.error("Gagal memuat data penghargaan");
}
};
loadPenghargaan();
}, [params?.id]);
const handleSubmit = async () => {
try {
statePenghargaan.edit.form = {
...statePenghargaan.edit.form,
name: formData.name,
juara: formData.juara,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
}
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
statePenghargaan.edit.form.imageId = uploaded.id;
}
await statePenghargaan.edit.update();
toast.success("Penghargaan berhasil diperbarui!");
router.push("/admin/desa/penghargaan");
} catch (error) {
console.error("Error updating penghargaan:", error);
toast.error("Terjadi kesalahan saat memperbarui penghargaan");
}
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit Penghargaan</Title>
<TextInput
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={formData.juara}
onChange={(e) => setFormData({ ...formData, juara: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
placeholder="masukkan juara"
/>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
statePenghargaan.edit.form.deskripsi = htmlContent;
}}
/>
</Box>
<Button onClick={handleSubmit}>Edit Penghargaan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditPenghargaan;

View File

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

View File

@@ -0,0 +1,114 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import penghargaanState from '../../../_state/desa/penghargaan';
import ApiFetch from '@/lib/api-fetch';
import CreateEditor from '../../../_com/createEditor';
function CreatePenghargaan() {
const statePenghargaan = useProxy(penghargaanState)
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter()
const resetForm = () => {
statePenghargaan.create.form = {
name: "",
juara: "",
deskripsi: "",
imageId: "",
}
setPreviewImage(null)
setFile(null)
}
const handleSubmit = async () => {
if (!file) {
return toast.error("Silahkan pilih file gambar terlebih dahulu")
}
const res = await ApiFetch.api.fileStorage.create.post({
file: file,
name: file.name
})
const uploaded = res.data?.data
if (!uploaded?.id) {
return toast.error("Gagal upload gambar")
}
statePenghargaan.create.form.imageId = uploaded.id
await statePenghargaan.create.create()
resetForm()
router.push("/admin/desa/penghargaan")
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Create Penghargaan</Title>
<TextInput
value={statePenghargaan.create.form.name}
onChange={(val) => {
statePenghargaan.create.form.name = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Nama Penghargaan</Text>}
placeholder="masukkan nama penghargaan"
/>
<TextInput
value={statePenghargaan.create.form.juara}
onChange={(val) => {
statePenghargaan.create.form.juara = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
placeholder="masukkan juara"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
<CreateEditor
value={statePenghargaan.create.form.deskripsi}
onChange={(htmlContent) => {
statePenghargaan.create.form.deskripsi = htmlContent;
}}
/>
</Box>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePenghargaan;

View File

@@ -1,34 +1,69 @@
'use client'
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import React from 'react';
import Penghargaan from './ui/penghargaan/page';
import GambarPerhargaan from './ui/gambar_perhargaan/page';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../_com/jusulListTab';
function Page() {
function Penghargaan() {
const state = useProxy(penghargaanState)
const router = useRouter()
useShallowEffect(() => {
state.findMany.load()
}, [])
if (!state.findMany.data) {
return(
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Stack>
<Title order={3}>Penghargaan</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Penghargaan"}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="Penghargaan">
Penghargaan
</TabsTab>
<TabsTab value="Gambar Penghargaan">
Gambar Penghargaan
</TabsTab>
</TabsList>
<TabsPanel value="Penghargaan">
<Penghargaan/>
</TabsPanel>
<TabsPanel value="Gambar Penghargaan">
<GambarPerhargaan/>
</TabsPanel>
</Tabs>
</Stack>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Penghargaan'
href='/admin/desa/penghargaan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{state.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/penghargaan/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default Page;
export default Penghargaan;

View File

@@ -1,17 +0,0 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListGambarPenghargaan() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Gambar Penghargaan</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListGambarPenghargaan;

View File

@@ -1,50 +0,0 @@
import colors from '@/con/colors';
import { Box, Paper, SimpleGrid, Stack, Title, Text, Group, Button, TextInput, Center } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListGambarPenghargaan from './listPage';
import { IconUpload } from '@tabler/icons-react';
function GambarPerhargaan() {
return (
<Box py={10}>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Tambah Gambar Penghargaan</Title>
<TextInput
label="Judul Gambar Penghargaan"
placeholder='masukkan judul gambar penghargaan'
/>
<Text fw={"bold"}>Deskripsi Gambar Penghargaan</Text>
<DesaEditor showSubmit={false} />
<Box>
<Text fw={"bold"}>Upload Gambar Penghargaan</Text>
<Box bg={colors['BG-trans']} p={"md"}>
<Center>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Center>
</Box>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListGambarPenghargaan />
</SimpleGrid>
</Stack>
</Box>
);
}
export default GambarPerhargaan;

View File

@@ -1,17 +0,0 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPenghargaan() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Penghargaan</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPenghargaan;

View File

@@ -1,40 +0,0 @@
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListPenghargaan from './listPage';
function Penghargaan() {
return (
<Box py={10}>
<Stack>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Penghargaan</Title>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Penghargaan</Text>
<DesaEditor
showSubmit={false}
/>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPenghargaan/>
</SimpleGrid>
</Stack>
</Box>
);
}
export default Penghargaan;

View File

@@ -0,0 +1,62 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Profile Desa",
value: "profiledesa",
href: "/admin/desa/profile/profile-desa"
},
{
label: "Profile Perbekel",
value: "profileperbekel",
href: "/admin/desa/profile/profile-perbekel"
}
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Profile Desa</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsDetail;

View File

@@ -0,0 +1,71 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabsEdit({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Sejarah Desa",
value: "sejarahdesa",
href: "/admin/desa/profile/edit/sejarah_desa"
},
{
label: "Visi Misi Desa",
value: "visimisidesa",
href: "/admin/desa/profile/edit/visi_misi_desa"
},
{
label: "Lambang Desa",
value: "lambangdesa",
href: "/admin/desa/profile/edit/lambang_desa"
},
{
label: "Maskot Desa",
value: "maskotdesa",
href: "/admin/desa/profile/edit/maskot_desa"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Tabs color={colors['blue-button']} variant='default' value={activeTab} onChange={handleTabChange}>
<TabsList>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsEdit;

View File

@@ -0,0 +1,11 @@
'use client'
import LayoutTabsDetail from "./_lib/layoutTabsDetail"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsDetail>
{children}
</LayoutTabsDetail>
)
}

View File

@@ -1,53 +0,0 @@
import colors from '@/con/colors';
import { Stack, Title, Tabs, TabsList, TabsTab, TabsPanel } from '@mantine/core';
import React from 'react';
import SejarahDesa from './ui/sejarah_desa/page';
import VisiMisiDesa from './ui/visi_misi_desa/page';
import LambangDesa from './ui/lambang_desa/page';
import MaskotDesa from './ui/maskot_desa/page';
import ProfilePerbekel from './ui/profile_perbekel/page';
function Page() {
return (
<Stack >
<Title order={3}>Profile Desa</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Sejarah Desa"}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="Sejarah Desa">
Sejarah Desa
</TabsTab>
<TabsTab value="Visi Misi Desa">
Visi Misi Desa
</TabsTab>
<TabsTab value="Lambang Desa">
Lambang Desa
</TabsTab>
<TabsTab value="Maskot Desa">
Maskot Desa
</TabsTab>
<TabsTab value="Profile Perbekel">
Profile Perbekel
</TabsTab>
</TabsList>
<TabsPanel value="Sejarah Desa">
<SejarahDesa/>
</TabsPanel>
<TabsPanel value="Visi Misi Desa">
<VisiMisiDesa/>
</TabsPanel>
<TabsPanel value="Lambang Desa">
<LambangDesa/>
</TabsPanel>
<TabsPanel value="Maskot Desa">
<MaskotDesa/>
</TabsPanel>
<TabsPanel value="Profile Perbekel">
<ProfilePerbekel/>
</TabsPanel>
</Tabs>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,126 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function Page() {
const lambangState = useProxy(stateProfileDesa.lambangDesa)
const router = useRouter()
const params = useParams()
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
const loadData = async () => {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-desa");
return;
}
const data = await lambangState.findUnique.load(id);
if (data) {
lambangState.update.initialize(data);
}
};
loadData();
return () => {
lambangState.update.reset();
lambangState.findUnique.reset(); // opsional: reset juga data lama
};
}, [params?.id, router]);
const handleSubmit = async () => {
if (isSubmitting || !lambangState.update.form.judul.trim()) {
toast.error("Judul wajib diisi");
return;
}
setIsSubmitting(true)
try {
const success = await lambangState.update.submit()
if (success) {
toast.success("Data berhasil disimpan");
router.push("/admin/desa/profile/profile-desa");
}
} catch (error) {
console.error("Error update lambang desa:", error);
toast.error("Terjadi kesalahan saat update lambang desa");
} finally {
setIsSubmitting(false);
}
}
const handleBack = () => {
router.back()
}
if (
lambangState.findUnique.loading ||
!lambangState.findUnique.data ||
lambangState.update.loading
) {
return (
<Box>
<Center h={400}>
<Text>Memuat data...</Text>
</Center>
</Box>
);
}
return (
<Box>
<Stack gap={'xs'}>
<Group>
<Button variant="subtle" onClick={handleBack}>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Group>
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Box>
<Box>
<Stack>
<Title order={3}>Edit Lambang Desa</Title>
<TextInput
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
placeholder="Judul"
value={lambangState.update.form.judul}
onChange={(e) => lambangState.update.form.judul = e.currentTarget.value}
error={!lambangState.update.form.judul && "Judul wajib diisi"}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
<EditEditor
value={lambangState.update.form.deskripsi}
onChange={(val) => lambangState.update.form.deskripsi = val}
/>
</Box>
<Group>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Box>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default Page;

View File

@@ -0,0 +1,244 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function Page() {
const maskotState = useProxy(stateProfileDesa.maskotDesa)
const router = useRouter()
const params = useParams()
const [images, setImages] = useState<
Array<{ file: File; preview: string; label: string }>
>([]);
const [formData, setFormData] = useState({
judul: maskotState.update.form.judul || '',
deskripsi: maskotState.update.form.deskripsi || '',
images: [] as Array<{ label: string; imageId: string }>
})
useEffect(() => {
const loadData = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await maskotState.findUnique.load(id);
if (data) {
// 🔥 INI YANG KURANG!
maskotState.update.initialize(data);
setFormData({
judul: data.judul || '',
deskripsi: data.deskripsi || '',
images: (data.images || []).map((img: any) => ({
label: img.label,
imageId: img.image?.id ?? '',
})),
});
if (data?.images?.length > 0 && data.images[0].image?.link) {
setImages(data.images.map((img: any) => ({
file: null,
preview: img.image.link,
label: img.label,
})));
}
}
} catch (error) {
console.error("Error loading berita:", error);
toast.error("Gagal memuat data berita");
}
};
loadData();
}, [params?.id]);
const handleBack = () => {
router.back()
}
const handleSubmit = async () => {
try {
const uploadedImages = [];
// Upload semua gambar baru
for (const img of images) {
if (!img.file || !(img.file instanceof File)) {
toast.error("File tidak valid untuk di-upload");
continue; // atau return kalau kamu mau hentikan semua
}
const res = await ApiFetch.api.fileStorage.create.post({
file: img.file,
name: img.file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
toast.error("Gagal upload salah satu gambar");
return;
}
uploadedImages.push({
imageId: uploaded.id,
label: img.label || 'main',
});
}
// Update ke global state
maskotState.update.updateField("judul", formData.judul);
maskotState.update.updateField("deskripsi", formData.deskripsi);
maskotState.update.updateField("images", uploadedImages);
const success = await maskotState.update.submit();
if (success) {
toast.success("Maskot berhasil diperbarui!");
router.push("/admin/desa/profile/profile-desa");
}
} catch (error) {
console.error("Error update maskot:", error);
toast.error("Gagal update maskot");
}
};
return (
<Box>
<Stack gap={'xs'}>
<Group>
<Button variant="subtle" onClick={handleBack}>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Group>
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '100%' }}>
<Stack gap={'xs'}>
<Box>
<Box>
<Stack>
<Title order={3}>Edit Maskot Desa</Title>
<TextInput
w={{ base: '100%', md: '50%' }}
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
placeholder="Masukkan judul"
value={formData.judul}
onChange={(val) => setFormData({ ...formData, judul: val.currentTarget.value })}
/>
<Box w={{ base: '100%', md: '50%' }}>
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
/>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box w={{ base: '100%', md: '50%' }}>
<Dropzone
onDrop={(files) => {
const newImages = files.map((file) => ({
file,
preview: URL.createObjectURL(file),
label: '',
}));
setImages((prev) => [...prev, ...newImages]);
}}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag images here or click to select files
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb
</Text>
</div>
</Group>
</Dropzone>
</Box>
</Box>
<SimpleGrid cols={{ base: 2, md: 4 }} >
{images.map((img, index) => (
<Box key={index} mb="md">
<Paper p="sm" radius="md" withBorder style={{ position: 'relative', maxWidth: 300 }}>
<Stack gap={'xs'}>
<Group>
<Button
size="xs"
color="red"
variant="light"
onClick={() => {
const updated = [...images];
updated.splice(index, 1);
setImages(updated);
}}
>
Hapus
</Button>
</Group>
<Image
src={img.preview}
alt={`Preview ${index}`}
width={280}
height={180}
fit="cover"
radius="sm"
/>
<TextInput
label={`Label Gambar ${index + 1}`}
placeholder="Contoh: Logo, Maskot, Dll"
value={img.label}
onChange={(e) => {
const updated = [...images];
updated[index].label = e.currentTarget.value;
setImages(updated);
}}
/>
</Stack>
</Paper>
</Box>
))}
</SimpleGrid>
<Group>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Box>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default Page;

View File

@@ -0,0 +1,126 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function Page() {
const sejarahState = useProxy(stateProfileDesa.sejarahDesa)
const router = useRouter()
const params = useParams()
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
const loadData = async () => {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-desa");
return;
}
const data = await sejarahState.findUnique.load(id);
if (data) {
sejarahState.update.initialize(data);
}
};
loadData();
return () => {
sejarahState.update.reset();
sejarahState.findUnique.reset(); // opsional: reset juga data lama
};
}, [params?.id, router]);
const handleSubmit = async () => {
if (isSubmitting || !sejarahState.update.form.judul.trim()) {
toast.error("Judul wajib diisi");
return;
}
setIsSubmitting(true)
try {
const success = await sejarahState.update.submit()
if (success) {
toast.success("Data berhasil disimpan");
router.push("/admin/desa/profile/profile-desa");
}
} catch (error) {
console.error("Error update sejarah desa:", error);
toast.error("Terjadi kesalahan saat update sejarah desa");
} finally {
setIsSubmitting(false);
}
}
const handleBack = () => {
router.back()
}
if (
sejarahState.findUnique.loading ||
!sejarahState.findUnique.data ||
sejarahState.update.loading
) {
return (
<Box>
<Center h={400}>
<Text>Memuat data...</Text>
</Center>
</Box>
);
}
return (
<Box>
<Stack gap={'xs'}>
<Group>
<Button variant="subtle" onClick={handleBack}>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Group>
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Box>
<Box>
<Stack>
<Title order={3}>Edit Sejarah Desa</Title>
<TextInput
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
placeholder="Judul"
value={sejarahState.update.form.judul}
onChange={(e) => sejarahState.update.form.judul = e.currentTarget.value}
error={!sejarahState.update.form.judul && "Judul wajib diisi"}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
<EditEditor
value={sejarahState.update.form.deskripsi}
onChange={(val) => sejarahState.update.form.deskripsi = val}
/>
</Box>
<Group>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Box>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default Page;

View File

@@ -0,0 +1,124 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, Stack, Text, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function Page() {
const visiMisiState = useProxy(stateProfileDesa.visiMisiDesa)
const router = useRouter()
const params = useParams()
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
const loadData = async () => {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-desa");
return;
}
const data = await visiMisiState.findUnique.load(id);
if (data) {
visiMisiState.update.initialize(data);
}
};
loadData();
return () => {
visiMisiState.update.reset();
visiMisiState.findUnique.reset(); // opsional: reset juga data lama
};
}, [params?.id, router]);
const handleSubmit = async () => {
if (isSubmitting || !visiMisiState.update.form.visi.trim()) {
toast.error("Visi wajib diisi");
return;
}
setIsSubmitting(true)
try {
const success = await visiMisiState.update.submit()
if (success) {
toast.success("Data berhasil disimpan");
router.push("/admin/desa/profile/profile-desa");
}
} catch (error) {
console.error("Error update sejarah desa:", error);
toast.error("Terjadi kesalahan saat update sejarah desa");
} finally {
setIsSubmitting(false);
}
}
const handleBack = () => {
router.back()
}
if (
visiMisiState.findUnique.loading ||
!visiMisiState.findUnique.data ||
visiMisiState.update.loading
) {
return (
<Box>
<Center h={400}>
<Text>Memuat data...</Text>
</Center>
</Box>
);
}
return (
<Box>
<Stack gap={'xs'}>
<Group>
<Button variant="subtle" onClick={handleBack}>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Group>
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Box>
<Box>
<Stack>
<Title order={3}>Edit Visi Misi Desa</Title>
<Text fz={"md"} fw={"bold"}>Visi</Text>
<EditEditor
value={visiMisiState.update.form.visi}
onChange={(val) => visiMisiState.update.form.visi = val}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Misi</Text>
<EditEditor
value={visiMisiState.update.form.misi}
onChange={(val) => visiMisiState.update.form.misi = val}
/>
</Box>
<Group>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Box>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default Page;

View File

@@ -0,0 +1,171 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Card, Center, Grid, GridCol, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
import { useSnapshot } from 'valtio';
import stateProfileDesa from '../../../_state/desa/profile';
import { useEffect } from 'react';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function Page() {
const router = useRouter();
const snap = useSnapshot(stateProfileDesa);
// Panggil load data sekali saat komponen mount
useEffect(() => {
stateProfileDesa.sejarahDesa.findUnique.load("edit");
stateProfileDesa.visiMisiDesa.findUnique.load("edit");
stateProfileDesa.lambangDesa.findUnique.load("edit");
stateProfileDesa.maskotDesa.findUnique.load("edit");
}, []);
const sejarah = snap.sejarahDesa.findUnique.data;
const visiMisi = snap.visiMisiDesa.findUnique.data;
const lambang = snap.lambangDesa.findUnique.data;
const maskot = snap.maskotDesa.findUnique.data;
return (
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"lg"}>
<Title order={2}>Preview Profile Desa</Title>
{/* Sejarah Desa */}
{sejarah && (
<Box>
<Stack gap={'lg'}>
<Paper p={"md"} bg={colors['BG-trans']}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={2}>Preview Sejarah Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/desa/profile/profile-desa/${sejarah.id}/sejarah_desa`)}>
<IconEdit size={20} />
</Button>
</GridCol>
</Grid>
<Box pb={30}>
<Center>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>{sejarah.judul}</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: sejarah.deskripsi }} />
</Paper>
</Paper>
</Stack>
</Box>
)}
{/* Visi Misi Desa */}
{visiMisi && (
<Box>
<Stack gap={'lg'}>
<Paper p={"md"} bg={colors['BG-trans']}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={2}>Preview Visi Misi Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${visiMisi.id}/visi_misi_desa`)} bg={colors['blue-button']}>
<IconEdit size={20} />
</Button>
</GridCol>
</Grid>
<Box pb={30}>
<Center>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Visi Misi Desa</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fw={"bold"} fz={{ base: "lg", md: "h2" }}>Visi Desa</Text>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: visiMisi.visi }} />
<Text fw={"bold"} fz={{ base: "lg", md: "h2" }}>Misi Desa</Text>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: visiMisi.misi }} />
</Paper>
</Paper>
</Stack>
</Box>
)}
{/* Lambang Desa */}
{lambang && (
<Box>
<Stack gap={'lg'}>
<Paper p={"md"} bg={colors['BG-trans']}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={2}>Preview Lambang Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${lambang.id}/lambang_desa`)} bg={colors['blue-button']}>
<IconEdit size={20} />
</Button>
</GridCol>
</Grid>
<Box pb={30}>
<Center>
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Lambang Desa</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: lambang.deskripsi }} />
</Paper>
</Paper>
</Stack>
</Box>
)}
{/* Maskot Desa */}
{maskot && (
<Box>
<Stack gap={'lg'}>
<Paper p={"md"} bg={colors['BG-trans']}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={2}>Preview Maskot Desa</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${maskot.id}/maskot_desa`)} bg={colors['blue-button']}>
<IconEdit size={20} />
</Button>
</GridCol>
</Grid>
<Box pb={30}>
<Center>
<Image src={"/pudak-icon.png"} alt="" w={{ base: 200, md: 300 }} />
</Center>
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Maskot Desa</Text>
</Box>
<Paper p={"xl"} bg={colors['white-trans-1']}>
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: maskot.deskripsi }} />
<Group wrap="wrap" gap="md">
{maskot.images.map((img, index) => (
<Card key={index} p="xs" w={220}>
<Image
src={img.image.link}
alt={img.label}
w={200}
h={200}
fit="cover"
radius="md"
style={{ border: '1px solid #ccc', objectFit: 'cover' }}
/>
<Text ta="center" mt="xs" fw="bold">{img.label}</Text>
</Card>
))}
</Group>
</Paper>
</Paper>
</Stack>
</Box>
)}
</Stack>
</Paper>
);
}
export default Page;

View File

@@ -0,0 +1,193 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function ProfilePerbekel() {
const perbekelState = useProxy(stateProfileDesa.profilPerbekel)
const router = useRouter()
const params = useParams()
const [isSubmitting, setIsSubmitting] = useState(false);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
useEffect(() => {
const loadData = async () => {
const id = params?.id as string;
if (!id) {
toast.error("ID tidak valid");
router.push("/admin/desa/profile/profile-perbekel");
return;
}
const data = await perbekelState.findUnique.load(id);
if (data) {
perbekelState.edit.initialize(data);
}
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
};
loadData();
return () => {
perbekelState.edit.reset();
perbekelState.findUnique.reset(); // opsional: reset juga data lama
};
}, [params?.id, router]);
const handleFileChange = (newFile: File | null) => {
if (!newFile) {
setFile(null);
return;
}
setFile(newFile);
const reader = new FileReader();
reader.onload = (event) => {
setPreviewImage(event.target?.result as string);
};
reader.readAsDataURL(newFile);
};
const handleSubmit = async () => {
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
toast.error("Biodata wajib diisi");
return;
}
setIsSubmitting(true)
try {
if (file) {
const uploadResponse = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
const uploaded = uploadResponse.data?.data;
if (!uploaded?.id) {
toast.error("Gagal upload gambar");
return;
}
perbekelState.edit.form.imageId = uploaded.id;
}
const success = await perbekelState.edit.submit()
if (success) {
toast.success("Data berhasil disimpan");
router.push("/admin/desa/profile/profile-perbekel");
}
} catch (error) {
console.error("Error update sejarah desa:", error);
toast.error("Terjadi kesalahan saat update sejarah desa");
} finally {
setIsSubmitting(false);
}
}
const handleBack = () => {
router.back()
}
if (
perbekelState.findUnique.loading ||
!perbekelState.findUnique.data ||
perbekelState.edit.loading
) {
return (
<Box>
<Center h={400}>
<Text>Memuat data...</Text>
</Center>
</Box>
);
}
return (
<Box py={10}>
<Stack gap={'xs'}>
<Group>
<Button variant="subtle" onClick={handleBack}>
<IconArrowBack color={colors['blue-button']} size={20} />
</Button>
</Group>
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
<Stack gap={'xs'}>
<Box>
<Box>
<Stack>
<Title order={3}>Edit Profil Perbekel</Title>
<Text fz={"md"} fw={"bold"}>Biodata</Text>
<EditEditor
value={perbekelState.edit.form.biodata}
onChange={(val) => perbekelState.edit.form.biodata = val}
/>
{/* File Upload */}
<FileInput
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
value={file}
onChange={handleFileChange}
accept="image/*"
/>
{/* Preview Gambar */}
<Box>
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
{previewImage ? (
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
) : (
<Center w={200} h={200} bg="gray.2">
<Stack align="center" gap="xs">
<IconImageInPicture size={48} color="gray" />
<Text size="sm" c="gray">Tidak ada gambar</Text>
</Stack>
</Center>
)}
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Pengalaman</Text>
<EditEditor
value={perbekelState.edit.form.pengalaman}
onChange={(val) => perbekelState.edit.form.pengalaman = val}
/>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Pengalaman Organisasi</Text>
<EditEditor
value={perbekelState.edit.form.pengalamanOrganisasi}
onChange={(val) => perbekelState.edit.form.pengalamanOrganisasi = val}
/>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Program Unggulan</Text>
<EditEditor
value={perbekelState.edit.form.programUnggulan}
onChange={(val) => perbekelState.edit.form.programUnggulan = val}
/>
</Box>
<Group>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Box>
</Box>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default ProfilePerbekel;

View File

@@ -0,0 +1,112 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Stack, Text, Title } from '@mantine/core';
import { IconEdit } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useSnapshot } from 'valtio';
import stateProfileDesa from '../../../_state/desa/profile';
function Page() {
const router = useRouter();
const snap = useSnapshot(stateProfileDesa);
// Panggil load data sekali saat komponen mount
useEffect(() => {
stateProfileDesa.profilPerbekel.findUnique.load("edit");
}, []);
const perbekel = snap.profilPerbekel.findUnique.data;
return (
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Title order={3}>Preview Profile PPID</Title>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/desa/profile/profile-perbekel/${snap.profilPerbekel.findUnique.data?.id}`)}>
<IconEdit size={16} />
</Button>
</GridCol>
</Grid>
{perbekel && (
<Box >
<Paper p={"xl"} bg={colors['BG-trans']}>
<Box px={{ base: "md", md: 100 }}>
<Grid>
<GridCol span={{ base: 12, md: 12 }}>
<Center>
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
</Center>
</GridCol>
<GridCol span={{ base: 12, md: 12 }}>
<Text ta={"center"} fz={{ base: "1.2rem", md: "1.8rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
</GridCol>
</Grid>
</Box>
<Divider my={"md"} color={colors['blue-button']} />
{/* biodata perbekel */}
<Box px={{ base: 0, md: 50 }} pb={30}>
<Box pb={20} px={{ base: 0, md: 50 }}>
<Paper bg={colors['BG-trans']} w={{ base: "100%", md: "100%" }}>
<Stack gap={0}>
<Center>
<Image
pt={{ base: 0, md: 90 }}
src={perbekel.image?.link || "/perbekel.png"}
w={{ base: 250, md: 350 }}
alt='Foto Profil PPID'
onError={(e) => {
e.currentTarget.src = "/perbekel.png";
}}
/>
</Center>
<Paper
bg={colors['blue-button']}
py={20}
className="glass3"
px={{ base: 10, md: 10 }}
>
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.2rem", md: "1.6rem" }}>
I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P.
</Text>
</Paper>
</Stack>
</Paper>
</Box>
<Box pt={10}>
<Box>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Biodata</Text>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.biodata }} />
</Box>
<Box>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman</Text>
<Text fz={{ base: "1rem", md: "1.5rem" }} dangerouslySetInnerHTML={{ __html: perbekel.pengalaman }} />
</Box>
</Box>
<Box pb={30}>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.pengalamanOrganisasi }} />
</Box>
</Box>
<Box pb={20}>
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
<Box px={20}>
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.programUnggulan }} />
</Box>
</Box>
</Box>
</Paper>
</Box>
)}
</Stack>
</Paper>
);
}
export default Page;

View File

@@ -1,39 +0,0 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
function LambangDesa() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Lambang Desa</Title>
<Text fw={"bold"}>Deskripsi Lambang Desa</Text>
<DesaEditor showSubmit={false} />
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Lambang Desa</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Box>
);
}
export default LambangDesa;

View File

@@ -1,39 +0,0 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
function MaskotDesa() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Maskot Desa</Title>
<Text fw={"bold"}>Deskripsi Maskot Desa</Text>
<DesaEditor showSubmit={false} />
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Maskot Desa</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Box>
);
}
export default MaskotDesa;

View File

@@ -1,49 +0,0 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, TextInput, Text } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
function ProfilePerbekel() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Profil Perbekel</Title>
<TextInput
label="Nama Perbekel"
placeholder="masukkan nama perbekel"
/>
<Text fz={"sm"} fw={"bold"}>Biodata</Text>
<DesaEditor showSubmit={false} />
<Text fz={"sm"} fw={"bold"}>Pengalaman</Text>
<DesaEditor showSubmit={false} />
<Text fz={"sm"} fw={"bold"}>Pengalaman Organisasi</Text>
<DesaEditor showSubmit={false} />
<Text fz={"sm"} fw={"bold"}>Program Unggulan</Text>
<DesaEditor showSubmit={false} />
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Profil Perbekel</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Box>
);
}
export default ProfilePerbekel;

View File

@@ -1,17 +0,0 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPage() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Sejarah Desa</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPage;

View File

@@ -1,65 +0,0 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import DesaEditorText from '../../../_com/desaEditorText';
import ListPage from './listPage';
import { useShallowEffect } from '@mantine/hooks';
function SejarahDesa() {
const stateSejarah = useProxy(stateProfileDesa.Sejarah)
useShallowEffect(() => {
if (!stateSejarah.findById.data) {
stateSejarah.findById.initialize()
}
}, [])
const submit = () => {
if (stateSejarah.findById.data?.id && stateSejarah.findById.data.sejarah) {
stateSejarah.update.save({
id: stateSejarah.findById.data.id,
sejarah: stateSejarah.findById.data.sejarah
})
}
}
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Sejarah Desa</Title>
<Text fw={"bold"}>Deskripsi Sejarah Desa</Text>
<DesaEditorText
key={stateSejarah.findById.data?.id ?? 'new'}
showSubmit={false}
onChange={(val) => {
if (stateSejarah.findById.data) {
stateSejarah.findById.data.sejarah = val
}
}}
initialContent={stateSejarah.findById.data?.sejarah ?? ""}
/>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
onClick={submit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPage />
</SimpleGrid>
</Box>
);
}
export default SejarahDesa;

View File

@@ -1,64 +0,0 @@
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { DesaEditor } from '../../../_com/desaEditor';
function VisiMisiDesa() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Visi Desa</Title>
<Text fw={"bold"}>Deskripsi Visi Desa</Text>
<DesaEditor showSubmit={false} />
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Visi Desa</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
<SimpleGrid py={30} cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Misi Desa</Title>
<Text fw={"bold"}>Deskripsi Misi Desa</Text>
<DesaEditor showSubmit={false}/>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Misi Desa</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Box>
);
}
export default VisiMisiDesa;

View File

@@ -0,0 +1,77 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabs({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Presentase Kelahiran & Kematian",
value: "presentasekelahiran&kematian",
href: "/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian"
},
{
label: "Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik",
value: "grafikhasilkepuasan",
href: "/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan"
},
{
label: "Fasilitas Kesehatan",
value: "fasilitaskesehatan",
href: "/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan"
},
{
label: "Jadwal Kegiatan",
value: "jadwalkegiatan",
href: "/admin/kesehatan/data-kesehatan-warga/jadwal_kegiatan"
},
{
label: "Artikel Kesehatan",
value: "artikelkesehatan",
href: "/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Data Kesehatan Warga</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabs;

View File

@@ -1,26 +0,0 @@
'use client'
import { Box, Paper, Text } from '@mantine/core';
import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
function DoctorSignUI() {
const doctorSign = useProxy(stateArtikelKesehatan.doctorSign)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Kapan Harus ke Dokter</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
doctorSign.create.form.content = val
}}
/>
</Paper>
</Box>
);
}
export default DoctorSignUI;

View File

@@ -1,34 +0,0 @@
'use client'
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function FirstAidUI() {
const firstAidState = useProxy(stateArtikelKesehatan.firstAid)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
label={<Text fw={"bold"}>Judul Pertolongan Pertama</Text>}
placeholder="Masukkan judul"
onChange={(val) => {
firstAidState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
firstAidState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default FirstAidUI;

View File

@@ -1,28 +0,0 @@
'use client'
import { Box, Paper, Stack, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
function IntoductionUI() {
const introduction = useProxy(stateArtikelKesehatan.introduction)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text fw={"bold"}>Pendahuluan</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
introduction.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default IntoductionUI;

View File

@@ -1,40 +0,0 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
function MythFactUI() {
const mythFact = useProxy(stateArtikelKesehatan.mythFact)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
label={<Text fw={"bold"}>Judul Pertolongan Pertama Penyakit</Text>}
placeholder="Masukkan judul"
onChange={(val) => {
mythFact.create.form.title = val.target.value
}}
/>
<TextInput
label={<Text fw={"bold"}>Mitos</Text>}
placeholder="Masukkan mitos"
onChange={(val) => {
mythFact.create.form.mitos = val.target.value
}}
/>
<TextInput
label={<Text fw={"bold"}>Fakta</Text>}
placeholder="Masukkan fakta"
onChange={(val) => {
mythFact.create.form.fakta = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default MythFactUI;

View File

@@ -1,172 +0,0 @@
'use client'
import { Box, Button, Center, Group, Paper, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import IntoductionUI from './introduction/page';
import SymptomUI from './symptom/page';
import PreventionUI from './prevention/page';
import MythFactUI from './mythVsfact/page';
import DoctorSignUI from './doctor_sign/page';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import FirstAidUI from './first_aid/page';
import { useShallowEffect } from '@mantine/hooks';
import colors from '@/con/colors';
function ArtikelKesehatan() {
const state = useProxy(stateArtikelKesehatan)
const submitAllForms = () => {
if (state.introduction.create.form.content) {
state.introduction.create.create()
}
if (state.symptom.create.form.title && state.symptom.create.form.content) {
state.symptom.create.create()
}
if (state.prevention.create.form.title && state.prevention.create.form.content) {
state.prevention.create.create()
}
if (state.firstAid.create.form.title && state.firstAid.create.form.content) {
state.firstAid.create.create()
}
if (state.mythFact.create.form.title && state.mythFact.create.form.mitos && state.mythFact.create.form.fakta) {
state.mythFact.create.create()
}
if (state.doctorSign.create.form.content) {
state.doctorSign.create.create()
}
}
return (
<Stack py={10}>
<SimpleGrid cols={{
base: 1, md: 2
}}>
<Box >
<Stack gap={"xs"}>
<Title order={4}>Artikel Kesehatan</Title>
<IntoductionUI />
<SymptomUI />
<PreventionUI />
<FirstAidUI />
<MythFactUI />
<DoctorSignUI />
<Group>
<Button bg={colors['blue-button']} mt={10} onClick={submitAllForms}>Submit</Button>
</Group>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={4}>Data Artikel Kesehatan</Title>
<AllList />
</Stack>
</Box>
</SimpleGrid>
</Stack>
);
}
function AllList() {
const listState = useProxy(stateArtikelKesehatan)
useShallowEffect(() => {
listState.introduction.findMany.load();
listState.symptom.findMany.load();
listState.prevention.findMany.load();
listState.firstAid.findMany.load();
listState.mythFact.findMany.load();
listState.doctorSign.findMany.load();
}, [])
if (!listState.introduction.findMany.data
|| !listState.symptom.findMany.data
|| !listState.prevention.findMany.data
|| !listState.firstAid.findMany.data
|| !listState.mythFact.findMany.data
|| !listState.doctorSign.findMany.data
) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack gap={"xs"}>
{/* Introduction */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pendahuluan</Title>
{listState.introduction.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Symptom */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Gejala Penyakit</Title>
{listState.symptom.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Prevention */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pencegahan Penyakit</Title>
{listState.prevention.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* First Aid */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pertolongan Pertama</Title>
{listState.firstAid.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Myth Fact */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Mitos vs Fakta</Title>
{listState.mythFact.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead >
<TableTr >
<TableTh >
<Center>Mitos</Center>
</TableTh>
<TableTh >
<Center>Fakta</Center>
</TableTh>
</TableTr>
</TableThead>
<TableTbody >
<TableTr>
<TableTd ta="center">{item.mitos}</TableTd>
<TableTd ta="center">{item.fakta}</TableTd>
</TableTr>
</TableTbody>
</Table>
</Box>
))}
</Paper>
{/* Doctor Sign */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Kapan Harus Ke Dokter?</Title>
{listState.doctorSign.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
</Stack>
}
export default ArtikelKesehatan;

View File

@@ -1,35 +0,0 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function PreventionUI() {
const preventionState = useProxy(stateArtikelKesehatan.prevention)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Judul Pencegahan Penyakit</Text>}
placeholder="Masukkan judul"
onChange={(val) => {
preventionState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
preventionState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default PreventionUI;

View File

@@ -1,34 +0,0 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function SymptomUI() {
const symptomState = useProxy(stateArtikelKesehatan.symptom)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Judul Gejala Penyakit</Text>}
placeholder='masukkan judul'
onChange={(val) => {
symptomState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
symptomState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default SymptomUI;

View File

@@ -1,89 +0,0 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Center, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import _ from 'lodash';
import { useProxy } from 'valtio/utils';
function DokterdanTenagaMedisList() {
const kesehatanState = useProxy(stateFasilitasKesehatan.dokterdantenagamedis);
useShallowEffect(() => {
kesehatanState.findMany.load();
}, []);
// Penanganan kasus ketika tidak ada data
if (_.isEmpty(kesehatanState.findMany.data)) {
return (
<Stack>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead>
<TableTr>
<TableTh>
<Center>Nama</Center>
</TableTh>
<TableTh>
<Center>Specialist</Center>
</TableTh>
<TableTh >
<Center>Jadwal</Center>
</TableTh>
</TableTr>
</TableThead>
<TableTbody >
<TableTr >
<TableTd colSpan={3}>
<Center>
<Text>Tidak ada data</Text>
</Center>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Stack>
);
}
return (
<Stack>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead >
<TableTr >
<TableTh >
<Center>Nama</Center>
</TableTh>
<TableTh >
<Center>Specialist</Center>
</TableTh>
<TableTh >
<Center>Jadwal</Center>
</TableTh>
</TableTr>
</TableThead>
<TableTbody >
{kesehatanState.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">Specialist {item.specialist}</TableTd>
<TableTd ta="center">{item.jadwal}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Stack>
);
}
export default DokterdanTenagaMedisList;

View File

@@ -1,41 +0,0 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
function DokterDanTenagaMedis() {
const dokterdantenagamedisState = useProxy(stateFasilitasKesehatan.dokterdantenagamedis)
return (
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Text fw={"bold"}>Dokter & Tenaga Medis</Text>
<TextInput
label="Nama Dokter"
placeholder='masukkan nama dokter'
onChange={(val) => {
dokterdantenagamedisState.create.form.name = val.target.value
}}
/>
<TextInput
label="Specialist"
placeholder='masukkan specialist'
onChange={(val) => {
dokterdantenagamedisState.create.form.specialist = val.target.value
}}
/>
<TextInput
mb={10}
label="Jadwal"
placeholder='masukkan jadwal'
onChange={(val) => {
dokterdantenagamedisState.create.form.jadwal = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}
export default DokterDanTenagaMedis;

View File

@@ -1,21 +0,0 @@
import { Box, Stack, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { useShallowEffect } from '@mantine/hooks';
function FasilitasPendukungList() {
const fasilitaspendukungState = useProxy(stateFasilitasKesehatan.fasilitaspendukung)
useShallowEffect(() => {
fasilitaspendukungState.findMany.load()
}, [])
return <Stack>
{fasilitaspendukungState.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{__html: item.content}}/>
</Box>
))}
</Stack>
}
export default FasilitasPendukungList;

View File

@@ -1,21 +0,0 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Paper, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function FasilitasPendukung() {
const fasilitaspendukungState = useProxy(stateFasilitasKesehatan.fasilitaspendukung)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Fasilitas Pendukung</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
fasilitaspendukungState.create.form.content = val;
}} />
</Paper>
</Box>
}
export default FasilitasPendukung;

View File

@@ -1,40 +0,0 @@
'use client'
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
function InformasiUmum() {
const infromasiState = useProxy(stateFasilitasKesehatan.informasiumum)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Informasi Umum</Text>
<Stack gap={"xs"}>
<TextInput
label="Fasilitas"
placeholder='masukkan nama fasilitas kesehatan'
onChange={(val) => {
infromasiState.create.form.fasilitas = val.target.value
}}
/>
<TextInput
label="Alamat"
placeholder='masukkan alamat'
onChange={(val) => {
infromasiState.create.form.alamat = val.target.value
}}
/>
<TextInput
mb={10}
label="Jam Operasional"
placeholder='masukkan jam operasional'
onChange={(val) => {
infromasiState.create.form.jamOperasional = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
}
export default InformasiUmum;

View File

@@ -1,23 +0,0 @@
'use client'
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Paper, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function LayananUnggulan() {
const informasiumumState = useProxy(stateFasilitasKesehatan.layananunggulan)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Layanan Unggulan</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
informasiumumState.create.form.content = val;
}} />
</Paper>
</Box>
;
}
export default LayananUnggulan;

View File

@@ -1,254 +0,0 @@
"use client"
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
import DokterDanTenagaMedis from './dokterdantenagamedis/page';
import FasilitasPendukung from './fasilitas_pendukung/page';
import InformasiUmum from './informasi_umum/page';
import LayananUnggulan from './layanan_unggulan/page';
import ProsedurPendaftaran from './prosedurpendaftaran/page';
import TarifDanLayanan from './tarifdanlayanan/page';
function FasilitasKesehatan() {
const allState = useProxy(stateFasilitasKesehatan)
const submitAllForms = () => {
if (allState.informasiumum.create.form.fasilitas &&
allState.informasiumum.create.form.alamat &&
allState.informasiumum.create.form.jamOperasional) {
allState.informasiumum.create.create()
}
if (allState.layananunggulan.create.form.content) {
allState.layananunggulan.create.create();
}
if (allState.dokterdantenagamedis.create.form.name &&
allState.dokterdantenagamedis.create.form.specialist &&
allState.dokterdantenagamedis.create.form.jadwal) {
allState.dokterdantenagamedis.create.create()
}
if (allState.fasilitaspendukung.create.form.content) {
allState.fasilitaspendukung.create.create();
}
if (allState.tarifdanlayanan.create.form.tarif &&
allState.tarifdanlayanan.create.form.layanan) {
allState.tarifdanlayanan.create.create()
}
if (allState.prosedurpendaftaran.create.form.content) {
allState.prosedurpendaftaran.create.create();
}
// refreshListData();
// resetAllForms();
}
// const refreshListData = () => {
// allState.informasiumum.findMany.load();
// allState.layananunggulan.findMany.load();
// allState.dokterdantenagamedis.findMany.load();
// allState.fasilitaspendukung.findMany.load();
// allState.tarifdanlayanan.findMany.load();
// allState.prosedurpendaftaran.findMany.load();
// }
// const resetAllForms = () => {
// allState.informasiumum.create.form = {
// fasilitas: '',
// alamat: '',
// jamOperasional: ''
// };
// allState.layananunggulan.create.form = {
// content: ''
// };
// allState.dokterdantenagamedis.create.form = {
// name: '',
// specialist: '',
// jadwal: ''
// };
// allState.fasilitaspendukung.create.form = {
// content: ''
// };
// allState.tarifdanlayanan.create.form = {
// tarif: '',
// layanan: ''
// };
// allState.prosedurpendaftaran.create.form = {
// content: ''
// };
// }
return (
<Stack py={10}>
<SimpleGrid cols={{
base: 1, md: 2
}}>
<Box>
<Stack gap={'xs'}>
<Title order={4}>Tambah Fasilitas Kesehatan</Title>
{/* Informasi Umum */}
<InformasiUmum />
{/* Layanan Unggulan */}
<LayananUnggulan />
{/* Dokter & Tenaga Medis */}
<DokterDanTenagaMedis />
{/* Fasilitas Pendukung */}
<FasilitasPendukung />
{/* Tarif & Layanan */}
<TarifDanLayanan />
{/* Prosedur Pendaftaran */}
<ProsedurPendaftaran />
<Button onClick={submitAllForms}>Submit</Button>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={4}>Data Fasilitas Kesehatan</Title>
<AllList />
</Stack>
</Box>
</SimpleGrid>
</Stack>
);
}
function AllList() {
const allListState = useProxy(stateFasilitasKesehatan)
useShallowEffect(() => {
allListState.informasiumum.findMany.load();
allListState.layananunggulan.findMany.load();
allListState.dokterdantenagamedis.findMany.load();
allListState.fasilitaspendukung.findMany.load();
allListState.tarifdanlayanan.findMany.load();
allListState.prosedurpendaftaran.findMany.load();
}, [])
if (!allListState.informasiumum.findMany.data
|| !allListState.layananunggulan.findMany.data
|| !allListState.dokterdantenagamedis.findMany.data
|| !allListState.fasilitaspendukung.findMany.data
|| !allListState.tarifdanlayanan.findMany.data
|| !allListState.prosedurpendaftaran.findMany.data
) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Informasi Umum</Title>
{allListState.informasiumum.findMany.data?.map((item) => (
<Box key={item.id}>
<Text>{item.fasilitas}</Text>
<Text>{item.alamat}</Text>
<Text>{item.jamOperasional}</Text>
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Layanan Unggulan</Title>
{allListState.layananunggulan.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Dokter & Tenaga Medis</Title>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead >
<TableTr >
<TableTh >
<Center>Nama</Center>
</TableTh>
<TableTh >
<Center>Specialist</Center>
</TableTh>
<TableTh >
<Center>Jadwal</Center>
</TableTh>
</TableTr>
</TableThead>
<TableTbody >
{allListState.dokterdantenagamedis.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">Specialist {item.specialist}</TableTd>
<TableTd ta="center">{item.jadwal}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Fasilitas Pendukung</Title>
{allListState.fasilitaspendukung.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Tarif & Layanan</Title>
<Table
suppressHydrationWarning
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead>
<TableTr>
<TableTh>
Layanan
</TableTh>
<TableTh>
Tarif
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{allListState.tarifdanlayanan.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.layanan}</TableTd>
<TableTd>Rp.{item.tarif}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Prosedur Pendaftaran</Title>
{allListState.prosedurpendaftaran.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
</Stack>
}
export default FasilitasKesehatan;

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