Compare commits

...

14 Commits

Author SHA1 Message Date
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
225 changed files with 12046 additions and 3755 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": "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

@@ -50,27 +50,32 @@ 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[]
Penghargaan Penghargaan[]
InfoWabahPenyakit InfoWabahPenyakit[]
}
//========================================= MENU PPID ========================================= //
@@ -117,6 +122,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())
@@ -248,32 +254,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 ========================================= //
@@ -412,16 +454,16 @@ model PelayananPendudukNonPermanen {
// ========================================= PENGHARGAAN ========================================= //
model Penghargaan {
id String @id @default(cuid())
id String @id @default(cuid())
name String
juara String
deskripsi 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)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= MENU KESEHATAN ========================================= //
@@ -435,12 +477,18 @@ model FasilitasKesehatan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
InformasiUmum InformasiUmum[]
LayananUnggulan LayananUnggulan[]
DokterdanTenagaMedis DokterdanTenagaMedis[]
FasilitasPendukung FasilitasPendukung[]
ProsedurPendaftaran ProsedurPendaftaran[]
TarifDanLayanan TarifDanLayanan[]
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 {
@@ -448,10 +496,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)
}
@@ -582,7 +630,8 @@ model PendaftaranJadwalKegiatan {
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
model DataKematian_Kelahiran {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
uuid String @default(cuid()) @unique
tahun String
kematianKasar String
kematianBayi String
@@ -596,6 +645,7 @@ model DataKematian_Kelahiran {
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id Int @id @default(autoincrement())
uuid String @default(cuid()) @unique
label String
jumlah String
createdAt DateTime @default(now())
@@ -687,3 +737,98 @@ 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)
}

View File

@@ -12,6 +12,11 @@ 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) {
@@ -30,6 +35,104 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
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: {
@@ -67,8 +170,7 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
},
});
}
console.log("pelayanan perizinan berusaha success ...");
console.log("pelayanan penduduk non permanen success ...");
for (const s of strukturPPID) {
await prisma.strukturPPID.upsert({

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

@@ -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,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 uuid = res.data?.data?.uuid;
if (uuid) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.findMany.load();
return uuid;
}
}
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(uuid: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${uuid}`);
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: {
uuid: "",
form: {...defaultForm},
loading: false,
async byId() {
},
async submit() {
const uuid = this.uuid;
if (!uuid) {
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/${uuid}`, {
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(uuid: string) {
if (!uuid) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${uuid}`, {
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

@@ -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 uuid = res.data?.data?.uuid;
if (uuid) {
toast.success("Success create");
persentasekelahiran.create.form = { ...defaultForm };
persentasekelahiran.findMany.load();
return uuid;
}
}
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(uuid: string) {
try {
const res = await fetch(`/api/kesehatan/persentasekelahiran/${uuid}`);
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: {
uuid: "",
form: { ...defaultForm },
loading: false,
async submit() {
const uuid = this.uuid;
if (!uuid) {
toast.warn("UUID 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/${uuid}`, {
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(uuid: string) {
if (!uuid) return toast.warn("UUID tidak valid");
try {
persentasekelahiran.delete.loading = true;
const response = await fetch(`/api/kesehatan/persentasekelahiran/del/${uuid}`, {
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

@@ -1,18 +1,31 @@
export function convertYoutubeUrlToEmbed(url: string): string | null {
const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
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);
// const matchWatch = url.match(watchRegex);
// const matchShort = url.match(shortRegex);
if (matchWatch) {
return `https://www.youtube.com/embed/${matchWatch[1]}`;
}
// if (matchWatch) {
// return `https://www.youtube.com/embed/${matchWatch[1]}`;
// }
if (matchShort) {
return `https://www.youtube.com/embed/${matchShort[1]}`;
}
// if (matchShort) {
// return `https://www.youtube.com/embed/${matchShort[1]}`;
// }
return null;
}
// return null;
// }

View File

@@ -11,18 +11,16 @@ import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
function EditVideo() {
const router = useRouter();
const [embedLink, setEmbedLink] = useState("");
const 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 () => {
@@ -36,8 +34,6 @@ function EditVideo() {
deskripsi: data.deskripsi || '',
linkVideo: data.linkVideo || '',
});
const embed = convertYoutubeUrlToEmbed(data.linkVideo);
setEmbedLink(embed || "");
}
} catch (error) {
console.error('Error loading video:', error);
@@ -47,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,
@@ -55,11 +59,6 @@ function EditVideo() {
deskripsi: formData.deskripsi,
linkVideo: formData.linkVideo,
};
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
if (!converted) {
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
return;
}
await videoState.update.update();
toast.success('Video berhasil diperbarui!');
router.push('/admin/desa/gallery/video');
@@ -80,29 +79,23 @@ 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="Link Video YouTube"
placeholder="https://www.youtube.com/watch?v=abc123"
value={formData.linkVideo}
onChange={(e) => {
setFormData({
...formData,
linkVideo: e.currentTarget.value,
})
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
setEmbedLink(embed || "");
setFormData({ ...formData, linkVideo: e.currentTarget.value });
}}
required
/>
@@ -118,18 +111,17 @@ function EditVideo() {
></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>

View File

@@ -16,7 +16,7 @@ function CreateVideo() {
const videoState = useProxy(stateGallery.video)
const router = useRouter();
const [link, setLink] = useState("");
const [embedLink, setEmbedLink] = useState("");
const embedLink = convertYoutubeUrlToEmbed(link);
const resetForm = () => {
videoState.create.form = {
@@ -26,15 +26,17 @@ function CreateVideo() {
};
};
const handleSubmit = async () => {
const converted = convertYoutubeUrlToEmbed(videoState.create.form.linkVideo);
if (!converted) {
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>
@@ -63,8 +65,6 @@ function CreateVideo() {
value={link}
onChange={(e) => {
setLink(e.currentTarget.value);
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
setEmbedLink(embed || "");
}}
required
/>

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,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;

View File

@@ -1,25 +0,0 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils';
function ListProsedurPendaftaran() {
const prosedurpendaftaranState = useProxy(stateFasilitasKesehatan.prosedurpendaftaran)
useShallowEffect(() => {
prosedurpendaftaranState.findMany.load()
}, [])
if (!prosedurpendaftaranState.findMany.data)return<Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
{prosedurpendaftaranState.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{__html: item.content}}/>
</Box>
))}
</Stack>
}
export default ListProsedurPendaftaran;

View File

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

View File

@@ -1,82 +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 TarifDanLayananList() {
const tarifdanlayanan = useProxy(stateFasilitasKesehatan.tarifdanlayanan)
useShallowEffect(() => {
tarifdanlayanan.findMany.load()
}, [])
if (_.isEmpty(tarifdanlayanan.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
suppressHydrationWarning
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead>
<TableTr>
<TableTh>
Layanan
</TableTh>
<TableTh>
Tarif
</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{tarifdanlayanan.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.layanan}</TableTd>
<TableTd>Rp.{item.tarif}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Stack>
}
export default TarifDanLayananList;

View File

@@ -1,32 +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 TarifDanLayanan() {
const tarifdanlayanan = useProxy(stateFasilitasKesehatan.tarifdanlayanan)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Tarif & Layanan</Text>
<Stack gap={"xs"}>
<TextInput
label="Tarif"
placeholder='masukkan tarif'
onChange={(val) => {
tarifdanlayanan.create.form.tarif = val.target.value
}}
/>
<TextInput
mb={10}
label="Layanan"
placeholder='masukkan layanan'
onChange={(val) => {
tarifdanlayanan.create.form.layanan = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
}
export default TarifDanLayanan;

View File

@@ -1,101 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import stategrafikKepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Skeleton, Stack, TextInput, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
function GrafikHasilKepuasan() {
const grafikkepuasan = useProxy(stategrafikKepuasan.grafikkepuasan)
const [mounted, setMounted] = useState(false);
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [chartData, setChartData] = useState<any[]>([])
useShallowEffect(() => {
const fetchData = async () => {
await grafikkepuasan.findMany.load();
if (grafikkepuasan.findMany.data && grafikkepuasan.findMany.data.length > 0) {
setChartData(grafikkepuasan.findMany.data);
}
};
fetchData();
}, []);
useEffect(() => {
setMounted(true); // setelah komponen siap di client
}, []);
if (!mounted) {
return null; // Jangan render apa-apa dulu sebelum mounted
}
return (
<Stack py={10} gap={"xs"}>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Grafik Hasil Kepuasan</Title>
<Stack gap={"xs"}>
<TextInput
label="Label"
placeholder='Masukkan label yang diinginkan'
value={grafikkepuasan.create.form.label}
onChange={(val) => {
grafikkepuasan.create.form.label = val.currentTarget.value
}}
/>
<TextInput
label="Jumlah Penderita"
type='number'
placeholder='Masukkan jumlah penderita'
value={grafikkepuasan.create.form.jumlah}
onChange={(val) => {
grafikkepuasan.create.form.jumlah = val.currentTarget.value
}}
/>
<Group>
<Button bg={colors['blue-button']} mt={10}
onClick={async () => {
await grafikkepuasan.create.create();
await grafikkepuasan.findMany.load();
if (grafikkepuasan.findMany.data) {
setChartData(grafikkepuasan.findMany.data);
}
}}
>Submit</Button>
</Group>
</Stack>
</Paper>
{!chartData ? (
<Box style={{ width: '100%', minWidth: 300, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title py={15} order={4}>Data Hasil Kepuasan</Title>
<Skeleton h={400} />
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title py={15} order={4}>Data Hasil Kepuasan</Title>
<BarChart
width={isMobile ? 250 : isTablet ? 300 : 350}
height={380}
data={chartData}
>
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
</BarChart>
</Paper>
</Box>
)}
</Stack>
);
}
export default GrafikHasilKepuasan;

View File

@@ -1,124 +0,0 @@
'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */
import statePersentase from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Skeleton, Stack, TextInput, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
function PersentaseDataKelahiranKematian() {
const persentase = useProxy(statePersentase.persentasekelahiran);
const [chartData, setChartData] = useState<any[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
useEffect(() => {
setMounted(true);
}, []);
useShallowEffect(() => {
const fetchData = async () => {
await persentase.findMany.load();
if (persentase.findMany.data && persentase.findMany.data.length > 0) {
setChartData(persentase.findMany.data);
}
};
fetchData();
}, []);
return (
<Stack py={10}>
{/* Form Input */}
<Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Persentase Data Kelahiran & Kematian</Title>
<Stack gap={"xs"}>
<TextInput
label="Tahun"
type="number"
value={persentase.create.form.tahun}
placeholder="Masukkan tahun"
onChange={(val) => {
persentase.create.form.tahun = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Kasar"
type="number"
value={persentase.create.form.kematianKasar}
placeholder="Masukkan kematian kasar"
onChange={(val) => {
persentase.create.form.kematianKasar = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Bayi"
type="number"
value={persentase.create.form.kematianBayi}
placeholder="Masukkan kematian bayi"
onChange={(val) => {
persentase.create.form.kematianBayi = val.currentTarget.value;
}}
/>
<TextInput
label="Kelahiran Kasar"
type="number"
value={persentase.create.form.kelahiranKasar}
placeholder="Masukkan kelahiran kasar"
onChange={(val) => {
persentase.create.form.kelahiranKasar = val.currentTarget.value;
}}
/>
<Group>
<Button
bg={colors['blue-button']}
mt={10}
onClick={async () => {
await persentase.create.create();
await persentase.findMany.load();
if (persentase.findMany.data) {
setChartData(persentase.findMany.data);
}
}}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<Skeleton h={400} />
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
);
}
export default PersentaseDataKelahiranKematian;

View File

@@ -1,10 +1,9 @@
'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';
import { Box, Paper, Text } from '@mantine/core';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { useProxy } from 'valtio/utils';
function DoctorSignUI() {
const doctorSign = useProxy(stateArtikelKesehatan.doctorSign)

View File

@@ -1,10 +1,9 @@
'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';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function FirstAidUI() {
const firstAidState = useProxy(stateArtikelKesehatan.firstAid)

View File

@@ -2,9 +2,9 @@
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';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
function IntoductionUI() {
const introduction = useProxy(stateArtikelKesehatan.introduction)

View File

@@ -169,4 +169,4 @@ function AllList() {
</Stack>
}
export default ArtikelKesehatan;
export default ArtikelKesehatan;

View File

@@ -1,10 +1,9 @@
'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';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function PreventionUI() {
const preventionState = useProxy(stateArtikelKesehatan.prevention)

View File

@@ -1,9 +1,9 @@
'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';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function SymptomUI() {
const symptomState = useProxy(stateArtikelKesehatan.symptom)

View File

@@ -0,0 +1,359 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
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';
interface FasilitasKesehatanFormBase {
name: string;
informasiUmum: {
fasilitas: string;
alamat: string;
jamOperasional: string;
};
layananUnggulan: {
content: string;
};
dokterdanTenagaMedis: {
name: string;
specialist: string;
jadwal: string;
};
fasilitasPendukung: {
content: string;
};
prosedurPendaftaran: {
content: string;
};
tarifDanLayanan: {
layanan: string;
tarif: string;
};
}
function EditFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState<FasilitasKesehatanFormBase>({
name: stateFasilitasKesehatan.edit.form.name || '',
informasiUmum: {
fasilitas: stateFasilitasKesehatan.edit.form.informasiUmum?.fasilitas || '',
alamat: stateFasilitasKesehatan.edit.form.informasiUmum?.alamat || '',
jamOperasional: stateFasilitasKesehatan.edit.form.informasiUmum?.jamOperasional || '',
},
layananUnggulan: {
content: stateFasilitasKesehatan.edit.form.layananUnggulan?.content || '',
},
dokterdanTenagaMedis: {
name: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.name || '',
specialist: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.specialist || '',
jadwal: stateFasilitasKesehatan.edit.form.dokterdanTenagaMedis?.jadwal || '',
},
fasilitasPendukung: {
content: stateFasilitasKesehatan.edit.form.fasilitasPendukung?.content || '',
},
prosedurPendaftaran: {
content: stateFasilitasKesehatan.edit.form.prosedurPendaftaran?.content || '',
},
tarifDanLayanan: {
layanan: stateFasilitasKesehatan.edit.form.tarifDanLayanan?.layanan || '',
tarif: stateFasilitasKesehatan.edit.form.tarifDanLayanan?.tarif || '',
},
});
useEffect(() => {
const loadFasilitasKesehatan = async () => {
const id = params?.id as string;
if (!id) return;
try {
await stateFasilitasKesehatan.edit.load(id);
const { form } = stateFasilitasKesehatan.edit;
if (form) {
setFormData({
name: form.name,
informasiUmum: {
fasilitas: form.informasiUmum?.fasilitas || '',
alamat: form.informasiUmum?.alamat || '',
jamOperasional: form.informasiUmum?.jamOperasional || '',
},
layananUnggulan: {
content: form.layananUnggulan?.content || '',
},
dokterdanTenagaMedis: {
name: form.dokterdanTenagaMedis?.name || '',
specialist: form.dokterdanTenagaMedis?.specialist || '',
jadwal: form.dokterdanTenagaMedis?.jadwal || '',
},
fasilitasPendukung: {
content: form.fasilitasPendukung?.content || '',
},
prosedurPendaftaran: {
content: form.prosedurPendaftaran?.content || '',
},
tarifDanLayanan: {
layanan: form.tarifDanLayanan?.layanan || '',
tarif: form.tarifDanLayanan?.tarif || '',
},
});
}
} catch (error) {
console.error("Error loading fasilitas kesehatan:", error);
toast.error("Gagal memuat data fasilitas kesehatan");
}
};
loadFasilitasKesehatan();
}, [params?.id]);
const handleSubmit = async () => {
try {
stateFasilitasKesehatan.edit.form = {
...stateFasilitasKesehatan.edit.form,
name: formData.name,
informasiUmum: {
fasilitas: formData.informasiUmum.fasilitas,
alamat: formData.informasiUmum.alamat,
jamOperasional: formData.informasiUmum.jamOperasional,
},
layananUnggulan: {
content: formData.layananUnggulan.content,
},
dokterdanTenagaMedis: {
name: formData.dokterdanTenagaMedis.name,
specialist: formData.dokterdanTenagaMedis.specialist,
jadwal: formData.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: {
content: formData.fasilitasPendukung.content,
},
prosedurPendaftaran: {
content: formData.prosedurPendaftaran.content,
},
tarifDanLayanan: {
layanan: formData.tarifDanLayanan.layanan,
tarif: formData.tarifDanLayanan.tarif,
},
};
const success = await stateFasilitasKesehatan.edit.submit();
if (success) {
toast.success("Fasilitas kesehatan berhasil diperbarui!");
router.push("/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan");
}
} catch (error) {
console.error("Error updating fasilitas kesehatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data fasilitas kesehatan");
}
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant="subtle" color="blue">
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Stack gap="xs">
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Edit Fasilitas Kesehatan</Title>
<TextInput
label={<Text fz="sm" fw="bold">Nama Fasilitas Kesehatan</Text>}
placeholder="masukkan nama fasilitas kesehatan"
value={formData.name}
onChange={(e) => {
setFormData(prev => ({
...prev,
name: e.target.value
}));
}}
/>
<Box>
<Text fz="md" fw="bold">Informasi Umum</Text>
<TextInput
label={<Text fz="sm" fw="bold">Fasilitas</Text>}
placeholder="masukkan fasilitas"
value={formData.informasiUmum.fasilitas}
onChange={(e) => {
setFormData(prev => ({
...prev,
informasiUmum: {
...prev.informasiUmum,
fasilitas: e.target.value
}
}));
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Alamat</Text>}
placeholder="masukkan alamat"
value={formData.informasiUmum.alamat}
onChange={(e) => {
setFormData(prev => ({
...prev,
informasiUmum: {
...prev.informasiUmum,
alamat: e.target.value
}
}));
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Jam Operasional</Text>}
placeholder="masukkan jam operasional"
value={formData.informasiUmum.jamOperasional}
onChange={(e) => {
setFormData(prev => ({
...prev,
informasiUmum: {
...prev.informasiUmum,
jamOperasional: e.target.value
}
}));
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Layanan Unggulan</Text>
<EditEditor
value={formData.layananUnggulan.content}
onChange={(e) => {
setFormData(prev => ({
...prev,
layananUnggulan: {
...prev.layananUnggulan,
content: e
}
}));
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Dokter dan Tenaga Medis</Text>
<TextInput
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
placeholder="masukkan nama dokter"
value={formData.dokterdanTenagaMedis.name}
onChange={(e) => {
setFormData(prev => ({
...prev,
dokterdanTenagaMedis: {
...prev.dokterdanTenagaMedis,
name: e.target.value
}
}));
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Specialist</Text>}
placeholder="masukkan specialist"
value={formData.dokterdanTenagaMedis.specialist}
onChange={(e) => {
setFormData(prev => ({
...prev,
dokterdanTenagaMedis: {
...prev.dokterdanTenagaMedis,
specialist: e.target.value
}
}));
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Jadwal</Text>}
placeholder="masukkan jadwal"
value={formData.dokterdanTenagaMedis.jadwal}
onChange={(e) => {
setFormData(prev => ({
...prev,
dokterdanTenagaMedis: {
...prev.dokterdanTenagaMedis,
jadwal: e.target.value
}
}));
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Fasilitas Pendukung</Text>
<EditEditor
value={formData.fasilitasPendukung.content}
onChange={(e) => {
setFormData(prev => ({
...prev,
fasilitasPendukung: {
...prev.fasilitasPendukung,
content: e
}
}));
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Prosedur Pendaftaran</Text>
<EditEditor
value={formData.prosedurPendaftaran.content}
onChange={(e) => {
setFormData(prev => ({
...prev,
prosedurPendaftaran: {
...prev.prosedurPendaftaran,
content: e
}
}));
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Tarif dan Layanan</Text>
<TextInput
label={<Text fz="sm" fw="bold">Tarif</Text>}
placeholder="masukkan tarif"
value={formData.tarifDanLayanan.tarif}
onChange={(e) => {
setFormData(prev => ({
...prev,
tarifDanLayanan: {
...prev.tarifDanLayanan,
tarif: e.target.value
}
}));
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Layanan</Text>}
placeholder="masukkan layanan"
value={formData.tarifDanLayanan.layanan}
onChange={(e) => {
setFormData(prev => ({
...prev,
tarifDanLayanan: {
...prev.tarifDanLayanan,
layanan: e.target.value
}
}));
}}
/>
</Box>
<Button
onClick={handleSubmit}
bg={colors['blue-button']}
loading={stateFasilitasKesehatan.edit.loading}
>
Simpan
</Button>
</Stack>
</Paper>
</Stack>
</Box>
);
}
export default EditFasilitasKesehatan;

View File

@@ -0,0 +1,127 @@
'use client'
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
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 DetailFasilitasKesehatan() {
const params = useParams()
const router = useRouter();
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null)
useShallowEffect(() => {
stateFasilitasKesehatan.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
stateFasilitasKesehatan.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan")
}
}
if (!stateFasilitasKesehatan.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 w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
{stateFasilitasKesehatan.findUnique.data ? (
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fz={"lg"} fw={"bold"}>Nama Fasilitas Kesehatan</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.name}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Informasi Umum</Text>
<Text fz={"md"} fw={"bold"}>Fasilitas</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.fasilitas}</Text>
<Text fz={"md"} fw={"bold"}>Alamat</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.alamat}</Text>
<Text fz={"md"} fw={"bold"}>Jam Operasional</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.informasiumum.jamOperasional}</Text>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.name}</Text>
<Text fz={"md"} fw={"bold"}>Specialist</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.specialist}</Text>
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
<Text fz={"md"} fw={"bold"}>Layanan</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.tarifdanlayanan.layanan}</Text>
<Text fz={"md"} fw={"bold"}>Tarif</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.tarifdanlayanan.tarif}</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red" onClick={() => {
if (stateFasilitasKesehatan.findUnique.data) {
setSelectedId(stateFasilitasKesehatan.findUnique.data.id)
setModalHapus(true)
}
}}>
<IconX size={20} />
</Button>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${stateFasilitasKesehatan.findUnique.data?.id}/edit`)} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus fasilitas kesehatan ini?"
/>
</Box>
);
}
export default DetailFasilitasKesehatan;

View File

@@ -0,0 +1,187 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
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 { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const router = useRouter();
const resetForm = () => {
stateFasilitasKesehatan.create.form = {
name: "",
informasiUmum: {
fasilitas: "",
alamat: "",
jamOperasional: "",
},
layananUnggulan: {
content: "",
},
dokterdanTenagaMedis: {
name: "",
specialist: "",
jadwal: "",
},
fasilitasPendukung: {
content: "",
},
prosedurPendaftaran: {
content: "",
},
tarifDanLayanan: {
layanan: "",
tarif: "",
},
};
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await stateFasilitasKesehatan.create.submit();
toast.success("Data berhasil disimpan");
resetForm();
// After successful submission, redirect to the list page
router.push('/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan');
}
return (
<Box component="form" onSubmit={handleSubmit}>
<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 Fasilitas Kesehatan</Title>
<TextInput
label={<Text fz="sm" fw="bold">Nama Fasilitas Kesehatan</Text>}
placeholder="masukkan nama fasilitas kesehatan"
value={stateFasilitasKesehatan.create.form.name}
onChange={(e) => {
stateFasilitasKesehatan.create.form.name = e.target.value;
}}
/>
<Box>
<Text fz="md" fw="bold">Informasi Umum</Text>
<TextInput
label={<Text fz="sm" fw="bold">Fasilitas</Text>}
placeholder="masukkan fasilitas"
value={stateFasilitasKesehatan.create.form.informasiUmum.fasilitas}
onChange={(e) => {
stateFasilitasKesehatan.create.form.informasiUmum.fasilitas = e.target.value;
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Alamat</Text>}
placeholder="masukkan alamat"
value={stateFasilitasKesehatan.create.form.informasiUmum.alamat}
onChange={(e) => {
stateFasilitasKesehatan.create.form.informasiUmum.alamat = e.target.value;
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Jam Operasional</Text>}
placeholder="masukkan jam operasional"
value={stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional}
onChange={(e) => {
stateFasilitasKesehatan.create.form.informasiUmum.jamOperasional = e.target.value;
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Layanan Unggulan</Text>
<CreateEditor
value={stateFasilitasKesehatan.create.form.layananUnggulan.content}
onChange={(e) => {
stateFasilitasKesehatan.create.form.layananUnggulan.content = e;
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Dokter dan Tenaga Medis</Text>
<TextInput
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
placeholder="masukkan nama dokter"
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name}
onChange={(e) => {
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.name = e.target.value;
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Specialist</Text>}
placeholder="masukkan specialist"
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist}
onChange={(e) => {
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.specialist = e.target.value;
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Jadwal</Text>}
placeholder="masukkan jadwal"
value={stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal}
onChange={(e) => {
stateFasilitasKesehatan.create.form.dokterdanTenagaMedis.jadwal = e.target.value;
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Fasilitas Pendukung</Text>
<CreateEditor
value={stateFasilitasKesehatan.create.form.fasilitasPendukung.content}
onChange={(e) => {
stateFasilitasKesehatan.create.form.fasilitasPendukung.content = e;
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Prosedur Pendaftaran</Text>
<CreateEditor
value={stateFasilitasKesehatan.create.form.prosedurPendaftaran.content}
onChange={(e) => {
stateFasilitasKesehatan.create.form.prosedurPendaftaran.content = e;
}}
/>
</Box>
<Box>
<Text fz="md" fw="bold">Tarif dan Layanan</Text>
<TextInput
label={<Text fz="sm" fw="bold">Tarif</Text>}
placeholder="masukkan tarif"
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif}
onChange={(e) => {
stateFasilitasKesehatan.create.form.tarifDanLayanan.tarif = e.target.value;
}}
/>
<TextInput
label={<Text fz="sm" fw="bold">Layanan</Text>}
placeholder="masukkan layanan"
value={stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan}
onChange={(e) => {
stateFasilitasKesehatan.create.form.tarifDanLayanan.layanan = e.target.value;
}}
/>
</Box>
<Button onClick={handleSubmit} bg={colors['blue-button']}>
Simpan
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreateFasilitasKesehatan;

View File

@@ -0,0 +1,81 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
function FasilitasKesehatan() {
return (
<Box>
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
/>
<ListFasilitasKesehatan/>
</Box>
);
}
function ListFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const router = useRouter();
useShallowEffect(() => {
stateFasilitasKesehatan.findMany.load()
}, [])
if (!stateFasilitasKesehatan.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500}/>
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{stateFasilitasKesehatan.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.informasiumum.alamat}</TableTd>
<TableTd>{item.informasiumum.jamOperasional}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
export default FasilitasKesehatan;

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function EditGrafikHasilKepuasan() {
const router = useRouter()
const params = useParams() as { uuid: string }
const stateGrafikKepuasan = useProxy(grafikkepuasan)
const uuid = params.uuid
// Load data saat komponen mount
useEffect(() => {
if (uuid) {
stateGrafikKepuasan.findUnique.load(uuid).then(() => {
const data = stateGrafikKepuasan.findUnique.data
if (data) {
stateGrafikKepuasan.update.form = {
label: data.label || '',
jumlah: data.jumlah || '',
}
}
})
}
}, [uuid])
const handleSubmit = async () => {
// Set the ID before submitting
stateGrafikKepuasan.update.uuid = uuid;
await stateGrafikKepuasan.update.submit();
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan')
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Edit Grafik Hasil Kepuasan</Title>
<TextInput
label="Label"
placeholder="masukkan label"
value={stateGrafikKepuasan.update.form.label}
onChange={(val) => {
stateGrafikKepuasan.update.form.label = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
placeholder="masukkan jumlah"
value={stateGrafikKepuasan.update.form.jumlah}
onChange={(val) => {
stateGrafikKepuasan.update.form.jumlah = val.currentTarget.value;
}}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Simpan
</Button>
</Stack>
</Paper>
</Box>
)
}
export default EditGrafikHasilKepuasan;

View File

@@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function CreateGrafikHasilKepuasanMasyarakat() {
const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter()
const resetForm = () => {
stateGrafikKepuasan.create.form = {
label: "",
jumlah: "",
}
}
const handleSubmit = async () => {
const id = await stateGrafikKepuasan.create.create();
if (id) {
const idStr = String(id);
await stateGrafikKepuasan.findUnique.load(idStr);
if (stateGrafikKepuasan.findUnique.data) {
setChartData([stateGrafikKepuasan.findUnique.data]);
}
}
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
<Stack gap={"xs"}>
<TextInput
label="Label"
type="text"
value={stateGrafikKepuasan.create.form.label}
placeholder="Masukkan label"
onChange={(val) => {
stateGrafikKepuasan.create.form.label = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
value={stateGrafikKepuasan.create.form.jumlah}
placeholder="Masukkan jumlah"
onChange={(val) => {
stateGrafikKepuasan.create.form.jumlah = val.currentTarget.value;
}}
/>
<Group>
<Button
bg={colors['blue-button']}
mt={10}
onClick={handleSubmit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
</Box>
);
}
export default CreateGrafikHasilKepuasanMasyarakat;

View File

@@ -0,0 +1,148 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
function GrafikHasilKepuasanMasyarakat() {
type PDKMGrafik = {
id: string;
label: string;
jumlah: number;
}
const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const handleDelete = () => {
if (selectedId) {
stateGrafikKepuasan.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateGrafikKepuasan.findMany.load()
}
}
useShallowEffect(() => {
setMounted(true)
stateGrafikKepuasan.findMany.load()
}, [])
useEffect(() => {
setMounted(true);
if (stateGrafikKepuasan.findMany.data) {
setChartData(stateGrafikKepuasan.findMany.data.map((item) => ({
id: item.uuid,
label: item.label,
jumlah: Number(item.jumlah),
})));
}
}, [stateGrafikKepuasan.findMany.data]);
if (!stateGrafikKepuasan.findMany.data) {
return (
<Stack>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Stack gap={"xs"}>
{/* Form Input */}
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Grafik Hasil Kepuasan Masyarakat'
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Label</TableTh>
<TableTh>Jumlah</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{stateGrafikKepuasan.findMany.data?.map((item) => (
<TableTr key={item.uuid}>
<TableTd>{item.label}</TableTd>
<TableTd>{item.jumlah}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.uuid}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={stateGrafikKepuasan.delete.loading}
onClick={() => {
setSelectedId(item.uuid)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
/>
</Box>
);
}
export default GrafikHasilKepuasanMasyarakat;

View File

@@ -1,8 +1,9 @@
'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function DeskripsiKegiatan() {
const deskripsiKegiatanState = useProxy(stateJadwalKegiatan.deskripsiKegiatan)

View File

@@ -1,8 +1,9 @@
'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function DokumenYangDiperlukan() {
const dokumenDiperlukan = useProxy(stateJadwalKegiatan.dokumenjadwalkegiatan)

View File

@@ -1,3 +1,4 @@
'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, TextInput, Title } from '@mantine/core';

View File

@@ -1,8 +1,9 @@
'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import colors from '@/con/colors';
import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
function LayananTersedia() {
const layananTersediaState = useProxy(stateJadwalKegiatan.layanantersedia)

View File

@@ -1,8 +1,9 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
'use client'
import colors from '@/con/colors';
import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
import { KesehatanEditor } from '../../_com/kesehatanEditor';
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
function SyaratDanKetentuan() {
const syaratKetentuan = useProxy(stateJadwalKegiatan.syaratketentuan)

View File

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

View File

@@ -1,54 +0,0 @@
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import ArtikelKesehatan from './_ui/artikel_kesehatan/page';
import FasilitasKesehatan from './_ui/fasilitas_kesehatan/page';
import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page';
import JadwalKegiatan from './_ui/jadwal_kegiatan/page';
import PersentaseDataKelahiranKematian from './_ui/persentase_data_kelahiran_kematian/page';
function Page() {
return (
<Stack>
<Title order={3}>Data Kesehatan Warga</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Persentase Kelahiran & Kematian"}>
<TabsList bg={colors['BG-trans']} p={'xs'}>
<TabsTab value="Persentase Kelahiran & Kematian">
Persentase Kelahiran & Kematian
</TabsTab>
<TabsTab value="Grafik Hasil Kepuasan">
Grafik Hasil Kepuasan
</TabsTab>
<TabsTab value="Fasilitas Kesehatan">
Fasilitas Kesehatan
</TabsTab>
<TabsTab value="Jadwal Kegiatan">
Jadwal Kegiatan
</TabsTab>
<TabsTab value="Artikel Kesehatan">
Artikel Kesehatan
</TabsTab>
</TabsList>
<TabsPanel value="Persentase Kelahiran & Kematian">
<PersentaseDataKelahiranKematian />
</TabsPanel>
<TabsPanel value="Grafik Hasil Kepuasan">
<GrafikHasilKepuasan/>
</TabsPanel>
<TabsPanel value="Fasilitas Kesehatan">
<FasilitasKesehatan/>
</TabsPanel>
<TabsPanel value="Jadwal Kegiatan">
<JadwalKegiatan/>
</TabsPanel>
<TabsPanel value="Artikel Kesehatan">
<ArtikelKesehatan/>
</TabsPanel>
</Tabs>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,114 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditPersentaseDataKelahiranKematian() {
const router = useRouter()
const params = useParams() as { uuid: string }
const statePresentase = useProxy(persentasekelahiran)
const id = params.uuid
// Load data saat komponen mount
// Di file page.tsx, ubah useEffect-nya menjadi:
useEffect(() => {
if (!id) return;
statePresentase.update.uuid = id;
statePresentase.findUnique.load(id)
.then(() => {
const data = statePresentase.findUnique.data;
if (data) {
statePresentase.update.form = {
tahun: String(data.tahun || ''),
kematianKasar: String(data.kematianKasar || ''),
kelahiranKasar: String(data.kelahiranKasar || ''),
kematianBayi: String(data.kematianBayi || '')
};
}
})
.catch(error => {
console.error('Error loading data:', error);
toast.error('Gagal memuat data');
});
}, [id]);
// Di handleSubmit, ubah menjadi:
const handleSubmit = async () => {
try {
statePresentase.update.uuid = id;
await statePresentase.update.submit();
toast.success('Data berhasil diperbarui');
router.push('/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian');
} catch (error) {
console.error('Error updating data:', error);
toast.error('Gagal memperbarui data');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Edit Persentase Data Kelahiran & Kematian</Title>
<TextInput
label="Tahun"
placeholder="masukkan tahun"
value={statePresentase.update.form.tahun}
onChange={(val) => {
statePresentase.update.form.tahun = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Kasar"
type="number"
placeholder="masukkan kematian kasar"
value={statePresentase.update.form.kematianKasar}
onChange={(val) => {
statePresentase.update.form.kematianKasar = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Bayi"
type="number"
placeholder="masukkan kematian bayi"
value={statePresentase.update.form.kematianBayi}
onChange={(val) => {
statePresentase.update.form.kematianBayi = val.currentTarget.value;
}}
/>
<TextInput
label="Kelahiran Kasar"
type="number"
placeholder="masukkan kelahiran kasar"
value={statePresentase.update.form.kelahiranKasar}
onChange={(val) => {
statePresentase.update.form.kelahiranKasar = val.currentTarget.value;
}}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Simpan Perubahan
</Button>
</Stack>
</Paper>
</Box>
)
}
export default EditPersentaseDataKelahiranKematian;

View File

@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function CreatePersentaseDataKelahiranKematian() {
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter()
const resetForm = () => {
statePersentase.create.form = {
tahun: "",
kematianBayi: "",
kematianKasar: "",
kelahiranKasar: "",
}
}
const handleSubmit = async () => {
const id = await statePersentase.create.create();
if (id) {
const idStr = String(id);
await statePersentase.findUnique.load(idStr);
if (statePersentase.findUnique.data) {
setChartData([statePersentase.findUnique.data]);
}
}
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian");
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Persentase Data Kelahiran & Kematian</Title>
<Stack gap={"xs"}>
<TextInput
label="Tahun"
type="number"
value={statePersentase.create.form.tahun}
placeholder="Masukkan tahun"
onChange={(val) => {
statePersentase.create.form.tahun = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Kasar"
type="number"
value={statePersentase.create.form.kematianKasar}
placeholder="Masukkan kematian kasar"
onChange={(val) => {
statePersentase.create.form.kematianKasar = val.currentTarget.value;
}}
/>
<TextInput
label="Kematian Bayi"
type="number"
value={statePersentase.create.form.kematianBayi}
placeholder="Masukkan kematian bayi"
onChange={(val) => {
statePersentase.create.form.kematianBayi = val.currentTarget.value;
}}
/>
<TextInput
label="Kelahiran Kasar"
type="number"
value={statePersentase.create.form.kelahiranKasar}
placeholder="Masukkan kelahiran kasar"
onChange={(val) => {
statePersentase.create.form.kelahiranKasar = val.currentTarget.value;
}}
/>
<Group>
<Button
bg={colors['blue-button']}
mt={10}
onClick={handleSubmit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
</Box>
);
}
export default CreatePersentaseDataKelahiranKematian;

View File

@@ -0,0 +1,158 @@
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function PersentaseDataKelahiranKematian() {
type PDKMGrafik = {
uuid: string;
tahun: string;
kematianKasar: number;
kematianBayi: number;
kelahiranKasar: number;
}
const statePersentase = useProxy(persentasekelahiran);
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const handleDelete = () => {
if (selectedId) {
statePersentase.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
statePersentase.findMany.load()
}
}
useShallowEffect(() => {
setMounted(true)
statePersentase.findMany.load()
}, [])
useEffect(() => {
setMounted(true);
if (statePersentase.findMany.data) {
setChartData(statePersentase.findMany.data.map((item) => ({
uuid: item.uuid,
tahun: item.tahun,
kematianKasar: Number(item.kematianKasar),
kematianBayi: Number(item.kematianBayi),
kelahiranKasar: Number(item.kelahiranKasar),
})));
}
}, [statePersentase.findMany.data]);
if (!statePersentase.findMany.data) {
return (
<Stack>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Stack gap={"xs"}>
{/* Form Input */}
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Persentase Data Kelahiran & Kematian'
href='/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Tahun</TableTh>
<TableTh>Kematian Kasar</TableTh>
<TableTh>Kematian Bayi</TableTh>
<TableTh>kelahiran Kasar</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{statePersentase.findMany.data?.map((item) => (
<TableTr key={item.uuid}>
<TableTd>{item.tahun}</TableTd>
<TableTd>{item.kematianKasar}</TableTd>
<TableTd>{item.kematianBayi}</TableTd>
<TableTd>{item.kelahiranKasar}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/${item.uuid}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={statePersentase.delete.loading}
onClick={() => {
setSelectedId(item.uuid)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus persentase data kelahiran & kematian ini?'
/>
</Box>
);
}
export default PersentaseDataKelahiranKematian;

View File

@@ -0,0 +1,150 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditInfoWabahPenyakit() {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
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: infoWabahPenyakitState.edit.form.name || '',
deskripsiSingkat: infoWabahPenyakitState.edit.form.deskripsiSingkat || '',
deskripsi: infoWabahPenyakitState.edit.form.deskripsiLengkap || '',
imageId: infoWabahPenyakitState.edit.form.imageId || '',
})
useEffect(() => {
const loadInfoWabahPenyakit = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await infoWabahPenyakitState.edit.load(id);
if (data) {
setFormData({
name: data.name || '',
deskripsiSingkat: data.deskripsiSingkat || '',
deskripsi: data.deskripsiLengkap || '',
imageId: data.imageId || '',
});
if (data?.image?.link) {
setPreviewImage(data.image.link);
}
}
} catch (error) {
console.error("Error loading program kesehatan:", error);
toast.error("Gagal memuat data program kesehatan");
}
};
loadInfoWabahPenyakit();
}, [params?.id]);
const handleSubmit = async () => {
try {
infoWabahPenyakitState.edit.form = {
...infoWabahPenyakitState.edit.form,
name: formData.name,
deskripsiSingkat: formData.deskripsiSingkat,
deskripsiLengkap: 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");
}
infoWabahPenyakitState.edit.form.imageId = uploaded.id;
}
await infoWabahPenyakitState.edit.update();
toast.success("Info wabah penyakit berhasil diperbarui!");
router.push("/admin/kesehatan/info-wabah-penyakit");
} catch (error) {
console.error("Error updating info wabah penyakit:", error);
toast.error("Gagal memuat data info wabah penyakit");
}
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Edit Info Wabah Penyakit</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.deskripsiSingkat}
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
placeholder="masukkan deskripsi"
/>
<Box>
<Text fz="sm" fw="bold">Deskripsi</Text>
<EditEditor
value={formData.deskripsi}
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
/>
</Box>
<FileInput
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg="gray">
<IconImageInPicture />
</Center>
)}
<Button onClick={handleSubmit} bg={colors['blue-button']}>
Simpan
</Button>
</Stack>
</Paper>
</Stack>
</Box >
);
}
export default EditInfoWabahPenyakit;

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