diff --git a/bun.lockb b/bun.lockb index 775d4131..347d9ab5 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index c9a52ec1..a23894c8 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,9 @@ "@mantine/carousel": "^7.16.2", "@mantine/charts": "^7.17.1", "@mantine/core": "^7.17.4", - "@mantine/dates": "^7.17.4", - "@mantine/dropzone": "^7.17.0", + "@mantine/dates": "^8.1.0", + "@mantine/dropzone": "^8.1.1", + "@mantine/form": "^8.1.0", "@mantine/hooks": "^7.17.4", "@mantine/tiptap": "^7.17.4", "@paljs/types": "^8.1.0", diff --git a/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json b/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json new file mode 100644 index 00000000..2259bea4 --- /dev/null +++ b/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json @@ -0,0 +1,7 @@ +[ + { + "id": "1", + "name": "Pelayanan Penduduk Non-Permanent", + "deskripsi": "

Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.

" + } +] \ No newline at end of file diff --git a/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json b/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json new file mode 100644 index 00000000..8df36cf0 --- /dev/null +++ b/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json @@ -0,0 +1,8 @@ +[ + { + "id": "1", + "name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)", + "deskripsi": "

Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.

", + "link" : "https://oss.go.id/" + } +] \ No newline at end of file diff --git a/prisma/data/desa/profile/lambang_desa.json b/prisma/data/desa/profile/lambang_desa.json new file mode 100644 index 00000000..7827289a --- /dev/null +++ b/prisma/data/desa/profile/lambang_desa.json @@ -0,0 +1,7 @@ +[ + { + "id": "edit", + "judul": "Lambang Desa", + "deskripsi" : "" + } +] \ No newline at end of file diff --git a/prisma/data/desa/profile/maskot_desa.json b/prisma/data/desa/profile/maskot_desa.json new file mode 100644 index 00000000..b405ff8f --- /dev/null +++ b/prisma/data/desa/profile/maskot_desa.json @@ -0,0 +1,7 @@ +[ + { + "id": "edit", + "judul": "Maskot Desa", + "deskripsi" : "

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.

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.

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.

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.

Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).

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.

" + } +] \ No newline at end of file diff --git a/prisma/data/desa/profile/profil_perbekel.json b/prisma/data/desa/profile/profil_perbekel.json index 3f846693..efaf0021 100644 --- a/prisma/data/desa/profile/profil_perbekel.json +++ b/prisma/data/desa/profile/profil_perbekel.json @@ -1,6 +1,6 @@ [ { - "id": "1", + "id": "edit", "biodata": "

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.

", "pengalaman": "", "pengalamanOrganisasi": "", diff --git a/prisma/data/desa/profile/profile_desa.json b/prisma/data/desa/profile/profile_desa.json deleted file mode 100644 index 29ed4d8a..00000000 --- a/prisma/data/desa/profile/profile_desa.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "id": "1", - "sejarah" : "

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.

", - "visi" : "

Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana

", - "misi" : "", - "lambang" : "", - "maskot" : "

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.

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.

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.

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.

Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).

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.

", - "profilPerbekelId" : "1" - } -] \ No newline at end of file diff --git a/prisma/data/desa/profile/sejarah_desa.json b/prisma/data/desa/profile/sejarah_desa.json new file mode 100644 index 00000000..347194e5 --- /dev/null +++ b/prisma/data/desa/profile/sejarah_desa.json @@ -0,0 +1,7 @@ +[ + { + "id": "edit", + "judul": "Sejarah Desa", + "deskripsi": "

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.

" + } +] \ No newline at end of file diff --git a/prisma/data/desa/profile/visi_misi_desa.json b/prisma/data/desa/profile/visi_misi_desa.json new file mode 100644 index 00000000..5fd0b7bc --- /dev/null +++ b/prisma/data/desa/profile/visi_misi_desa.json @@ -0,0 +1,7 @@ +[ + { + "id" : "edit", + "visi" : "

Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana

", + "misi" : "" + } +] \ No newline at end of file diff --git a/prisma/data/ppid/profile-ppid/profilePPid.json b/prisma/data/ppid/profile-ppid/profilePPid.json index 3727382c..0c6828f7 100644 --- a/prisma/data/ppid/profile-ppid/profilePPid.json +++ b/prisma/data/ppid/profile-ppid/profilePPid.json @@ -1,11 +1,10 @@ [ { - "id": "1", + "id": "edit", "name": "I.B Surya Prabhawa Manuaba, S.H., M.H.", "biodata": "

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.

", "riwayat": "", "pengalaman": "", - "unggulan": "

Pemberdayaan Ekonomi dan UMKM

", - "imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png" + "unggulan": "

Pemberdayaan Ekonomi dan UMKM

" } ] diff --git a/prisma/data/ppid/struktur-ppid/strukturPPID.json b/prisma/data/ppid/struktur-ppid/strukturPPID.json new file mode 100644 index 00000000..cdc2ff14 --- /dev/null +++ b/prisma/data/ppid/struktur-ppid/strukturPPID.json @@ -0,0 +1,6 @@ +[ + { + "id" : "1", + "name" : "Struktur PPID" + } +] \ No newline at end of file diff --git a/prisma/migrations/20250616155255_16_jun/migration.sql b/prisma/migrations/20250616155255_16_jun/migration.sql new file mode 100644 index 00000000..fff7b159 --- /dev/null +++ b/prisma/migrations/20250616155255_16_jun/migration.sql @@ -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; diff --git a/prisma/migrations/20250617083234_17jun/migration.sql b/prisma/migrations/20250617083234_17jun/migration.sql new file mode 100644 index 00000000..2ff042ca --- /dev/null +++ b/prisma/migrations/20250617083234_17jun/migration.sql @@ -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; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 31430ab9..187b4e07 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -50,21 +50,42 @@ 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[] + 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[] } //========================================= MENU PPID ========================================= // + +//========================================= STRUKTUR PPID ========================================= // +model StrukturPPID { + id String @id @default(cuid()) + name 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) +} + // ========================================= VISI MISI PPID ========================================= // model VisiMisiPPID { id String @id @default(cuid()) @@ -89,23 +110,23 @@ model DasarHukumPPID { // ========================================= PROFILE PPID ========================================= // model ProfilePPID { - id String @id @default(cuid()) - name String @db.Text - biodata String @db.Text - riwayat String @db.Text - pengalaman String @db.Text - unggulan String @db.Text - imageUrl String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + id String @id @default(cuid()) + name String @db.Text + biodata String @db.Text + riwayat String @db.Text + pengalaman String @db.Text + unggulan String @db.Text + image FileStorage? @relation(fields: [imageId], references: [id]) + imageId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } // ========================================= DAFTAR INFORMASI PUBLIK ========================================= // model DaftarInformasiPublik { id String @id @default(cuid()) - nomor Int @default(autoincrement()) jenisInformasi String deskripsi String tanggal String @@ -181,7 +202,7 @@ model FormulirPermohonanKeberatan { // ========================================= IKM ========================================= // model IndeksKepuasanMasyarakat { - id Int @id @default(autoincrement()) + id String @id @default(cuid()) label String kepuasan String createdAt DateTime @default(now()) @@ -226,32 +247,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 ========================================= // @@ -319,51 +376,87 @@ model CategoryPengumuman { isActive Boolean @default(true) } -// ========================================= IMAGES ========================================= // -model Images { - id String @id @default(cuid()) - url String - label String @default("null") - active Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - GalleryFoto GalleryFoto[] -} - -// ========================================= VIDEOS ========================================= // -model Videos { - id String @id @default(cuid()) - url String - label String @default("null") - active Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - GalleryVideo GalleryVideo[] -} - // ========================================= GALLERY ========================================= // model GalleryFoto { - id String @id @default(cuid()) + id String @id @default(cuid()) name String - image String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - imagesId String? @unique - imageGalleryFoto Images? @relation(fields: [imagesId], references: [id]) + deskripsi String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + imagesId String? @unique + imageGalleryFoto FileStorage? @relation(fields: [imagesId], references: [id]) } model GalleryVideo { - id String @id @default(cuid()) - name String - video String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - videosId String? @unique - videosGalleryVideo Videos? @relation(fields: [videosId], references: [id]) + id String @id @default(cuid()) + name String + deskripsi String @db.Text + linkVideo String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= LAYANAN DESA ========================================= // +model PelayananSuratKeterangan { + id String @id @default(cuid()) + name String + deskripsi String @db.Text + image FileStorage @relation(fields: [imageId], references: [id]) + imageId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model PelayananTelunjukSaktiDesa { + id String @id @default(cuid()) + name String + deskripsi String @db.Text + link String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model PelayananPerizinanBerusaha { + id String @id @default(cuid()) + name String + deskripsi String @db.Text + link String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model PelayananPendudukNonPermanen { + id String @id @default(cuid()) + name String + deskripsi String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= PENGHARGAAN ========================================= // +model Penghargaan { + id String @id @default(cuid()) + name String + juara String + deskripsi String @db.Text + image FileStorage @relation(fields: [imageId], references: [id]) + imageId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } // ========================================= MENU KESEHATAN ========================================= // @@ -615,3 +708,17 @@ model DoctorSign { deletedAt DateTime @default(now()) isActive Boolean @default(true) } + +// ========================================= POSYANDU ========================================= // +model Posyandu { + id String @id @default(cuid()) + name String + nomor 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) +} diff --git a/prisma/seed.ts b/prisma/seed.ts index e3fe12c8..b6b9067b 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -6,13 +6,17 @@ import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInf import jenisInformasiDiminta from "./data/list-jenisInfromasi.json"; import layanan from "./data/list-layanan.json"; import potensi from "./data/list-potensi.json"; -import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json"; import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json"; import profilePPID from "./data/ppid/profile-ppid/profilePPid.json"; -import path from "path"; -import fs from "fs"; -import { mkdir, writeFile } from "fs/promises"; -import { v4 as uuid } from "uuid"; +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) { @@ -31,6 +35,159 @@ import { v4 as uuid } from "uuid"; console.log("layanan success ..."); + for (const l of sejarahDesa) { + await prisma.sejarahDesa.upsert({ + where: { + id: l.id, + }, + update: { + judul: l.judul, + deskripsi: l.deskripsi, + }, + create: { + id: l.id, + judul: l.judul, + deskripsi: l.deskripsi, + }, + }); + } + + console.log("sejarah desa success ..."); + + for (const l of maskotDesa) { + await prisma.maskotDesa.upsert({ + where: { + id: l.id, + }, + update: { + judul: l.judul, + deskripsi: l.deskripsi, + }, + create: { + id: l.id, + judul: l.judul, + deskripsi: l.deskripsi, + }, + }); + } + + console.log("maskot desa success ..."); + + for (const l of lambangDesa) { + await prisma.lambangDesa.upsert({ + where: { + id: l.id, + }, + update: { + judul: l.judul, + deskripsi: l.deskripsi, + }, + create: { + id: l.id, + judul: l.judul, + deskripsi: l.deskripsi, + }, + }); + } + + console.log("lambang desa success ..."); + + for (const c of profilPerbekel) { + await prisma.profilPerbekel.upsert({ + where: { id: c.id }, + update: { + biodata: c.biodata, + pengalaman: c.pengalaman, + pengalamanOrganisasi: c.pengalamanOrganisasi, + programUnggulan: c.programUnggulan, + // imageId tidak di-update + }, + create: { + id: c.id, + biodata: c.biodata, + pengalaman: c.pengalaman, + pengalamanOrganisasi: c.pengalamanOrganisasi, + programUnggulan: c.programUnggulan, + // imageId tidak di-create + }, + }); + } + console.log("✅ profilePerbekel seeded without imageId (editable later via UI)"); + + for (const l of visiMisiDesa) { + await prisma.visiMisiDesa.upsert({ + where: { + id: l.id, + }, + update: { + visi: l.visi, + misi: l.misi, + }, + create: { + id: l.id, + visi: l.visi, + misi: l.misi, + }, + }); + } + + console.log("visi misi desa success ..."); + + for (const l of pelayananPerizinanBerusaha) { + await prisma.pelayananPerizinanBerusaha.upsert({ + where: { + id: l.id, + }, + update: { + name: l.name, + deskripsi: l.deskripsi, + link: l.link, + }, + create: { + id: l.id, + name: l.name, + deskripsi: l.deskripsi, + link: l.link, + }, + }); + } + + console.log("pelayanan perizinan berusaha success ..."); + + for (const l of pelayananPendudukNonPermanen) { + await prisma.pelayananPendudukNonPermanen.upsert({ + where: { + id: l.id, + }, + update: { + name: l.name, + deskripsi: l.deskripsi, + }, + create: { + id: l.id, + name: l.name, + deskripsi: l.deskripsi, + }, + }); + } + console.log("pelayanan penduduk non permanen success ..."); + + for (const s of strukturPPID) { + await prisma.strukturPPID.upsert({ + where: { + id: s.id, + }, + update: { + name: s.name, + }, + create: { + id: s.id, + name: s.name, + }, + }); + } + console.log("struktur ppid success ..."); + for (const p of potensi) { await prisma.potensi.upsert({ where: { @@ -124,56 +281,29 @@ import { v4 as uuid } from "uuid"; } console.log("cara memperoleh salinan informasi success ..."); - const seedProfilePPID = async () => { - const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid") - - // Buat folder hanya jika belum ada - if (!fs.existsSync(targetDir)) { - await mkdir(targetDir, { recursive: true }) - } - - for (const c of profilePPID) { - let finalImageUrl = c.imageUrl - - if (c.imageUrl.startsWith("/uploads/seeded-images/")) { - const filename = path.basename(c.imageUrl) - const seedImagePath = path.resolve("prisma", "seed-images", filename) - - const targetFilename = `${uuid()}_${filename}` - const targetPath = path.join(targetDir, targetFilename) - - const buffer = fs.readFileSync(seedImagePath) - await writeFile(targetPath, buffer) - - finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}` - } - - await prisma.profilePPID.upsert({ - where: { id: c.id }, - update: { - name: c.name, - biodata: c.biodata, - riwayat: c.riwayat, - pengalaman: c.pengalaman, - unggulan: c.unggulan, - imageUrl: finalImageUrl, - }, - create: { - id: c.id, - name: c.name, - biodata: c.biodata, - riwayat: c.riwayat, - pengalaman: c.pengalaman, - unggulan: c.unggulan, - imageUrl: finalImageUrl, - }, - }) - } - - console.log("✅ profilePPID seeded from JSON with image copying") + for (const c of profilePPID) { + await prisma.profilePPID.upsert({ + where: { id: c.id }, + update: { + name: c.name, + biodata: c.biodata, + riwayat: c.riwayat, + pengalaman: c.pengalaman, + unggulan: c.unggulan, + // imageId tidak di-update + }, + create: { + id: c.id, + name: c.name, + biodata: c.biodata, + riwayat: c.riwayat, + pengalaman: c.pengalaman, + unggulan: c.unggulan, + // imageId tidak di-create + }, + }); } - - await seedProfilePPID() + console.log("✅ profilePPID seeded without imageId (editable later via UI)"); for (const v of visiMisiPPID) { await prisma.visiMisiPPID.upsert({ @@ -210,8 +340,6 @@ import { v4 as uuid } from "uuid"; }); } console.log("dasar hukum PPID success ..."); - - })() .then(() => prisma.$disconnect()) .catch((e) => { diff --git a/public/Share.png b/public/Share.png new file mode 100644 index 00000000..82ffa19d Binary files /dev/null and b/public/Share.png differ diff --git a/public/bagikanPostingan.png b/public/bagikanPostingan.png new file mode 100644 index 00000000..72826cbf Binary files /dev/null and b/public/bagikanPostingan.png differ diff --git a/public/bungapudak.png b/public/bungapudak.png new file mode 100644 index 00000000..e35e2875 Binary files /dev/null and b/public/bungapudak.png differ diff --git a/uploads/image/darmasaba-icon.png b/public/darmasaba-icon.png similarity index 100% rename from uploads/image/darmasaba-icon.png rename to public/darmasaba-icon.png diff --git a/public/klimakstari.png b/public/klimakstari.png new file mode 100644 index 00000000..e668ffdf Binary files /dev/null and b/public/klimakstari.png differ diff --git a/public/pohonpudak.png b/public/pohonpudak.png new file mode 100644 index 00000000..0782bbc7 Binary files /dev/null and b/public/pohonpudak.png differ diff --git a/uploads/image/pudak-icon.png b/public/pudak-icon.png similarity index 100% rename from uploads/image/pudak-icon.png rename to public/pudak-icon.png diff --git a/public/sematkan.png b/public/sematkan.png new file mode 100644 index 00000000..9ce93c24 Binary files /dev/null and b/public/sematkan.png differ diff --git a/public/struktur_ppid.png b/public/struktur_ppid.png new file mode 100644 index 00000000..5124ac1e Binary files /dev/null and b/public/struktur_ppid.png differ diff --git a/public/tarisekar.png b/public/tarisekar.png new file mode 100644 index 00000000..77dcc5d3 Binary files /dev/null and b/public/tarisekar.png differ diff --git a/public/uploads/seeded-images/profile-ppid/2513dd3c-256d-4f15-9e35-75e9819acdf4_perbekel.png b/public/uploads/seeded-images/profile-ppid/2513dd3c-256d-4f15-9e35-75e9819acdf4_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/2513dd3c-256d-4f15-9e35-75e9819acdf4_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/2ee01665-bf74-44b8-b851-c71b00007436_perbekel.png b/public/uploads/seeded-images/profile-ppid/2ee01665-bf74-44b8-b851-c71b00007436_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/2ee01665-bf74-44b8-b851-c71b00007436_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/82fd837b-fd2d-440e-8207-35c65f1022c7_perbekel.png b/public/uploads/seeded-images/profile-ppid/82fd837b-fd2d-440e-8207-35c65f1022c7_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/82fd837b-fd2d-440e-8207-35c65f1022c7_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/9e891f51-e57c-4d5d-8b38-87022e88b0dd_perbekel.png b/public/uploads/seeded-images/profile-ppid/9e891f51-e57c-4d5d-8b38-87022e88b0dd_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/9e891f51-e57c-4d5d-8b38-87022e88b0dd_perbekel.png differ diff --git a/public/video.png b/public/video.png new file mode 100644 index 00000000..3c144bc0 Binary files /dev/null and b/public/video.png differ diff --git a/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx b/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx index ccedf0f0..db1e64af 100644 --- a/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx +++ b/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx @@ -21,7 +21,7 @@ export function ModalKonfirmasiHapus({ Konfirmasi Hapus} centered > {text} diff --git a/src/app/admin/(dashboard)/_state/desa/gallery.ts b/src/app/admin/(dashboard)/_state/desa/gallery.ts new file mode 100644 index 00000000..3405a6ba --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/gallery.ts @@ -0,0 +1,415 @@ +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 fotoForm = z.object({ + name: z.string().min(1, { message: "Name is required" }), + deskripsi: z.string().min(1, { message: "Deskripsi is required" }), + imagesId: z.string().nonempty(), +}); + +const videoForm = z.object({ + name: z.string().min(1, { message: "Name is required" }), + deskripsi: z.string().min(1, { message: "Deskripsi is required" }), + linkVideo: z.string().min(1, { message: "Link video is required" }), +}); + +const defaultFormFoto = { + name: "", + deskripsi: "", + imagesId: "", +}; + +const defaultFormVideo = { + name: "", + deskripsi: "", + linkVideo: "", +}; + +const foto = proxy({ + create: { + form: { ...defaultFormFoto }, + loading: false, + async create() { + const cek = fotoForm.safeParse(foto.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + foto.create.loading = true; + const res = await ApiFetch.api.desa.gallery.foto["create"].post( + foto.create.form + ); + if (res.status === 200) { + foto.findMany.load(); + return toast.success("Foto berhasil disimpan!"); + } + return toast.error("Gagal menyimpan foto"); + } catch (error) { + console.log((error as Error).message); + } finally { + foto.create.loading = false; + } + }, + resetForm() { + foto.create.form = { ...defaultFormFoto }; + }, + }, + findMany: { + data: null as + | Prisma.GalleryFotoGetPayload<{ + include: { + imageGalleryFoto: true; + }; + }>[] + | null, + async load() { + const res = await ApiFetch.api.desa.gallery.foto["find-many"].get(); + if (res.status === 200) { + foto.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.GalleryFotoGetPayload<{ + include: { + imageGalleryFoto: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/gallery/foto/${id}`); + if (res.ok) { + const data = await res.json(); + foto.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch foto:", res.statusText); + foto.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching foto:", error); + foto.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + foto.delete.loading = true; + const response = await fetch(`/api/desa/gallery/foto/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Foto berhasil dihapus"); + await foto.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus foto"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus foto"); + } finally { + foto.delete.loading = false; + } + }, + }, + update: { + id: "", + form: { ...defaultFormFoto }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch(`/api/desa/gallery/foto/${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, + imagesId: data.imagesId || "", + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading foto:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = fotoForm.safeParse(foto.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + try { + foto.update.loading = true; + const response = await fetch(`/api/desa/gallery/foto/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + imagesId: this.form.imagesId, + }), + }); + 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 || "Foto berhasil diupdate"); + await foto.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate foto"); + } + } catch (error) { + console.error("Error updating foto:", error); + toast.error( + error instanceof Error ? error.message : "Gagal mengupdate foto" + ); + return false; + } finally { + foto.update.loading = false; + } + }, + reset() { + foto.update.id = ""; + foto.update.form = { ...defaultFormFoto }; + }, + }, +}); + +const video = proxy({ + create: { + form: { ...defaultFormVideo }, + loading: false, + async create() { + const cek = videoForm.safeParse(video.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + video.create.loading = true; + const res = await ApiFetch.api.desa.gallery.video["create"].post( + video.create.form + ); + if (res.status === 200) { + video.findMany.load(); + return toast.success("Video berhasil disimpan!"); + } + return toast.error("Gagal menyimpan video"); + } catch (error) { + console.log((error as Error).message); + } finally { + video.create.loading = false; + } + }, + resetForm() { + video.create.form = { ...defaultFormVideo }; + }, + }, + findMany: { + data: null as + | Prisma.GalleryVideoGetPayload<{ + omit: { + isActive: true; + }; + }>[] + | null, + async load() { + const res = await ApiFetch.api.desa.gallery.video["find-many"].get(); + if (res.status === 200) { + video.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.GalleryVideoGetPayload<{ + omit: { + isActive: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/gallery/video/${id}`); + if (res.ok) { + const data = await res.json(); + video.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch video:", res.statusText); + video.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching video:", error); + video.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + video.delete.loading = true; + const response = await fetch(`/api/desa/gallery/video/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Video berhasil dihapus"); + await video.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus video"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus video"); + } finally { + video.delete.loading = false; + } + }, + }, + update: { + id: "", + form: { ...defaultFormVideo }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch(`/api/desa/gallery/video/${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, + linkVideo: data.linkVideo, + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading video:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = videoForm.safeParse(video.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + try { + video.update.loading = true; + const response = await fetch(`/api/desa/gallery/video/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + linkVideo: this.form.linkVideo, + }), + }); + 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 || "Video berhasil diupdate"); + await video.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate video"); + } + } catch (error) { + console.error("Error updating video:", error); + toast.error( + error instanceof Error ? error.message : "Gagal mengupdate video" + ); + return false; + } finally { + video.update.loading = false; + } + }, + reset() { + video.update.id = ""; + video.update.form = { ...defaultFormVideo }; + }, + }, +}); + +const stateGallery = proxy({ + foto, + video, +}); + +export default stateGallery; diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts new file mode 100644 index 00000000..558d4672 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts @@ -0,0 +1,718 @@ +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 templateSuratKeteranganForm = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + imageId: z.string().nonempty(), +}); + +const suratKeteranganForm = { + name: "", + deskripsi: "", + imageId: "", +}; + +const telunjukSaktiDesaForm = { + name: "", + deskripsi: "", + link: "", +}; + +const templateTelunjukSaktiDesaForm = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), +}); + + +const templatePelayananPerizinanBerusaha = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + link: z.string().min(3, "Link minimal 3 karakter"), +}); + +type pelayananPerizinanBerusahaForm = + Prisma.PelayananPerizinanBerusahaGetPayload<{ + select: { + id: true; + name: true; + deskripsi: true; + link: true; + }; + }>; + +const pelayananPerizinanBerusahaForm = { + name: "", + deskripsi: "", + link: "", +}; + +const templatePelayananPendudukNonPermanen = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), +}); + +type pelayananPendudukNonPermanenForm = + Prisma.PelayananPendudukNonPermanenGetPayload<{ + select: { + id: true; + name: true; + deskripsi: true; + }; + }>; + +const pelayananPendudukNonPermanenForm = { + name: "", + deskripsi: "", +}; + + +const suratKeterangan = proxy({ + create: { + form: { ...suratKeteranganForm }, + loading: false, + async create() { + const cek = templateSuratKeteranganForm.safeParse( + suratKeterangan.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + suratKeterangan.create.loading = true; + const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ + "create" + ].post(suratKeterangan.create.form); + if (res.status === 200) { + suratKeterangan.findMany.load(); + return toast.success("Surat Keterangan berhasil disimpan!"); + } + return toast.error("Gagal menyimpan surat keterangan"); + } catch (error) { + console.log((error as Error).message); + } finally { + suratKeterangan.create.loading = false; + } + }, + resetForm() { + suratKeterangan.create.form = { ...suratKeteranganForm }; + }, + }, + findMany: { + data: [] as Prisma.PelayananSuratKeteranganGetPayload<{ + include: { image: true }; + }>[], + async load() { + const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ + "find-many" + ].get(); + if (res.status === 200) { + suratKeterangan.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PelayananSuratKeteranganGetPayload<{ + include: { + image: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/${id}` + ); + if (res.ok) { + const data = await res.json(); + suratKeterangan.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch surat keterangan:", res.statusText); + suratKeterangan.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching surat keterangan:", error); + suratKeterangan.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + suratKeterangan.delete.loading = true; + const response = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Surat Keterangan berhasil dihapus"); + await suratKeterangan.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus surat keterangan"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus surat keterangan"); + } finally { + suratKeterangan.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...suratKeteranganForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/${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; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching surat keterangan:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = templateSuratKeteranganForm.safeParse( + suratKeterangan.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + suratKeterangan.edit.loading = true; + const response = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + imageId: this.form.imageId, + }), + } + ); + 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 || "Surat Keterangan berhasil diupdate"); + await suratKeterangan.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal mengupdate surat keterangan" + ); + } + } catch (error) { + console.error("Error updating surat keterangan:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update surat keterangan" + ); + return false; + } finally { + suratKeterangan.edit.loading = false; + } + }, + }, +}); + +const pelayananTelunjukSaktiDesa = proxy({ + create: { + form: { ...telunjukSaktiDesaForm }, + loading: false, + async create() { + const cek = templateTelunjukSaktiDesaForm.safeParse( + pelayananTelunjukSaktiDesa.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + pelayananTelunjukSaktiDesa.create.loading = true; + const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ + "create" + ].post(pelayananTelunjukSaktiDesa.create.form); + if (res.status === 200) { + pelayananTelunjukSaktiDesa.findMany.load(); + return toast.success("Telunjuk Sakti Desa berhasil disimpan!"); + } + return toast.error("Gagal menyimpan telunjuk sakti desa"); + } catch (error) { + console.log((error as Error).message); + } finally { + pelayananTelunjukSaktiDesa.create.loading = false; + } + }, + resetForm() { + pelayananTelunjukSaktiDesa.create.form = { ...telunjukSaktiDesaForm }; + }, + }, + findMany: { + data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{ + omit: { isActive: true }; + }>[], + async load() { + const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ + "find-many" + ].get(); + if (res.status === 200) { + pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PelayananTelunjukSaktiDesaGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/desa/layanan/pelayanantelunjuksaktidesa/${id}` + ); + if (res.ok) { + const data = await res.json(); + pelayananTelunjukSaktiDesa.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch telunjuk sakti desa:", res.statusText); + pelayananTelunjukSaktiDesa.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching telunjuk sakti desa:", error); + pelayananTelunjukSaktiDesa.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + pelayananTelunjukSaktiDesa.delete.loading = true; + const response = await fetch( + `/api/desa/layanan/pelayanantelunjuksaktidesa/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus"); + await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus telunjuk sakti desa"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus telunjuk sakti desa"); + } finally { + pelayananTelunjukSaktiDesa.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...telunjukSaktiDesaForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch( + `/api/desa/layanan/pelayanantelunjuksaktidesa/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + name: data.name, + deskripsi: data.deskripsi, + link: data.link, + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching telunjuk sakti desa:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = templateTelunjukSaktiDesaForm.safeParse( + pelayananTelunjukSaktiDesa.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + pelayananTelunjukSaktiDesa.edit.loading = true; + const response = await fetch( + `/api/desa/layanan/pelayanantelunjuksaktidesa/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + deskripsi: this.form.deskripsi, + link: this.form.link, + }), + } + ); + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + const result = await response.json(); + if (result.success) { + toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate"); + await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal mengupdate telunjuk sakti desa" + ); + } + } catch (error) { + console.error("Error updating telunjuk sakti desa:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update telunjuk sakti desa" + ); + return false; + } finally { + pelayananTelunjukSaktiDesa.edit.loading = false; + } + }, + }, +}) + +const pelayananPerizinanBerusaha = proxy({ + findById: { + data: null as pelayananPerizinanBerusahaForm | null, + loading: false, + initialize() { + pelayananPerizinanBerusaha.findById.data = { + id: "", + name: "", + deskripsi: "", + link: "", + } as pelayananPerizinanBerusahaForm; + }, + async load(id: string) { + try { + pelayananPerizinanBerusaha.findById.loading = true; + const res = await fetch( + `/api/desa/layanan/pelayananperizinanberusaha/${id}` + ); + if (res.ok) { + const data = await res.json(); + pelayananPerizinanBerusaha.findById.data = data.data ?? null; + } else { + console.error( + "Failed to fetch pelayanan perizinan berusaha:", + res.statusText + ); + pelayananPerizinanBerusaha.findById.data = null; + } + } catch (error) { + console.error("Error fetching pelayanan perizinan berusaha:", error); + pelayananPerizinanBerusaha.findById.data = null; + } + }, + }, + update: { + id: "", + form: { ...pelayananPerizinanBerusahaForm }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak boleh kosong"); + return null; + } + try { + const response = await fetch( + `/api/desa/layanan/pelayananperizinanberusaha/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + pelayananPerizinanBerusaha.update.id = data.id; + pelayananPerizinanBerusaha.update.form = { + name: data.name, + deskripsi: data.deskripsi, + link: data.link, + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching pelayanan perizinan berusaha:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal memuat data" + ); + return null; + } + }, + async update(data: pelayananPerizinanBerusahaForm) { + const cek = templatePelayananPerizinanBerusaha.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + + try { + pelayananPerizinanBerusaha.update.loading = true; + const res = await fetch( + `/api/desa/layanan/pelayananperizinanberusaha/${data.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + } + ); + if (res.ok) { + toast.success("Pelayanan perizinan berusaha berhasil diupdate"); + await pelayananPerizinanBerusaha.findById.load(data.id); + } else { + toast.error("Gagal mengupdate pelayanan perizinan berusaha"); + } + } catch (error) { + console.error("Error updating pelayanan perizinan berusaha:", error); + toast.error( + "Terjadi kesalahan saat mengupdate pelayanan perizinan berusaha" + ); + } finally { + pelayananPerizinanBerusaha.update.loading = false; + } + }, + }, +}); + +const pelayananPendudukNonPermanen = proxy({ + findById: { + data: null as pelayananPendudukNonPermanenForm | null, + loading: false, + initialize() { + pelayananPendudukNonPermanen.findById.data = { + id: "", + name: "", + deskripsi: "", + } as pelayananPendudukNonPermanenForm; + }, + async load(id: string) { + try { + pelayananPendudukNonPermanen.findById.loading = true; + const res = await fetch( + `/api/desa/layanan/pelayananpenduduknonpermanen/${id}` + ); + if (res.ok) { + const data = await res.json(); + pelayananPendudukNonPermanen.findById.data = data.data ?? null; + } else { + console.error( + "Failed to fetch pelayanan penduduk non permanen:", + res.statusText + ); + pelayananPendudukNonPermanen.findById.data = null; + } + } catch (error) { + console.error("Error fetching pelayanan penduduk non permanen:", error); + pelayananPendudukNonPermanen.findById.data = null; + } + }, + }, + update: { + id: "", + form: { ...pelayananPendudukNonPermanenForm }, + loading: false, + async load(id: string) { + if (!id) { + toast.warn("ID tidak boleh kosong"); + return null; + } + try { + const response = await fetch( + `/api/desa/layanan/pelayananpenduduknonpermanen/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + pelayananPendudukNonPermanen.update.id = data.id; + pelayananPendudukNonPermanen.update.form = { + name: data.name, + deskripsi: data.deskripsi, + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching pelayanan penduduk non permanen:", error); + toast.error( + error instanceof Error + ? error.message + : "Gagal memuat data" + ); + return null; + } + }, + async update(data: pelayananPendudukNonPermanenForm) { + const cek = templatePelayananPendudukNonPermanen.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + + try { + pelayananPendudukNonPermanen.update.loading = true; + const res = await fetch( + `/api/desa/layanan/pelayananpenduduknonpermanen/${data.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + } + ); + if (res.ok) { + toast.success("Pelayanan penduduk non permanen berhasil diupdate"); + await pelayananPendudukNonPermanen.findById.load(data.id); + } else { + toast.error("Gagal mengupdate pelayanan penduduk non permanen"); + } + } catch (error) { + console.error("Error updating pelayanan penduduk non permanen:", error); + toast.error( + "Terjadi kesalahan saat mengupdate pelayanan penduduk non permanen" + ); + } finally { + pelayananPendudukNonPermanen.update.loading = false; + } + }, + }, +}); + +const stateLayananDesa = proxy({ + suratKeterangan, + pelayananPerizinanBerusaha, + pelayananTelunjukSaktiDesa, + pelayananPendudukNonPermanen, +}); + +export default stateLayananDesa; diff --git a/src/app/admin/(dashboard)/_state/desa/penghargaan.ts b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts new file mode 100644 index 00000000..2545ae1a --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts @@ -0,0 +1,221 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +const templateForm = z.object({ + name: z.string().min(1).max(50), + juara: z.string().min(1).max(50), + deskripsi: z.string().min(1).max(5000), + imageId: z.string().min(1).max(50), +}); + +const defaultForm = { + name: "", + juara: "", + deskripsi: "", + imageId: "", +}; + +const penghargaanState = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = templateForm.safeParse(penghargaanState.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + penghargaanState.create.loading = true; + const res = await ApiFetch.api.desa.penghargaan["create"].post( + penghargaanState.create.form + ); + if (res.status === 200) { + penghargaanState.findMany.load(); + return toast.success("success create"); + } + console.log(res); + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + penghargaanState.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.PenghargaanGetPayload<{ + include: { + image: true; + }; + }>[] + | null, + async load() { + const res = await ApiFetch.api.desa.penghargaan["find-many"].get(); + if (res.status === 200) { + penghargaanState.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PenghargaanGetPayload<{ + include: { + image: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/desa/penghargaan/${id}`); + if (res.ok) { + const data = await res.json(); + penghargaanState.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + penghargaanState.findUnique.data = null; + } + } catch (error) { + console.error("Error loading penghargaan:", error); + penghargaanState.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + penghargaanState.delete.loading = true; + const response = await fetch(`/api/desa/penghargaan/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const result = await response.json(); + + if (response.ok) { + toast.success(result.message || "Penghargaan berhasil dihapus"); + await penghargaanState.findMany.load(); + } else { + toast.error(result?.message || "Gagal menghapus penghargaan"); + } + } catch (error) { + console.log((error as Error).message); + toast.error("Terjadi kesalahan saat menghapus penghargaan"); + } finally { + penghargaanState.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...defaultForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/desa/penghargaan/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + name: data.name, + juara: data.juara, + deskripsi: data.deskripsi, + imageId: data.imageId, + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading penghargaan:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = templateForm.safeParse(penghargaanState.edit.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + penghargaanState.edit.loading = true; + const response = await fetch( + `/api/desa/penghargaan/${penghargaanState.edit.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + juara: this.form.juara, + deskripsi: this.form.deskripsi, + imageId: this.form.imageId, + }), + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + const result = await response.json(); + if (result.success) { + toast.success("Berhasil update penghargaan"); + await penghargaanState.findMany.load(); + return true; + } else { + throw new Error(result?.message || "Gagal update penghargaan"); + } + } catch (error) { + console.error("Error updating penghargaan:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update penghargaan" + ); + return false; + } finally { + penghargaanState.edit.loading = false; + } + }, + reset() { + penghargaanState.edit.id = ""; + penghargaanState.edit.form = { ...defaultForm }; + }, + }, +}); +export default penghargaanState; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts index 31987b33..91b4e311 100644 --- a/src/app/admin/(dashboard)/_state/desa/pengumuman.ts +++ b/src/app/admin/(dashboard)/_state/desa/pengumuman.ts @@ -1,83 +1,246 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import ApiFetch from "@/lib/api-fetch" -import { Prisma } from "@prisma/client" -import { toast } from "react-toastify" -import { proxy } from "valtio" -import { z } from "zod" +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; const templateFormPengumuman = z.object({ - judul: z.string().min(3, "Judul minimal 3 karakter"), - deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), - content: z.string().min(3, "Content minimal 3 karakter"), - categoryPengumumanId: z.string().nonempty(), -}) + judul: z.string().min(3, "Judul minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + content: z.string().min(3, "Content minimal 3 karakter"), + categoryPengumumanId: z.string().nonempty(), +}); -const category = proxy ({ - findMany: { - data: null as - | null - | Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[], - async load() { - const res = await ApiFetch.api.desa.pengumuman.category["find-many"].get(); - if (res.status === 200) { - category.findMany.data = res.data?.data as any ?? []; - } - } - } -}) +const category = proxy({ + findMany: { + data: null as + | null + | Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[], + async load() { + const res = await ApiFetch.api.desa.pengumuman.category[ + "find-many" + ].get(); + if (res.status === 200) { + category.findMany.data = (res.data?.data as any) ?? []; + } + }, + }, +}); type PengumumanForm = Prisma.PengumumanGetPayload<{ - select: { - judul: true; - deskripsi: true; - content: true; - categoryPengumumanId: true; - } -}> + select: { + judul: true; + deskripsi: true; + content: true; + categoryPengumumanId: true; + }; +}>; const pengumuman = proxy({ -create: { + create: { form: {} as PengumumanForm, loading: false, async create() { - const cek = templateFormPengumuman.safeParse(pengumuman.create.form); - if (!cek.success) { + const cek = templateFormPengumuman.safeParse(pengumuman.create.form); + if (!cek.success) { const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; return toast.error(err); - } - try { + } + try { pengumuman.create.loading = true; - const res = await ApiFetch.api.desa.pengumuman["create"].post(pengumuman.create.form) + const res = await ApiFetch.api.desa.pengumuman["create"].post( + pengumuman.create.form + ); if (res.status === 200) { - pengumuman.findMany.load(); - return toast.success("success create"); + pengumuman.findMany.load(); + return toast.success("success create"); } - console.log(res) + console.log(res); return toast.error("failed create"); - } catch (error) { + } catch (error) { console.log((error as Error).message); - } finally{ + } finally { pengumuman.create.loading = false; - } - } -}, -findMany: { + } + }, + }, + findMany: { data: null as - | Prisma.PengumumanGetPayload<{omit: {isActive: true}}>[] - | null, - async load () { - const res = await ApiFetch.api.desa.pengumuman["find-many"].get(); - console.log(res) - if (res.status === 200) { - pengumuman.findMany.data = res.data?.data ?? []; + | Prisma.PengumumanGetPayload<{ + include: { + CategoryPengumuman: true; } - } -} -}) + }>[] + | null, + async load() { + const res = await ApiFetch.api.desa.pengumuman["find-many"].get(); + console.log(res); + if (res.status === 200) { + pengumuman.findMany.data = res.data?.data ?? []; + } + }, + }, + // findUnique: { + // data: null as + // | Prisma.PengumumanGetPayload<{ + // include: { + // CategoryPengumuman: true; + // } + // }> + // | null, + // async load(id: string) { + // try { + // const res = await fetch(`/api/desa/pengumuman/${id}`); + // if (res.ok) { + // const data = await res.json(); + // pengumuman.findUnique.data = data.data ?? null; + // } else { + // console.error('Failed to fetch pengumuman:', res.statusText); + // pengumuman.findUnique.data = null; + // } + // } catch (error) { + // console.error('Error fetching pengumuman:', error); + // pengumuman.findUnique.data = null; + // } + // }, + // }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + pengumuman.delete.loading = true; + + const response = await fetch(`/api/desa/pengumuman/delete/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Pengumuman berhasil dihapus"); + await pengumuman.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus pengumuman"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus pengumuman"); + } finally { + pengumuman.delete.loading = false; + } + }, + }, + update: { + id: "", + form: {} as PengumumanForm, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + const response = await fetch(`/api/desa/pengumuman/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + judul: data.judul, + deskripsi: data.deskripsi, + content: data.content, + categoryPengumumanId: data.categoryPengumumanId || "", + }; + return data; + } else { + throw new Error(result?.message || "Gagal mengambil data pengumuman"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data pengumuman"); + } finally { + pengumuman.update.loading = false; + } + }, + + async update() { + const cek = templateFormPengumuman.safeParse(pengumuman.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + pengumuman.update.loading = true; + + const response = await fetch(`/api/desa/pengumuman/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + judul: this.form.judul, + deskripsi: this.form.deskripsi, + content: this.form.content, + categoryPengumumanId: this.form.categoryPengumumanId, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update pengumuman"); + await pengumuman.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal update pengumuman"); + } + } catch (error) { + console.error("Error updating pengumuman:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update pengumuman" + ); + return false; + } finally { + pengumuman.update.loading = false; + } + }, + }, +}); const stateDesaPengumuman = proxy({ - category, - pengumuman -}) -export default stateDesaPengumuman \ No newline at end of file + category, + pengumuman, +}); +export default stateDesaPengumuman; diff --git a/src/app/admin/(dashboard)/_state/desa/potensi.ts b/src/app/admin/(dashboard)/_state/desa/potensi.ts index c61ec4f1..91e898a0 100644 --- a/src/app/admin/(dashboard)/_state/desa/potensi.ts +++ b/src/app/admin/(dashboard)/_state/desa/potensi.ts @@ -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), diff --git a/src/app/admin/(dashboard)/_state/desa/profile.ts b/src/app/admin/(dashboard)/_state/desa/profile.ts index 744b426d..07142d72 100644 --- a/src/app/admin/(dashboard)/_state/desa/profile.ts +++ b/src/app/admin/(dashboard)/_state/desa/profile.ts @@ -1,239 +1,829 @@ -import ApiFetch from "@/lib/api-fetch"; -import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; import { proxy } from "valtio"; import { z } from "zod"; +import { Prisma } from "@prisma/client"; -/* Sejarah */ -const templateFormSejarahForm = z.object({ - sejarah: z.string().min(3, "Sejarah minimal 3 karakter"), -}) - -type SejarahForm = Prisma.ProfileDesaGetPayload<{ - select: { - id: true; - sejarah: true; - } -}> - -const Sejarah = proxy({ - findById: { - data: null as SejarahForm | null, - loading: false, - initialize() { - Sejarah.findById.data = { - id: "", - sejarah: "", - } as SejarahForm; - }, - async load(id: string) { - try { - Sejarah.findById.loading = true; - const res = await ApiFetch.api.desa.profile["find-by-id"].get({ - query: { id }, - }); - if (res.status === 200) { - Sejarah.findById.data = { - id: id, - sejarah: res.data?.data?.sejarah ?? "" - }; - } else { - toast.error("Gagal mengambil data sejarah"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat mengambil data sejarah"); - } finally { - Sejarah.findById.loading = false; - } - } - }, - update: { - loading: false, - async save(data: SejarahForm) { - const cek = templateFormSejarahForm.safeParse(data); - if (!cek.success) { - const errors = cek.error.issues - .map((issue) => `${issue.path.join(".")}: ${issue.message}`) - .join(", "); - toast.error(`Form tidak valid: ${errors}`); - return; - } - try { - Sejarah.update.loading = true; - const res = await ApiFetch.api.desa.profile.sejarah["update"].post(data); - if (res.status === 200) { - toast.success("Berhasil update sejarah"); - await Sejarah.findById.load(data.id); - } else { - toast.error("Gagal update sejarah"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat update sejarah"); - } finally { - Sejarah.update.loading = false; - } - } - } -}) - -/* Visi Misi Desa */ -const templateFormVisiForm = z.object({ - visi: z.string().min(3, "Visi minimal 3 karakter"), - misi: z.string().min(3, "Misi minimal 3 karakter") -}) - -type VisiMisiDesaForm = Prisma.ProfileDesaGetPayload<{ - select: { - id: true; - visi: true; - misi: true; - } -}> - -const VisiMisiDesa = proxy({ - findById: { - data: null as VisiMisiDesaForm | null, - loading: false, - initialize() { - VisiMisiDesa.findById.data = { - id: "", - visi: "", - misi: "" - } as VisiMisiDesaForm; - }, - async load(id: string) { - try { - VisiMisiDesa.findById.loading = true; - const res = await ApiFetch.api.desa.profile["find-by-id"].get({ - query: { id }, - }); - if (res.status === 200) { - VisiMisiDesa.findById.data = { - id: id, - visi: res.data?.data?.visi ?? "", - misi: res.data?.data?.misi ?? "" - }; - } else { - toast.error("Gagal mengambil data visi misi"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat mengambil data visi misi"); - } finally { - VisiMisiDesa.findById.loading = false; - } - } - }, - update: { - loading: false, - async save(data: VisiMisiDesaForm) { - const cek = templateFormVisiForm.safeParse(data); - if (!cek.success) { - const errors = cek.error.issues - .map((issue) => `${issue.path.join(".")}: ${issue.message}`) - .join(", "); - toast.error(`Form tidak valid: ${errors}`); - return; - } - try { - VisiMisiDesa.update.loading = true; - const res = await ApiFetch.api.desa.profile.visimisiDesa["update"].post(data); - if (res.status === 200) { - toast.success("Berhasil update visi misi"); - await VisiMisiDesa.findById.load(data.id); - } else { - toast.error("Gagal update visi"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat update visi misi"); - } finally { - VisiMisiDesa.update.loading = false; - } - } - } -}) -/* Lambang Desa */ -const templateFormLambangDesaForm = z.object({ - lambang: z.string().min(3, "Lambang minimal 3 karakter"), -}) - -type LambangDesaForm = Prisma.ProfileDesaGetPayload<{ - select: { - id: true; - lambang: true; - } -}> - -const LambangDesa = proxy({ - findById: { - data: null as LambangDesaForm | null, - loading: false, - initialize() { - LambangDesa.findById.data = { - id: "", - lambang: "", - } as LambangDesaForm; - }, - async load(id: string) { - try { - LambangDesa.findById.loading = true; - const res = await ApiFetch.api.desa.profile["find-by-id"].get({ - query: { id }, - }); - if (res.status === 200) { - LambangDesa.findById.data = { - id: id, - lambang: res.data?.data?.lambang ?? "" - }; - } else { - toast.error("Gagal mengambil data lambang desa"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat mengambil data lambang desa"); - } finally { - LambangDesa.findById.loading = false; - } - } - }, - update: { - loading: false, - async save(data: LambangDesaForm) { - const cek = templateFormLambangDesaForm.safeParse(data); - if (!cek.success) { - const errors = cek.error.issues - .map((issue) => `${issue.path.join(".")}: ${issue.message}`) - .join(", "); - toast.error(`Form tidak valid: ${errors}`); - return; - } - try { - LambangDesa.update.loading = true; - const res = await ApiFetch.api.desa.profile.lambangDesa["update"].post(data); - if (res.status === 200) { - toast.success("Berhasil update lambang desa"); - await LambangDesa.findById.load(data.id); - } else { - toast.error("Gagal update lambang desa"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat update lambang desa"); - } finally { - LambangDesa.update.loading = false; - } - } - } +// ========================================= SEJARAH DESA ========================================= // +const sejarahDesaForm = z.object({ + judul: z.string().min(3, "Judul minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), }); -const stateProfileDesa = { - Sejarah, - VisiMisiDesa, - LambangDesa, +const sejarahDesaDefaultForm = { + judul: "", + deskripsi: "", }; +type SejarahDesaForm = Prisma.SejarahDesaGetPayload<{ + select: { + id: true; + judul: true; + deskripsi: true; + }; +}>; + +const sejarahDesa = proxy({ + findUnique: { + data: null as SejarahDesaForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/sejarah/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + + if (result.success) { + this.data = result.data; + return result.data; + } else { + throw new Error( + result.message || "Gagal mengambil data sejarah desa" + ); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + console.error("Load sejarah desa error:", msg); + toast.error("Terjadi kesalahan saat mengambil data sejarah desa"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + }, + }, + update: { + id: "", + form: { ...sejarahDesaDefaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(sejarahData: SejarahDesaForm) { + this.id = sejarahData.id; + this.isReadOnly = false; + this.form = { + judul: sejarahData.judul || "", + deskripsi: sejarahData.deskripsi || "", + }; + }, + + updateField(field: keyof typeof sejarahDesaDefaultForm, value: string) { + this.form[field] = value; + }, + + async submit() { + // Validate form + const validation = sejarahDesaForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch( + `/api/desa/profile/sejarah/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update profile"); + // Refresh profile data + await sejarahDesa.findUnique.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update profile"); + } + } catch (error) { + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Update profile error:", errorMessage); + toast.error("Terjadi kesalahan saat update profile"); + return false; + } finally { + this.loading = false; + } + }, + + // Reset form + reset() { + this.id = ""; + this.form = { ...sejarahDesaDefaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + }, + }, +}); + +// ========================================= VISI MISI DESA ========================================= // +const visiMisiDesaForm = z.object({ + visi: z.string().min(3, "Visi minimal 3 karakter"), + misi: z.string().min(3, "Misi minimal 3 karakter"), +}); + +const visiMisiDesaDefaultForm = { + visi: "", + misi: "", +}; + +type VisiMisiDesaForm = Prisma.VisiMisiDesaGetPayload<{ + select: { + id: true; + visi: true; + misi: true; + }; +}>; + +const visiMisiDesa = proxy({ + findUnique: { + data: null as VisiMisiDesaForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/visi-misi/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + + if (result.success) { + this.data = result.data; + return result.data; + } else { + throw new Error( + result.message || "Gagal mengambil data visi misi desa" + ); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + console.error("Load visi misi desa error:", msg); + toast.error("Terjadi kesalahan saat mengambil data visi misi desa"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + }, + }, + update: { + id: "", + form: { ...visiMisiDesaDefaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(visiMisiData: VisiMisiDesaForm) { + this.id = visiMisiData.id; + this.isReadOnly = false; + this.form = { + visi: visiMisiData.visi || "", + misi: visiMisiData.misi || "", + }; + }, + + updateField(field: keyof typeof visiMisiDesaDefaultForm, value: string) { + this.form[field] = value; + }, + + async submit() { + // Validate form + const validation = visiMisiDesaForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/visi-misi/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update visi misi desa"); + // Refresh profile data + await visiMisiDesa.findUnique.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update visi misi desa"); + } + } catch (error) { + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Update visi misi desa error:", errorMessage); + toast.error("Terjadi kesalahan saat update visi misi desa"); + return false; + } finally { + this.loading = false; + } + }, + + // Reset form + reset() { + this.id = ""; + this.form = { ...visiMisiDesaDefaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + }, + }, +}); + +// ========================================= LAMBANG DESA ========================================= // +const lambangDesaForm = z.object({ + judul: z.string().min(3, "Judul minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), +}); + +const lambangDesaDefaultForm = { + judul: "", + deskripsi: "", +}; + +type LambangDesaForm = Prisma.LambangDesaGetPayload<{ + select: { + id: true; + judul: true; + deskripsi: true; + }; +}>; + +const lambangDesa = proxy({ + findUnique: { + data: null as LambangDesaForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/lambang/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + + if (result.success) { + this.data = result.data; + return result.data; + } else { + throw new Error( + result.message || "Gagal mengambil data lambang desa" + ); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + console.error("Load lambang desa error:", msg); + toast.error("Terjadi kesalahan saat mengambil data lambang desa"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + }, + }, + update: { + id: "", + form: { ...lambangDesaDefaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(lambangDesaData: LambangDesaForm) { + this.id = lambangDesaData.id; + this.isReadOnly = false; + this.form = { + judul: lambangDesaData.judul || "", + deskripsi: lambangDesaData.deskripsi || "", + }; + }, + + updateField(field: keyof typeof lambangDesaDefaultForm, value: string) { + this.form[field] = value; + }, + + async submit() { + // Validate form + const validation = lambangDesaForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch( + `/api/desa/profile/lambang/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update lambang desa"); + // Refresh profile data + await lambangDesa.findUnique.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update lambang desa"); + } + } catch (error) { + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Update lambang desa error:", errorMessage); + toast.error("Terjadi kesalahan saat update lambang desa"); + return false; + } finally { + this.loading = false; + } + }, + + // Reset form + reset() { + this.id = ""; + this.form = { ...lambangDesaDefaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + }, + }, +}); + +// ========================================= MASKOT DESA ========================================= // +const maskotForm = z.object({ + judul: z.string().min(3, "Judul minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + images: z + .array( + z.object({ + label: z.string().min(1, "Label wajib"), + imageId: z.string().min(1, "Image ID wajib"), + }) + ) + .min(1, "Minimal 1 gambar harus diisi"), +}); + +const maskotDefaultForm = { + judul: "", + deskripsi: "", + images: [] as { label: string; imageId: string }[], +}; + +type FormData = typeof maskotDefaultForm; + +type MaskotDesaForm = Prisma.MaskotDesaGetPayload<{ + include: { + images: { + include: { + image: { + select: { + id: true; + name: true; + path: true; + link: true; + }; + }; + }; + }; + }; +}>; + +const maskotDesa = proxy({ + findUnique: { + data: null as MaskotDesaForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/maskot/${id}`); + const result = await response.json(); + + if (response.ok && result.success) { + this.data = result.data; + return result.data; + } else { + throw new Error(result.message || "Gagal mengambil data profile"); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + console.error("Load profile error:", msg); + toast.error("Terjadi kesalahan saat mengambil data profile"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + }, + }, + + update: { + id: "", + form: { ...maskotDefaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(profileData: MaskotDesaForm) { + this.id = profileData.id; + this.isReadOnly = false; + this.form = { + judul: profileData.judul || "", + deskripsi: profileData.deskripsi || "", + images: (profileData.images || []).map((img) => ({ + label: img.label, + imageId: img.image.id, + })), + }; + }, + + updateField(field: K, value: FormData[K]) { + this.form[field] = value; + }, + + addImage() { + this.form.images.push({ label: "", imageId: "" }); + }, + + removeImage(index: number) { + this.form.images.splice(index, 1); + }, + + async submit() { + const validation = maskotForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch( + `/api/desa/profile/maskot/${this.id}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form), + } + ); + + const result = await response.json(); + + if (response.ok && result.success) { + toast.success("Berhasil update profile"); + await maskotDesa.findUnique.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update profile"); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + toast.error("Terjadi kesalahan saat update profile"); + return false; + } finally { + this.loading = false; + } + }, + + reset() { + this.id = ""; + this.form = { ...maskotDefaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + }, + }, + + async loadForEdit(id: string) { + const data = await this.findUnique.load(id); + if (data) { + this.update.initialize(data); + } + return data; + }, + + reset() { + this.findUnique.reset(); + this.update.reset(); + }, +}); + +// ========================================= PROFIL PERBEKEL ========================================= // +const profilPerbekelForm = z.object({ + biodata: z.string().min(3, "Biodata minimal 3 karakter"), + pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"), + pengalamanOrganisasi: z + .string() + .min(3, "Pengalaman Organisasi minimal 3 karakter"), + programUnggulan: z.string().min(3, "Program Unggulan minimal 3 karakter"), + imageId: z.string().min(1, "Gambar wajib dipilih"), +}); + +const profilPerbekelDefaultForm = { + biodata: "", + pengalaman: "", + pengalamanOrganisasi: "", + programUnggulan: "", + imageId: "", +}; + +type ProfilPerbekelForm = Prisma.ProfilPerbekelGetPayload<{ + select: { + id: true; + biodata: true; + pengalaman: true; + pengalamanOrganisasi: true; + programUnggulan: true; + imageId: true; + image?: { + select: { + link: true; + }; + }; + }; +}>; + +const profilPerbekel = proxy({ + findUnique: { + data: null as ProfilPerbekelForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/desa/profile/profileperbekel/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { + this.data = result.data; + return result.data; + } else { + throw new Error( + result.message || "Gagal mengambil data profil perbekel" + ); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + toast.error("Terjadi kesalahan saat mengambil data profil perbekel"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + }, + }, + + edit: { + id: "", + form: { ...profilPerbekelDefaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(profilData: ProfilPerbekelForm) { + this.id = profilData.id; + this.isReadOnly = false; + this.form = { + biodata: profilData.biodata || "", + pengalaman: profilData.pengalaman || "", + pengalamanOrganisasi: profilData.pengalamanOrganisasi || "", + programUnggulan: profilData.programUnggulan || "", + imageId: profilData.imageId || "", + }; + }, + + updateField(field: keyof typeof profilPerbekelDefaultForm, value: string) { + this.form[field] = value; + }, + + async submit() { + const validation = profilPerbekelForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch( + `/api/desa/profile/profileperbekel/${this.id}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.form), + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update profil perbekel"); + await profilPerbekel.findUnique.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update profil perbekel"); + } + } catch (error) { + const msg = (error as Error).message; + this.error = msg; + toast.error("Terjadi kesalahan saat update profil perbekel"); + return false; + } finally { + this.loading = false; + } + }, + reset() { + this.id = ""; + this.form = { ...profilPerbekelDefaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + }, + }, + + async loadForEdit(id: string) { + const profileData = await this.findUnique.load(id); + if (profileData) { + this.edit.initialize(profileData); + } + return profileData; + }, + + reset() { + this.findUnique.reset(); + this.edit.reset(); + }, +}); + +const stateProfileDesa = proxy({ + lambangDesa, + maskotDesa, + profilPerbekel, + visiMisiDesa, + sejarahDesa, +}); export default stateProfileDesa; diff --git a/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts b/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts index ff0b2792..eee0179a 100644 --- a/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts +++ b/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts @@ -5,61 +5,239 @@ import { proxy } from "valtio"; import { z } from "zod"; const templateDaftarInformasi = z.object({ - jenisInformasi: z.string().min(3, "Jenis Informasi minimal 3 karakter"), - deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), - tanggal: z.string().min(3, "Tanggal minimal 3 karakter"), -}) + jenisInformasi: z.string().min(3, "Jenis Informasi minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + tanggal: z.string().min(3, "Tanggal minimal 3 karakter"), +}); + +const defaultForm = { + jenisInformasi: "", + deskripsi: "", + tanggal: "", +}; type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{ - select: { - jenisInformasi: true; - deskripsi: true; - tanggal: true; - }; + select: { + jenisInformasi: true; + deskripsi: true; + tanggal: true; + }; }>; -const daftarInformasi = proxy({ - create: { - form: {} as DaftarInformasi, - loading: false, - async create() { - const cek = templateDaftarInformasi.safeParse(daftarInformasi.create.form); - if (!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - return toast.error(err); - } - try { - daftarInformasi.create.loading = true; - const res = await ApiFetch.api.ppid.daftarinformasipublik["create"].post(daftarInformasi.create.form); - if (res.status === 200) { - daftarInformasi.findMany.load(); - return toast.success("success create"); - } - return toast.error("failed create"); - } catch (error) { - console.log((error as Error).message); - } finally { - daftarInformasi.create.loading = false; - } - }, - }, - findMany: { - data: null as - | Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[] - | null, - async load() { - const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get(); - if (res.status === 200) { - daftarInformasi.findMany.data = res.data?.data ?? []; - } +const daftarInformasiPublik = proxy({ + create: { + form: {} as DaftarInformasi, + loading: false, + async create() { + const cek = templateDaftarInformasi.safeParse( + daftarInformasiPublik.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + daftarInformasiPublik.create.loading = true; + const res = await ApiFetch.api.ppid.daftarinformasipublik[ + "create" + ].post(daftarInformasiPublik.create.form); + if (res.status === 200) { + daftarInformasiPublik.findMany.load(); + return toast.success("success create"); } - } - }); + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + daftarInformasiPublik.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.daftarinformasipublik[ + "find-many" + ].get(); + if (res.status === 200) { + daftarInformasiPublik.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.DaftarInformasiPublikGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/ppid/daftarinformasipublik/${id}`); + if (res.ok) { + const data = await res.json(); + daftarInformasiPublik.findUnique.data = data.data ?? null; + } else { + console.error( + "Failed to fetch daftar informasi publik:", + res.statusText + ); + daftarInformasiPublik.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching daftar informasi publik:", error); + daftarInformasiPublik.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); - const stateDaftarInformasiPublik = proxy({ - daftarInformasi - }) + try { + daftarInformasiPublik.delete.loading = true; - export default stateDaftarInformasiPublik; \ No newline at end of file + const response = await fetch( + `/api/ppid/daftarinformasipublik/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success( + result.message || "Daftar Informasi Publik berhasil dihapus" + ); + await daftarInformasiPublik.findMany.load(); // refresh list + } else { + toast.error( + result?.message || "Gagal menghapus daftar informasi publik" + ); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus daftar informasi publik"); + } finally { + daftarInformasiPublik.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/ppid/daftarinformasipublik/${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 = { + jenisInformasi: data.jenisInformasi, + deskripsi: data.deskripsi, + tanggal: data.tanggal, + }; + return data; + } else { + throw new Error(result?.message || "Gagal mengambil data"); + } + } catch (error) { + console.error("Error loading daftar informasi publik:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + + async update() { + const cek = templateDaftarInformasi.safeParse( + daftarInformasiPublik.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + daftarInformasiPublik.edit.loading = true; + + const response = await fetch( + `/api/ppid/daftarinformasipublik/${this.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jenisInformasi: this.form.jenisInformasi, + deskripsi: this.form.deskripsi, + tanggal: this.form.tanggal, + }), + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` + ); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update daftar informasi publik"); + await daftarInformasiPublik.findMany.load(); // refresh list + return true; + } else { + throw new Error( + result.message || "Gagal update daftar informasi publik" + ); + } + } catch (error) { + console.error("Error updating daftar informasi publik:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update daftar informasi publik" + ); + return false; + } finally { + daftarInformasiPublik.edit.loading = false; + } + }, + + reset() { + daftarInformasiPublik.edit.id = ""; + daftarInformasiPublik.edit.form = { ...defaultForm }; + }, + }, +}); + +export default daftarInformasiPublik; diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts index 5e2c0192..164c6e81 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts @@ -11,12 +11,13 @@ const templateGrafikJenisKelamin = z.object({ type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{ select: { + id: true; laki: true; perempuan: true; }; }>; -const defaultForm: GrafikJenisKelamin = { +const defaultForm: Omit & { id?: string } = { laki: "", perempuan: "", }; @@ -41,9 +42,16 @@ const grafikBerdasarkanJenisKelamin = proxy({ "create" ].post(grafikBerdasarkanJenisKelamin.create.form); if (res.status === 200) { - grafikBerdasarkanJenisKelamin.create.form = defaultForm; - grafikBerdasarkanJenisKelamin.findMany.load(); - return toast.success("success create"); + const id = res.data?.data?.id; + if (id) { + toast.success("Success create"); + grafikBerdasarkanJenisKelamin.create.form = { + laki: "", + perempuan: "", + }; + grafikBerdasarkanJenisKelamin.findMany.load(); + return id; + } } return toast.error("failed create"); } catch (error) { @@ -69,9 +77,103 @@ const grafikBerdasarkanJenisKelamin = proxy({ } }, }, + findUnique: { + data: null as Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/ppid/grafikberdasarkanjeniskelamin/${id}` + ); + if (res.ok) { + const data = await res.json(); + grafikBerdasarkanJenisKelamin.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + grafikBerdasarkanJenisKelamin.findUnique.data = null; + } + } catch (error) { + console.error("Error loading grafik berdasarkan jenis kelamin:", error); + grafikBerdasarkanJenisKelamin.findUnique.data = null; + } + }, + }, + update: { + id: "", + form: {...defaultForm}, + loading: false, + async byId() { + // Method implementation if needed + }, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + const cek = templateGrafikJenisKelamin.safeParse(this.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + this.loading = true; + try { + const response = await fetch( + `/api/ppid/grafikberdasarkanjeniskelamin/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + const result = await response.json(); + if (!response.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + toast.success("Berhasil update data!"); + await grafikBerdasarkanJenisKelamin.findMany.load(); + return result.data; + } catch (error) { + console.error("Error update data:", error); + toast.error("Gagal update data grafik berdasarkan jenis kelamin"); + } finally { + this.loading = false; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + grafikBerdasarkanJenisKelamin.delete.loading = true; + + const response = await fetch(`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Grafik berdasarkan jenis kelamin berhasil dihapus"); + await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus grafik berdasarkan jenis kelamin"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin"); + } finally { + grafikBerdasarkanJenisKelamin.delete.loading = false; + } + }, + } }); -const stateGrafikBerdasarkanJenisKelamin = proxy({ - grafikBerdasarkanJenisKelamin, -}); -export default stateGrafikBerdasarkanJenisKelamin; +export default grafikBerdasarkanJenisKelamin; diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts index 4fd2e69b..027669ad 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts @@ -13,6 +13,7 @@ const templateGrafikResponden = z.object({ type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{ select: { + id: true; sangatbaik: true; baik: true; kurangbaik: true; @@ -20,7 +21,7 @@ type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{ }; }>; -const defaultForm: GrafikResponden = { +const defaultForm: Omit & { id?: string } = { sangatbaik: "", baik: "", kurangbaik: "", @@ -47,9 +48,18 @@ const grafikBerdasarkanResponden = proxy({ "create" ].post(grafikBerdasarkanResponden.create.form); if (res.status === 200) { - grafikBerdasarkanResponden.create.form = defaultForm; - grafikBerdasarkanResponden.findMany.load(); - return toast.success("success create"); + const id = res.data?.data?.id; + if (id) { + toast.success("Success create"); + grafikBerdasarkanResponden.create.form = { + sangatbaik: "", + baik: "", + kurangbaik: "", + tidakbaik: "", + }; + grafikBerdasarkanResponden.findMany.load(); + return id; + } } return toast.error("failed create"); } catch (error) { @@ -75,10 +85,112 @@ const grafikBerdasarkanResponden = proxy({ } }, }, + findUnique: { + data: null as Prisma.GrafikBerdasarkanRespondenGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/ppid/grafikberdasarkanresponden/${id}` + ); + if (res.ok) { + const data = await res.json(); + grafikBerdasarkanResponden.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + grafikBerdasarkanResponden.findUnique.data = null; + } + } catch (error) { + console.error("Error loading grafik berdasarkan responden:", error); + grafikBerdasarkanResponden.findUnique.data = null; + } + }, + }, + update: { + id: "", + form: {...defaultForm}, + loading: false, + async byId() { + // Method implementation if needed + }, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + const cek = templateGrafikResponden.safeParse(this.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return null; + } + + this.loading = true; + + try { + const response = await fetch(`/api/ppid/grafikberdasarkanresponden/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + + const result = await response.json(); + + if (!response.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + + await grafikBerdasarkanResponden.findMany.load(); + + return result.data; + } catch (error) { + console.error("Error update data:", error); + toast.error("Gagal update data grafik berdasarkan responden"); + } finally { + this.loading = false; + } + } + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + grafikBerdasarkanResponden.delete.loading = true; + + const response = await fetch(`/api/ppid/grafikberdasarkanresponden/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Grafik berdasarkan responden berhasil dihapus"); + await grafikBerdasarkanResponden.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus grafik berdasarkan responden"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan responden"); + } finally { + grafikBerdasarkanResponden.delete.loading = false; + } + } + } }); -const stateGrafikResponden = proxy({ - grafikBerdasarkanResponden, -}); - -export default stateGrafikResponden; \ No newline at end of file +export default grafikBerdasarkanResponden; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts index 3d05f6cf..7b3f4693 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts @@ -5,80 +5,182 @@ 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(2, "Data remaja harus diisi"), + dewasa: z.string().min(2, "Data dewasa harus diisi"), + orangtua: z.string().min(2, "Data orangtua harus diisi"), + lansia: z.string().min(2, "Data lansia harus diisi"), }); type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{ - select: { - remaja: true; - dewasa: true; - orangtua: true; - lansia: true; - }; + select: { + id: true; + remaja: true; + dewasa: true; + orangtua: true; + lansia: true; + }; }>; -const defaultForm: GrafikUmur = { - remaja: "", - dewasa: "", - orangtua: "", - lansia: "", +const defaultForm: Omit & { id?: string } = { + remaja: "", + dewasa: "", + orangtua: "", + lansia: "", }; const grafikBerdasarkanUmur = proxy({ - create: { - form: defaultForm, - loading: false, - async create() { - const cek = templateGrafikUmur.safeParse( - grafikBerdasarkanUmur.create.form - ); - if (!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - return toast.error(err); - } - try { - grafikBerdasarkanUmur.create.loading = true; - const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ - "create" - ].post(grafikBerdasarkanUmur.create.form); - if (res.status === 200) { - grafikBerdasarkanUmur.create.form = defaultForm; - grafikBerdasarkanUmur.findMany.load(); - return toast.success("success create"); - } - return toast.error("failed create"); - } catch (error) { - console.log((error as Error).message); - } finally { - grafikBerdasarkanUmur.create.loading = false; - } - }, + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikUmur.safeParse( + grafikBerdasarkanUmur.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikBerdasarkanUmur.create.loading = true; + const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ + "create" + ].post(grafikBerdasarkanUmur.create.form); + if (res.status === 200) { + const id = res.data?.data?.id; + if (id) { + toast.success("Success create"); + grafikBerdasarkanUmur.create.form = { + remaja: "", + dewasa: "", + orangtua: "", + lansia: "", + }; + grafikBerdasarkanUmur.findMany.load(); + return id; + } + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikBerdasarkanUmur.create.loading = false; + } }, - findMany: { - data: null as - | Prisma.GrafikBerdasarkanUmurGetPayload<{ - omit: { isActive: true }; + }, + findMany: { + data: null as + | Prisma.GrafikBerdasarkanUmurGetPayload<{ + omit: { isActive: true }; }>[] - | null, - loading: false, - async load() { - const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ - "find-many" - ].get(); - if (res.status === 200) { - grafikBerdasarkanUmur.findMany.data = res.data?.data ?? []; - } - }, + | null, + loading: false, + async load() { + const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ + "find-many" + ].get(); + if (res.status === 200) { + grafikBerdasarkanUmur.findMany.data = res.data?.data ?? []; + } }, -}) + }, + findUnique: { + data: null as Prisma.GrafikBerdasarkanUmurGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/ppid/grafikberdasarkanumur/${id}`); + if (res.ok) { + const data = await res.json(); + grafikBerdasarkanUmur.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + grafikBerdasarkanUmur.findUnique.data = null; + } + } catch (error) { + console.error("Error loading grafik berdasarkan umur:", error); + grafikBerdasarkanUmur.findUnique.data = null; + } + }, + }, + update: { + id: "", + form: { ...defaultForm }, + loading: false, + async byId() { + // Method implementation if needed + }, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + const cek = templateGrafikUmur.safeParse(this.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return null; + } + this.loading = true; + try { + const response = await fetch(`/api/ppid/grafikberdasarkanumur/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + const result = await response.json(); + if (!response.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + toast.success("Berhasil update data!"); + await grafikBerdasarkanUmur.findMany.load(); + return result.data; + } catch (error) { + console.error("Error update data:", error); + toast.error("Gagal update data grafik berdasarkan umur"); + } finally { + this.loading = false; + } + } + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); -const stateGrafikBerdasarkanUmur = proxy({ - grafikBerdasarkanUmur, -}) + try { + grafikBerdasarkanUmur.delete.loading = true; -export default stateGrafikBerdasarkanUmur; \ No newline at end of file + const response = await fetch(`/api/ppid/grafikberdasarkanumur/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Grafik berdasarkan umur berhasil dihapus"); + await grafikBerdasarkanUmur.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus grafik berdasarkan umur"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan umur"); + } finally { + grafikBerdasarkanUmur.delete.loading = false; + } + } + } +}); + +export default grafikBerdasarkanUmur; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts index b8badf0c..65287269 100644 --- a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts @@ -11,12 +11,13 @@ const templateGrafikHasilKepuasanMasyarakat = z.object({ type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{ select: { + id: true; label: true; kepuasan: true; }; }>; -const defaultForm: GrafikHasilKepuasanMasyarakat = { +const defaultForm: Omit & { id?: string } = { label: "", kepuasan: "", }; @@ -41,13 +42,18 @@ const grafikHasilKepuasanMasyarakat = proxy({ grafikHasilKepuasanMasyarakat.create.form ); if (res.status === 200) { - grafikHasilKepuasanMasyarakat.create.form = { - label: "", - kepuasan: "" - }; - grafikHasilKepuasanMasyarakat.findMany.load(); - return toast.success("success create"); + const id = res.data?.data?.id; + if (id) { + toast.success("Success create"); + grafikHasilKepuasanMasyarakat.create.form = { + label: "", + kepuasan: "", + }; + grafikHasilKepuasanMasyarakat.findMany.load(); + return id; + } } + return toast.error("failed create"); } catch (error) { console.log((error as Error).message); @@ -58,19 +64,127 @@ const grafikHasilKepuasanMasyarakat = proxy({ }, findMany: { data: null as - | Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[] - | null, + | Prisma.IndeksKepuasanMasyarakatGetPayload<{ + omit: { isActive: true }; + }>[] + | null, async load() { - const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get(); - if (res.status === 200) { - grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? []; + const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat[ + "find-many" + ].get(); + if (res.status === 200) { + grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.IndeksKepuasanMasyarakatGetPayload<{ + omit: { isActive: true }; + }> | null, + + async load(id: string) { + try { + const res = await fetch( + `/api/ppid/grafikhasilkepuasamanmasyarakat/${id}` + ); + if (res.ok) { + const data = await res.json(); + grafikHasilKepuasanMasyarakat.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + grafikHasilKepuasanMasyarakat.findUnique.data = null; } + } catch (error) { + console.error("Error loading grafik hasil kepuasan masyarakat:", error); + grafikHasilKepuasanMasyarakat.findUnique.data = null; + } + }, + }, + update: { + id: "", + form: { ...defaultForm }, + loading: false, + async byId() { + // Method implementation if needed + }, + async submit() { + const id = this.id; + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + // ✅ Validasi pakai Zod + const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(this.form); + if (!cek.success) { + const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`; + toast.error(err); + return null; + } + + this.loading = true; + + try { + const response = await fetch(`/api/ppid/grafikhasilkepuasamanmasyarakat/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + + const result = await response.json(); + + if (!response.ok || !result?.success) { + throw new Error(result?.message || "Gagal update data"); + } + + toast.success("Berhasil update data!"); + + // ✅ Optional: refresh list kalau kamu langsung ke halaman list + await grafikHasilKepuasanMasyarakat.findMany.load(); + + return result.data; + } catch (error) { + console.error("Error update data:", error); + toast.error("Gagal update data grafik hasil kepuasan masyarakat"); + } finally { + this.loading = false; + } } + + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + grafikHasilKepuasanMasyarakat.delete.loading = true; + + const response = await fetch(`/api/ppid/grafikhasilkepuasamanmasyarakat/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Grafik hasil kepuasan masyarakat berhasil dihapus"); + await grafikHasilKepuasanMasyarakat.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus grafik hasil kepuasan masyarakat"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus grafik hasil kepuasan masyarakat"); + } finally { + grafikHasilKepuasanMasyarakat.delete.loading = false; + } + }, } }); -const stateGrafikHasilKepuasanMasyarakat = proxy({ - grafikHasilKepuasanMasyarakat, -}); - -export default stateGrafikHasilKepuasanMasyarakat; +export default grafikHasilKepuasanMasyarakat; diff --git a/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts b/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts index 167d8edd..0a6304b8 100644 --- a/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts +++ b/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts @@ -1,4 +1,3 @@ -import ApiFetch from "@/lib/api-fetch"; import { Prisma } from "@prisma/client"; import { toast } from "react-toastify"; import { proxy } from "valtio"; @@ -13,11 +12,18 @@ const templateForm = z.object({ riwayat: z.string().min(3, "Riwayat minimal 3 karakter"), pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"), unggulan: z.string().min(3, "Unggulan minimal 3 karakter"), + imageId: z.string().min(1, "Gambar wajib dipilih"), }); -/** - * Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema. - */ +const defaultForm = { + name: "", + biodata: "", + riwayat: "", + pengalaman: "", + unggulan: "", + imageId: "", +}; + type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{ select: { id: true; @@ -26,147 +32,170 @@ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{ riwayat: true; pengalaman: true; unggulan: true; - imageUrl: true; + imageId: true; + image?: { + select: { + link: true; + }; + }; }; }>; /** - * State utama ProfilePPID yang mencakup fitur: - * - Ambil data berdasarkan ID - * - Update data - * - Upload gambar + * Improved State Management - Consolidated and more robust */ const stateProfilePPID = proxy({ - /** - * Bagian untuk ambil data berdasarkan ID - */ - findById: { + // Consolidated data management + profile: { data: null as ProfilePPIDForm | null, loading: false, + error: null as string | null, - /** - * Inisialisasi data kosong ke dalam state. - */ - initialize() { - stateProfilePPID.findById.data = { - id: '', - name: '', - biodata: '', - riwayat: '', - pengalaman: '', - unggulan: '', - imageUrl: '' - } as ProfilePPIDForm; - }, - - /** - * Mengambil data profil berdasarkan ID. - * @param {string} id - ID dari profile yang ingin diambil. - */ + // Single method to load profile data async load(id: string) { - try { - stateProfilePPID.findById.loading = true; - const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({ - query: { id }, - }); + if (!id) { + toast.warn("ID tidak valid"); + return null; + } - if (res.status === 200) { - stateProfilePPID.findById.data = res.data?.data ?? null; + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/ppid/profileppid/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { + this.data = result.data; + return result.data; } else { - toast.error("Gagal mengambil data profile"); + throw new Error(result.message || "Gagal mengambil data profile"); } } catch (error) { - console.error((error as Error).message); + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Load profile error:", errorMessage); toast.error("Terjadi kesalahan saat mengambil data profile"); + return null; } finally { - stateProfilePPID.findById.loading = false; + this.loading = false; } }, + + // Reset profile data + reset() { + this.data = null; + this.error = null; + this.loading = false; + } }, - /** - * Bagian untuk update data profile - */ - update: { + // Edit form management + editForm: { + id: "", + form: { ...defaultForm }, loading: false, + error: null as string | null, + isReadOnly: false, // Flag untuk data yang tidak bisa diedit - /** - * Melakukan validasi dan menyimpan perubahan data profile ke server. - * @param {ProfilePPIDForm} data - Data profil yang akan disimpan. - */ - async save(data: ProfilePPIDForm) { - const cek = templateForm.safeParse(data); + // Initialize form with profile data + initialize(profileData: ProfilePPIDForm) { + this.id = profileData.id; + this.isReadOnly = false; // Semua data bisa diedit + this.form = { + name: profileData.name || "", + biodata: profileData.biodata || "", + riwayat: profileData.riwayat || "", + pengalaman: profileData.pengalaman || "", + unggulan: profileData.unggulan || "", + imageId: profileData.imageId || "", + }; + }, - if (!cek.success) { - const errors = cek.error.issues + // Update form field + updateField(field: keyof typeof defaultForm, value: string) { + this.form[field] = value; + }, + + // Submit form + async submit() { + // Validate form + const validation = templateForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues .map((issue) => `${issue.path.join(".")}: ${issue.message}`) .join(", "); toast.error(`Form tidak valid: ${errors}`); - return; + return false; } - try { - stateProfilePPID.update.loading = true; - const res = await ApiFetch.api.ppid.profileppid["update"].post(data); + this.loading = true; + this.error = null; - if (res.status === 200) { + try { + const response = await fetch(`/api/ppid/profileppid/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { toast.success("Berhasil update profile"); - await stateProfilePPID.findById.load(data.id); + // Refresh profile data + await stateProfilePPID.profile.load(this.id); + return true; } else { - toast.error("Gagal update profile"); + throw new Error(result.message || "Gagal update profile"); } } catch (error) { - console.error((error as Error).message); + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Update profile error:", errorMessage); toast.error("Terjadi kesalahan saat update profile"); + return false; } finally { - stateProfilePPID.update.loading = false; + this.loading = false; } }, + + // Reset form + reset() { + this.id = ""; + this.form = { ...defaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + } }, - /** - * Bagian untuk upload gambar profil - */ - uploadImage: { - loading: false, - - /** - * Mengunggah gambar profil berdasarkan ID. - * @param {File} file - File gambar yang akan diunggah. - * @param {string} id - ID dari profil yang akan diperbarui gambarnya. - */ - async save(file: File, id: string) { - if (!file || !id) { - toast.error("File atau ID harus disertakan"); - return; - } - - try { - stateProfilePPID.uploadImage.loading = true; - - const form = new FormData(); - form.append("file", file); - form.append("id", id); - - const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form); - - if (res.status === 200) { - toast.success("Berhasil mengunggah gambar"); - await stateProfilePPID.findById.load(id); - } else { - toast.error("Gagal mengunggah gambar"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat mengunggah gambar"); - } finally { - stateProfilePPID.uploadImage.loading = false; - } - }, + // Helper methods + async loadForEdit(id: string) { + const profileData = await this.profile.load(id); + if (profileData) { + this.editForm.initialize(profileData); + } + return profileData; }, + + reset() { + this.profile.reset(); + this.editForm.reset(); + } }); -/** - * Ekspor state utama ProfilePPID untuk digunakan di komponen lain. - */ -export default stateProfilePPID; +export default stateProfilePPID; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts b/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts new file mode 100644 index 00000000..4df4347c --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts @@ -0,0 +1,169 @@ +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +const templateForm = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + imageId: z.string().min(1, "Gambar wajib dipilih"), +}) + +const defaultForm = { + name: "", + imageId: "", +}; + +type StrukturPPIDForm = Prisma.StrukturPPIDGetPayload<{ + select: { + id: true; + name: true; + imageId: true; + image?: { + select: { + link: true; + }; + }; + }; +}>; + +const stateStrukturPPID = proxy({ + struktur: { + data: null as StrukturPPIDForm | null, + loading: false, + error: null as string | null, + + async load(id: string) { + if(!id) { + toast.warn("ID tidak valid") + return null + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/ppid/strukturppid/${id}`); + + if(!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result = await response.json(); + + if(result.success) { + this.data = result.data; + return result.data + } else { + throw new Error(result.message || "Gagal mengambil data struktur") + } + } catch (error) { + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Load struktur error:", errorMessage); + toast.error("Terjadi kesalahan saat mengambil data struktur"); + return null; + } finally { + this.loading = false; + } + }, + + reset() { + this.data = null; + this.error = null; + this.loading = false; + } + }, + + editStruktur: { + id: "", + form: { ...defaultForm }, + loading: false, + error: null as string | null, + isReadOnly: false, + + initialize(strukturData: StrukturPPIDForm) { + this.id = strukturData.id; + this.isReadOnly = false; + this.form = { + name: strukturData.name || "", + imageId: strukturData.imageId || "", + }; + }, + + updateField(field: keyof typeof defaultForm, value: string) { + this.form[field] = value; + }, + + async submit() { + const validation = templateForm.safeParse(this.form); + + if (!validation.success) { + const errors = validation.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return false; + } + + this.loading = true; + this.error = null; + + try { + const response = await fetch(`/api/ppid/strukturppid/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(this.form), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update struktur"); + await stateStrukturPPID.struktur.load(this.id); + return true; + } else { + throw new Error(result.message || "Gagal update struktur"); + } + } catch (error) { + const errorMessage = (error as Error).message; + this.error = errorMessage; + console.error("Update struktur error:", errorMessage); + toast.error("Terjadi kesalahan saat update struktur"); + return false; + } finally { + this.loading = false; + } + }, + + reset() { + this.id = ""; + this.form = { ...defaultForm }; + this.error = null; + this.loading = false; + this.isReadOnly = false; + } + }, + + async loadForEdit(id: string) { + const strukturData = await this.struktur.load(id); + if (strukturData) { + this.editStruktur.initialize(strukturData); + } + return strukturData; + }, + + reset() { + this.struktur.reset(); + this.editStruktur.reset(); + } +}) + +export default stateStrukturPPID; + \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx new file mode 100644 index 00000000..47eea7f9 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx @@ -0,0 +1,72 @@ +/* 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 LayoutTabsLayanan({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const tabs = [ + { + label: "Pelayanan Surat Keterangan", + value: "pelayanansuratketerangan", + href: "/admin/desa/layanan/pelayanan_surat_keterangan" + }, + { + label: "Pelayanan Perizinan Berusaha", + value: "pelayananperizinanusaha", + href: "/admin/desa/layanan/pelayanan_perizinan_berusaha" + }, + { + label: "Pelayanan Telunjuk Sakti Desa", + value: "pelayanantelunjuksaktidesa", + href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa" + }, + { + label: "Pelayanan Penduduk Non-Permanent", + value: "pelayanantelunjuknonpermanent", + href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent" + } + ]; + const curentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(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 ( + + Layanan + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabsLayanan; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx index a753aede..b5ea38d1 100644 --- a/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx @@ -125,16 +125,6 @@ function EditBerita() { placeholder="masukkan judul" /> - { - setFormData({ - ...formData, - kategoriBeritaId: val?.id || '' - }); - }} - /> - setFormData({ ...formData, deskripsi: e.target.value })} @@ -174,6 +164,16 @@ function EditBerita() { /> + { + setFormData({ + ...formData, + kategoriBeritaId: val?.id || '' + }); + }} + /> + diff --git a/src/app/admin/(dashboard)/desa/berita/create/page.tsx b/src/app/admin/(dashboard)/desa/berita/create/page.tsx index f27cc51f..4f5e7dff 100644 --- a/src/app/admin/(dashboard)/desa/berita/create/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/create/page.tsx @@ -63,9 +63,9 @@ export default function CreateBerita() { return ( - + @@ -79,8 +79,9 @@ export default function CreateBerita() { placeholder="masukkan judul" /> { - beritaState.berita.create.form.kategoriBeritaId = val.id; + beritaState.berita.create.form.kategoriBeritaId = val?.id || ""; }} /> Konten { - beritaState.berita.create.form.content = htmlContent; - }} + value={beritaState.berita.create.form.content} + onChange={(htmlContent) => { + beritaState.berita.create.form.content = htmlContent; + }} /> @@ -126,26 +127,37 @@ export default function CreateBerita() { ); - 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 ; } - + + + const selectedValue = value || defaultValue; + return ( Kategori} + placeholder='Pilih kategori' + data={categoryState.findMany.data?.map((item) => ({ + label: item.name, + value: item.id, + }))} + onChange={(val) => { + const selected = categoryState.findMany.data?.find((item) => item.id === val); + if (selected) { + onChange(selected); + } + }} + searchable + nothingFoundMessage="Tidak ditemukan" + /> + ); +} + +export default CreatePengumuman; diff --git a/src/app/admin/(dashboard)/desa/pengumuman/page.tsx b/src/app/admin/(dashboard)/desa/pengumuman/page.tsx index 22ebd3d9..ad3df221 100644 --- a/src/app/admin/(dashboard)/desa/pengumuman/page.tsx +++ b/src/app/admin/(dashboard)/desa/pengumuman/page.tsx @@ -1,102 +1,111 @@ 'use client' -import { Box, Group, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; -import React from 'react'; -import { useProxy } from 'valtio/utils'; -import stateDesaPengumuman from '../../_state/desa/pengumuman'; -import { useShallowEffect } from '@mantine/hooks'; -import { Prisma } from '@prisma/client'; -import { BeritaEditor } from '../berita/_com/BeritaEditor'; import colors from '@/con/colors'; +import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../_com/header'; +import stateDesaPengumuman from '../../_state/desa/pengumuman'; +import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; +import { useState } from 'react'; + function Page() { return ( - Pengumuman - - - - + } + /> + ); } -function PengumumanCreate() { - const pengumumanState = useProxy(stateDesaPengumuman) - - - return ( - - - - { - pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id - }} /> - { - pengumumanState.pengumuman.create.form.judul = val.target.value - }} label={Judul} placeholder='masukkan judul' /> - { - pengumumanState.pengumuman.create.form.deskripsi = val.target.value - }} label={Deskripsi} placeholder='masukkan deskripsi' /> - { - pengumumanState.pengumuman.create.form.content = val - pengumumanState.pengumuman.create.create() - }} /> - - - - ) -} - function PengumumanList() { const pengumumanState = useProxy(stateDesaPengumuman) + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + useShallowEffect(() => { pengumumanState.pengumuman.findMany.load() }, []) - if (!pengumumanState.pengumuman.findMany.data) return - {Array.from({ length: 10 }).map((v, k) => )} - + const router = useRouter() + + const handleHapus = () => { + if (selectedId) { + pengumumanState.pengumuman.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + } + } + + if (!pengumumanState.pengumuman.findMany.data) { + return ( + + + + ) + } + return ( - - List Pengumuman - {pengumumanState.pengumuman.findMany.data?.map((item) => ( - {item.judul} - ))} + + + + List Pengumuman + + + + + + + + + + Judul + Kategori + Detail + + + + + {pengumumanState.pengumuman.findMany.data?.map((item) => ( + + + + {item.judul} + + + {item.CategoryPengumuman?.name} + + + + + ))} + +
+
+ + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus berita ini?' + />
) } -function SelectCategory({ onChange }: { - onChange: (value: Prisma.CategoryPengumumanGetPayload<{ - select: { - name: true, - id: true - } - }>) => void -}) { - const pengumumanState = useProxy(stateDesaPengumuman) - useShallowEffect(() => { - pengumumanState.category.findMany.load() - }, []) - - if (!pengumumanState.category.findMany.data) return - return - {/* {JSON.stringify(pengumumanState.category.findMany.data)} */} -