Compare commits
18 Commits
nico/13-ma
...
nico/keep
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e7fddbed | |||
| 423ad0e2ba | |||
| 084435500f | |||
| 5037009c40 | |||
| 8f2b9665a9 | |||
| 77f99a7c8f | |||
| d88f168258 | |||
| f9bd2cea11 | |||
| 5734e5d9a7 | |||
| 3654629bde | |||
| 02738104b5 | |||
| 92de697ae0 | |||
| cf6a5422ec | |||
| ee9368e911 | |||
| cab86eb02f | |||
| d1e39ae7f9 | |||
| d3a43c72ab | |||
| f5d68d4982 |
BIN
foldergambar/desa/name.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
foldergambar/desa/ppid/profile-ppid/name.png
Normal file
|
After Width: | Height: | Size: 922 KiB |
@@ -48,14 +48,16 @@
|
|||||||
"elysia": "^1.2.12",
|
"elysia": "^1.2.12",
|
||||||
"embla-carousel-autoplay": "^8.5.2",
|
"embla-carousel-autoplay": "^8.5.2",
|
||||||
"embla-carousel-react": "^7.1.0",
|
"embla-carousel-react": "^7.1.0",
|
||||||
|
"form-data": "^4.0.2",
|
||||||
"framer-motion": "^12.4.1",
|
"framer-motion": "^12.4.1",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"jotai": "^2.12.3",
|
"jotai": "^2.12.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"motion": "^12.4.1",
|
"motion": "^12.4.1",
|
||||||
"nanoid": "^5.1.0",
|
"nanoid": "^5.1.5",
|
||||||
"next": "15.1.6",
|
"next": "15.1.6",
|
||||||
"next-view-transitions": "^0.3.4",
|
"next-view-transitions": "^0.3.4",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
"prisma": "^6.3.1",
|
"prisma": "^6.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
"readdirp": "^4.1.1",
|
"readdirp": "^4.1.1",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
"swr": "^2.3.2",
|
"swr": "^2.3.2",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"valtio": "^2.1.3",
|
"valtio": "^2.1.3",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
|
|||||||
9
prisma/data/desa/profile/profil_perbekel.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
|
||||||
|
"pengalaman": "<ul><li>2021 - 2027: Perbekel Desa Darmasaba</li><li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li><li>2020 - Sekarang: Founder Ugawa Record Music Studio</li><li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li></ul>",
|
||||||
|
"pengalamanOrganisasi": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
||||||
|
"programUnggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>"
|
||||||
|
}
|
||||||
|
]
|
||||||
11
prisma/data/desa/profile/profile_desa.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"sejarah" : "<p>Asal – usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>",
|
||||||
|
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
|
||||||
|
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
|
||||||
|
"lambang" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
|
||||||
|
"maskot" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>",
|
||||||
|
"profilPerbekelId" : "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"jenisInformasi": "Peraturan Desa",
|
||||||
|
"deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa",
|
||||||
|
"tanggal": "15 Januari 2024"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -6,6 +6,6 @@
|
|||||||
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
|
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
|
||||||
"pengalaman": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
"pengalaman": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
||||||
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>",
|
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>",
|
||||||
"imageUrl": "/assets/images/ppid/profile-ppid/perbekel.png"
|
"imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,582 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `image` on the `Berita` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `katagoryBeritaId` on the `Berita` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `name` on the `FasilitasPendukung` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `fasilitasKesehatanId` on the `InformasiUmum` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `KatagoryBerita` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Added the required column `imageId` to the `Berita` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `jadwal` to the `DokterdanTenagaMedis` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `specialist` to the `DokterdanTenagaMedis` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `content` to the `FasilitasPendukung` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Berita" DROP CONSTRAINT "Berita_katagoryBeritaId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "InformasiUmum" DROP CONSTRAINT "InformasiUmum_fasilitasKesehatanId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Berita" DROP COLUMN "image",
|
||||||
|
DROP COLUMN "katagoryBeritaId",
|
||||||
|
ADD COLUMN "imageId" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "kategoriBeritaId" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DataKematian_Kelahiran" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DokterdanTenagaMedis" ADD COLUMN "jadwal" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "specialist" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "FasilitasPendukung" DROP COLUMN "name",
|
||||||
|
ADD COLUMN "content" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "InformasiUmum" DROP COLUMN "fasilitasKesehatanId";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "KatagoryBerita";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "FileStorage" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"realName" TEXT NOT NULL,
|
||||||
|
"path" TEXT NOT NULL,
|
||||||
|
"mimeType" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3),
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"link" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "FileStorage_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "VisiMisiPPID" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"visi" TEXT NOT NULL,
|
||||||
|
"misi" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "VisiMisiPPID_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DasarHukumPPID" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"judul" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DasarHukumPPID_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProfilePPID" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"biodata" TEXT NOT NULL,
|
||||||
|
"riwayat" TEXT NOT NULL,
|
||||||
|
"pengalaman" TEXT NOT NULL,
|
||||||
|
"unggulan" TEXT NOT NULL,
|
||||||
|
"imageUrl" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "ProfilePPID_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DaftarInformasiPublik" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"nomor" SERIAL NOT NULL,
|
||||||
|
"jenisInformasi" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"tanggal" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DaftarInformasiPublik_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PermohonanInformasiPublik" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"nomor" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"nik" TEXT NOT NULL,
|
||||||
|
"notelp" TEXT NOT NULL,
|
||||||
|
"alamat" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"jenisInformasiDimintaId" TEXT,
|
||||||
|
"caraMemperolehInformasiId" TEXT,
|
||||||
|
"caraMemperolehSalinanInformasiId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "PermohonanInformasiPublik_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "JenisInformasiDiminta" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "JenisInformasiDiminta_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CaraMemperolehInformasi" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "CaraMemperolehInformasi_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CaraMemperolehSalinanInformasi" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "CaraMemperolehSalinanInformasi_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "FormulirPermohonanKeberatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"notelp" TEXT NOT NULL,
|
||||||
|
"alasan" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "FormulirPermohonanKeberatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IndeksKepuasanMasyarakat" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"label" TEXT NOT NULL,
|
||||||
|
"kepuasan" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "IndeksKepuasanMasyarakat_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GrafikBerdasarkanJenisKelamin" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"perempuan" TEXT NOT NULL,
|
||||||
|
"laki" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "GrafikBerdasarkanJenisKelamin_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GrafikBerdasarkanResponden" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"sangatbaik" TEXT NOT NULL,
|
||||||
|
"baik" TEXT NOT NULL,
|
||||||
|
"kurangbaik" TEXT NOT NULL,
|
||||||
|
"tidakbaik" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "GrafikBerdasarkanResponden_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GrafikBerdasarkanUmur" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"remaja" TEXT NOT NULL,
|
||||||
|
"dewasa" TEXT NOT NULL,
|
||||||
|
"orangtua" TEXT NOT NULL,
|
||||||
|
"lansia" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "GrafikBerdasarkanUmur_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProfileDesa" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"sejarah" TEXT NOT NULL,
|
||||||
|
"visi" TEXT NOT NULL,
|
||||||
|
"misi" TEXT NOT NULL,
|
||||||
|
"lambang" TEXT NOT NULL,
|
||||||
|
"maskot" TEXT NOT NULL,
|
||||||
|
"profilPerbekelId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "ProfileDesa_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ProfilPerbekel" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"biodata" TEXT NOT NULL,
|
||||||
|
"pengalaman" TEXT NOT NULL,
|
||||||
|
"pengalamanOrganisasi" TEXT NOT NULL,
|
||||||
|
"programUnggulan" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "ProfilPerbekel_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "KategoriBerita" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "KategoriBerita_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PotensiDesa" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"kategori" TEXT NOT NULL,
|
||||||
|
"imageId" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "PotensiDesa_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TarifDanLayanan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"layanan" TEXT NOT NULL,
|
||||||
|
"tarif" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "TarifDanLayanan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "JadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "JadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "InformasiJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"tanggal" TEXT NOT NULL,
|
||||||
|
"waktu" TEXT NOT NULL,
|
||||||
|
"lokasi" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "InformasiJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DeskripsiJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DeskripsiJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LayananJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "LayananJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SyaratKetentuanJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "SyaratKetentuanJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DokumenJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DokumenJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PendaftaranJadwalKegiatan" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"tanggal" TEXT NOT NULL,
|
||||||
|
"namaOrangtua" TEXT NOT NULL,
|
||||||
|
"nomor" TEXT NOT NULL,
|
||||||
|
"alamat" TEXT NOT NULL,
|
||||||
|
"catatan" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "PendaftaranJadwalKegiatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GrafikKepuasan" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"label" TEXT NOT NULL,
|
||||||
|
"jumlah" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ArtikelKesehatan" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "ArtikelKesehatan_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Introduction" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "Introduction_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Symptom" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Prevention" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "Prevention_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "FirstAid" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "FirstAid_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MythVsFact" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"mitos" TEXT NOT NULL,
|
||||||
|
"fakta" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "MythVsFact_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DoctorSign" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "DoctorSign_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_FasilitasKesehatanToInformasiUmum" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_FasilitasKesehatanToInformasiUmum_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_FasilitasKesehatanToTarifDanLayanan" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "FileStorage_name_key" ON "FileStorage"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "JenisInformasiDiminta_name_key" ON "JenisInformasiDiminta"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CaraMemperolehInformasi_name_key" ON "CaraMemperolehInformasi"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CaraMemperolehSalinanInformasi_name_key" ON "CaraMemperolehSalinanInformasi"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "KategoriBerita_name_key" ON "KategoriBerita"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_FasilitasKesehatanToInformasiUmum_B_index" ON "_FasilitasKesehatanToInformasiUmum"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_FasilitasKesehatanToTarifDanLayanan_B_index" ON "_FasilitasKesehatanToTarifDanLayanan"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_jenisInformasiDimintaId_fkey" FOREIGN KEY ("jenisInformasiDimintaId") REFERENCES "JenisInformasiDiminta"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_caraMemperolehInformasiId_fkey" FOREIGN KEY ("caraMemperolehInformasiId") REFERENCES "CaraMemperolehInformasi"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PermohonanInformasiPublik" ADD CONSTRAINT "PermohonanInformasiPublik_caraMemperolehSalinanInformasiId_fkey" FOREIGN KEY ("caraMemperolehSalinanInformasiId") REFERENCES "CaraMemperolehSalinanInformasi"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ProfileDesa" ADD CONSTRAINT "ProfileDesa_profilPerbekelId_fkey" FOREIGN KEY ("profilPerbekelId") REFERENCES "ProfilPerbekel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Berita" ADD CONSTRAINT "Berita_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Berita" ADD CONSTRAINT "Berita_kategoriBeritaId_fkey" FOREIGN KEY ("kategoriBeritaId") REFERENCES "KategoriBerita"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PotensiDesa" ADD CONSTRAINT "PotensiDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" ADD CONSTRAINT "_FasilitasKesehatanToInformasiUmum_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" ADD CONSTRAINT "_FasilitasKesehatanToInformasiUmum_B_fkey" FOREIGN KEY ("B") REFERENCES "InformasiUmum"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" ADD CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" ADD CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_B_fkey" FOREIGN KEY ("B") REFERENCES "TarifDanLayanan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -47,6 +47,23 @@ model AppMenuChild {
|
|||||||
appMenuId String?
|
appMenuId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= FILE STORAGE ========================================= //
|
||||||
|
|
||||||
|
model FileStorage {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
realName String
|
||||||
|
path String
|
||||||
|
mimeType String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime?
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
link String
|
||||||
|
Berita Berita[]
|
||||||
|
PotensiDesa PotensiDesa[]
|
||||||
|
}
|
||||||
|
|
||||||
//========================================= MENU PPID ========================================= //
|
//========================================= MENU PPID ========================================= //
|
||||||
// ========================================= VISI MISI PPID ========================================= //
|
// ========================================= VISI MISI PPID ========================================= //
|
||||||
model VisiMisiPPID {
|
model VisiMisiPPID {
|
||||||
@@ -208,22 +225,52 @@ model GrafikBerdasarkanUmur {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= MENU DESA ========================================= //
|
// ========================================= MENU DESA ========================================= //
|
||||||
|
// ========================================= PROFILE DESA ========================================= //
|
||||||
|
model ProfileDesa {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sejarah String @db.Text
|
||||||
|
visi String @db.Text
|
||||||
|
misi String @db.Text
|
||||||
|
lambang String @db.Text
|
||||||
|
maskot String @db.Text
|
||||||
|
ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id])
|
||||||
|
profilPerbekelId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model ProfilPerbekel {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
biodata String @db.Text
|
||||||
|
pengalaman String @db.Text
|
||||||
|
pengalamanOrganisasi String @db.Text
|
||||||
|
programUnggulan String @db.Text
|
||||||
|
ProfileDesa ProfileDesa[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================= BERITA ========================================= //
|
// ========================================= BERITA ========================================= //
|
||||||
model Berita {
|
model Berita {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
judul String
|
judul String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
image String
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
content String @db.Text
|
content String @db.Text
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
KatagoryBerita KatagoryBerita? @relation(fields: [katagoryBeritaId], references: [id])
|
kategoriBerita KategoriBerita? @relation(fields: [kategoriBeritaId], references: [id])
|
||||||
katagoryBeritaId String?
|
kategoriBeritaId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
model KatagoryBerita {
|
model KategoriBerita {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
beritas Berita[]
|
beritas Berita[]
|
||||||
@@ -233,6 +280,21 @@ model KatagoryBerita {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= POTENSI DESA ========================================= //
|
||||||
|
model PotensiDesa {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String
|
||||||
|
kategori String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
content String @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================= PENGUMUMAN ========================================= //
|
// ========================================= PENGUMUMAN ========================================= //
|
||||||
model Pengumuman {
|
model Pengumuman {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 275 KiB |
409
prisma/seed.ts
@@ -1,196 +1,229 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from "@/lib/prisma";
|
||||||
import categoryPengumuman from './data/category-pengumuman.json'
|
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||||
import katagoryBerita from './data/katagory-berita.json'
|
import kategoriBerita from "./data/kategori-berita.json";
|
||||||
import caraMemperolehInformasi from './data/list-caraMemperolehInformasi.json'
|
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
||||||
import caraMemperolehSalinanInformasi from './data/list-caraMemperolehSalinanInformasi.json'
|
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
|
||||||
import jenisInformasiDiminta from './data/list-jenisInfromasi.json'
|
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
||||||
import layanan from './data/list-layanan.json'
|
import layanan from "./data/list-layanan.json";
|
||||||
import potensi from './data/list-potensi.json'
|
import potensi from "./data/list-potensi.json";
|
||||||
import profilePPID from './data/ppid/profile-ppid/profilePPid.json'
|
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||||
import visiMisiPPID from './data/ppid/visi-misi-ppid/visimisiPPID.json'
|
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||||
import dasarHukumPPID from './data/ppid/dasar-hukum-ppid/dasarhukumPPID.json'
|
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { mkdir, writeFile } from "fs/promises";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const l of layanan) {
|
for (const l of layanan) {
|
||||||
await prisma.layanan.upsert({
|
await prisma.layanan.upsert({
|
||||||
where: {
|
where: {
|
||||||
name: l.name
|
name: l.name,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
name: l.name
|
name: l.name,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
name: l.name
|
name: l.name,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("layanan success ...");
|
||||||
|
|
||||||
|
for (const p of potensi) {
|
||||||
|
await prisma.potensi.upsert({
|
||||||
|
where: {
|
||||||
|
name: p.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: p.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: p.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("potensi success ...");
|
||||||
|
|
||||||
|
for (const k of kategoriBerita) {
|
||||||
|
await prisma.kategoriBerita.upsert({
|
||||||
|
where: {
|
||||||
|
name: k.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: k.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: k.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("kategori berita success ...");
|
||||||
|
|
||||||
|
for (const c of categoryPengumuman) {
|
||||||
|
await prisma.categoryPengumuman.upsert({
|
||||||
|
where: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("category pengumuman success ...");
|
||||||
|
|
||||||
|
for (const j of jenisInformasiDiminta) {
|
||||||
|
await prisma.jenisInformasiDiminta.upsert({
|
||||||
|
where: {
|
||||||
|
name: j.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: j.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: j.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("jenis informasi diminta success ...");
|
||||||
|
|
||||||
|
for (const c of caraMemperolehInformasi) {
|
||||||
|
await prisma.caraMemperolehInformasi.upsert({
|
||||||
|
where: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("cara memperoleh informasi success ...");
|
||||||
|
|
||||||
|
for (const c of caraMemperolehSalinanInformasi) {
|
||||||
|
await prisma.caraMemperolehSalinanInformasi.upsert({
|
||||||
|
where: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("cara memperoleh salinan informasi success ...");
|
||||||
|
|
||||||
|
const seedProfilePPID = async () => {
|
||||||
|
const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid")
|
||||||
|
|
||||||
|
// Buat folder hanya jika belum ada
|
||||||
|
if (!fs.existsSync(targetDir)) {
|
||||||
|
await mkdir(targetDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("layanan success ...")
|
|
||||||
|
|
||||||
for (const p of potensi) {
|
|
||||||
await prisma.potensi.upsert({
|
|
||||||
where: {
|
|
||||||
name: p.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: p.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: p.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("potensi success ...")
|
|
||||||
|
|
||||||
for (const k of katagoryBerita) {
|
|
||||||
await prisma.katagoryBerita.upsert({
|
|
||||||
where: {
|
|
||||||
name: k.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: k.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: k.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("katagory berita success ...")
|
|
||||||
|
|
||||||
for (const c of categoryPengumuman) {
|
|
||||||
await prisma.categoryPengumuman.upsert({
|
|
||||||
where: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: c.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("category pengumuman success ...")
|
|
||||||
|
|
||||||
for (const j of jenisInformasiDiminta) {
|
|
||||||
await prisma.jenisInformasiDiminta.upsert({
|
|
||||||
where: {
|
|
||||||
name: j.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: j.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: j.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log("jenis informasi diminta success ...")
|
|
||||||
|
|
||||||
for (const c of caraMemperolehInformasi) {
|
|
||||||
await prisma.caraMemperolehInformasi.upsert({
|
|
||||||
where: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: c.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log("cara memperoleh informasi success ...")
|
|
||||||
|
|
||||||
for (const c of caraMemperolehSalinanInformasi) {
|
|
||||||
await prisma.caraMemperolehSalinanInformasi.upsert({
|
|
||||||
where: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: c.name
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: c.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log("cara memperoleh salinan informasi success ...")
|
|
||||||
|
|
||||||
for (const c of profilePPID) {
|
for (const c of profilePPID) {
|
||||||
await prisma.profilePPID.upsert({
|
let finalImageUrl = c.imageUrl
|
||||||
where: {
|
|
||||||
id: c.id
|
if (c.imageUrl.startsWith("/uploads/seeded-images/")) {
|
||||||
},
|
const filename = path.basename(c.imageUrl)
|
||||||
update: {
|
const seedImagePath = path.resolve("prisma", "seed-images", filename)
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
const targetFilename = `${uuid()}_${filename}`
|
||||||
riwayat: c.riwayat,
|
const targetPath = path.join(targetDir, targetFilename)
|
||||||
pengalaman: c.pengalaman,
|
|
||||||
unggulan: c.unggulan,
|
const buffer = fs.readFileSync(seedImagePath)
|
||||||
imageUrl: c.imageUrl
|
await writeFile(targetPath, buffer)
|
||||||
},
|
|
||||||
create: {
|
finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}`
|
||||||
id: c.id,
|
}
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
await prisma.profilePPID.upsert({
|
||||||
riwayat: c.riwayat,
|
where: { id: c.id },
|
||||||
pengalaman: c.pengalaman,
|
update: {
|
||||||
unggulan: c.unggulan,
|
name: c.name,
|
||||||
imageUrl: c.imageUrl
|
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("profile PPID success ...")
|
|
||||||
|
console.log("✅ profilePPID seeded from JSON with image copying")
|
||||||
for (const v of visiMisiPPID) {
|
}
|
||||||
await prisma.visiMisiPPID.upsert({
|
|
||||||
where: {
|
await seedProfilePPID()
|
||||||
id: v.id,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
misi: v.misi,
|
|
||||||
visi: v.visi
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: v.id,
|
|
||||||
misi: v.misi,
|
|
||||||
visi: v.visi
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log("visi misi PPID success ...")
|
|
||||||
|
|
||||||
for (const v of dasarHukumPPID) {
|
for (const v of visiMisiPPID) {
|
||||||
await prisma.dasarHukumPPID.upsert({
|
await prisma.visiMisiPPID.upsert({
|
||||||
where: {
|
where: {
|
||||||
id: v.id,
|
id: v.id,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
judul: v.judul,
|
misi: v.misi,
|
||||||
content: v.content
|
visi: v.visi,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: v.id,
|
id: v.id,
|
||||||
judul: v.judul,
|
misi: v.misi,
|
||||||
content: v.content
|
visi: v.visi,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
console.log("dasar hukum PPID success ...")
|
console.log("visi misi PPID success ...");
|
||||||
|
|
||||||
})().then(() => prisma.$disconnect()).catch((e) => {
|
for (const v of dasarHukumPPID) {
|
||||||
console.error(e)
|
await prisma.dasarHukumPPID.upsert({
|
||||||
prisma.$disconnect()
|
where: {
|
||||||
|
id: v.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
judul: v.judul,
|
||||||
|
content: v.content,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: v.id,
|
||||||
|
judul: v.judul,
|
||||||
|
content: v.content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("dasar hukum PPID success ...");
|
||||||
|
|
||||||
|
|
||||||
|
})()
|
||||||
|
.then(() => prisma.$disconnect())
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
prisma.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("exit", () => {
|
||||||
|
prisma.$disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', () => {
|
process.on("SIGINT", () => {
|
||||||
prisma.$disconnect()
|
prisma.$disconnect();
|
||||||
})
|
process.exit(0);
|
||||||
|
});
|
||||||
process.on('SIGINT', () => {
|
|
||||||
prisma.$disconnect()
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
BIN
public/perbekel.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
public/uploads/profile-ppid/1_1747885424609_budaya-1.jpg
Normal file
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 275 KiB |
85
src/app/admin/(dashboard)/_com/createEditor.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// TestEditor.tsx
|
||||||
|
import { RichTextEditor, Link } from '@mantine/tiptap';
|
||||||
|
import { useEditor } from '@tiptap/react';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import TextAlign from '@tiptap/extension-text-align';
|
||||||
|
import Superscript from '@tiptap/extension-superscript';
|
||||||
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
|
|
||||||
|
type CreateEditorProps = {
|
||||||
|
value: string;
|
||||||
|
onChange: (content: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CreateEditor({ value, onChange }: CreateEditorProps) {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link,
|
||||||
|
Superscript,
|
||||||
|
SubScript,
|
||||||
|
Highlight,
|
||||||
|
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||||
|
],
|
||||||
|
content: value,
|
||||||
|
onUpdate: () => {
|
||||||
|
if (editor) {
|
||||||
|
onChange(editor.getHTML());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RichTextEditor editor={editor}>
|
||||||
|
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Bold />
|
||||||
|
<RichTextEditor.Italic />
|
||||||
|
<RichTextEditor.Underline />
|
||||||
|
<RichTextEditor.Strikethrough />
|
||||||
|
<RichTextEditor.ClearFormatting />
|
||||||
|
<RichTextEditor.Highlight />
|
||||||
|
<RichTextEditor.Code />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.H1 />
|
||||||
|
<RichTextEditor.H2 />
|
||||||
|
<RichTextEditor.H3 />
|
||||||
|
<RichTextEditor.H4 />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Blockquote />
|
||||||
|
<RichTextEditor.Hr />
|
||||||
|
<RichTextEditor.BulletList />
|
||||||
|
<RichTextEditor.OrderedList />
|
||||||
|
<RichTextEditor.Subscript />
|
||||||
|
<RichTextEditor.Superscript />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link />
|
||||||
|
<RichTextEditor.Unlink />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.AlignLeft />
|
||||||
|
<RichTextEditor.AlignCenter />
|
||||||
|
<RichTextEditor.AlignJustify />
|
||||||
|
<RichTextEditor.AlignRight />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Undo />
|
||||||
|
<RichTextEditor.Redo />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
</RichTextEditor.Toolbar>
|
||||||
|
|
||||||
|
<RichTextEditor.Content />
|
||||||
|
</RichTextEditor>
|
||||||
|
);
|
||||||
|
}
|
||||||
102
src/app/admin/(dashboard)/_com/editEditor.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
'use client'
|
||||||
|
import { RichTextEditor, Link } from '@mantine/tiptap';
|
||||||
|
import { useEditor } from '@tiptap/react';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import TextAlign from '@tiptap/extension-text-align';
|
||||||
|
import Superscript from '@tiptap/extension-superscript';
|
||||||
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
type EditEditorProps = {
|
||||||
|
value: string;
|
||||||
|
onChange: (content: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditEditor({ value, onChange }: EditEditorProps) {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link,
|
||||||
|
Superscript,
|
||||||
|
SubScript,
|
||||||
|
Highlight,
|
||||||
|
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||||
|
],
|
||||||
|
content: value,
|
||||||
|
// Hapus `immediatelyRender` dan `onMount`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sinkronisasi konten saat `value` berubah
|
||||||
|
useEffect(() => {
|
||||||
|
if (editor && value !== editor.getHTML()) {
|
||||||
|
editor.commands.setContent(value);
|
||||||
|
}
|
||||||
|
}, [value, editor]);
|
||||||
|
|
||||||
|
// Sinkronisasi konten ke parent saat diubah
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
const updateHandler = () => onChange(editor.getHTML());
|
||||||
|
editor.on('update', updateHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
editor.off('update', updateHandler);
|
||||||
|
};
|
||||||
|
}, [editor, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RichTextEditor editor={editor}>
|
||||||
|
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">
|
||||||
|
{/* Toolbar seperti sebelumnya */}
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Bold />
|
||||||
|
<RichTextEditor.Italic />
|
||||||
|
<RichTextEditor.Underline />
|
||||||
|
<RichTextEditor.Strikethrough />
|
||||||
|
<RichTextEditor.ClearFormatting />
|
||||||
|
<RichTextEditor.Highlight />
|
||||||
|
<RichTextEditor.Code />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.H1 />
|
||||||
|
<RichTextEditor.H2 />
|
||||||
|
<RichTextEditor.H3 />
|
||||||
|
<RichTextEditor.H4 />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Blockquote />
|
||||||
|
<RichTextEditor.Hr />
|
||||||
|
<RichTextEditor.BulletList />
|
||||||
|
<RichTextEditor.OrderedList />
|
||||||
|
<RichTextEditor.Subscript />
|
||||||
|
<RichTextEditor.Superscript />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link />
|
||||||
|
<RichTextEditor.Unlink />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.AlignLeft />
|
||||||
|
<RichTextEditor.AlignCenter />
|
||||||
|
<RichTextEditor.AlignJustify />
|
||||||
|
<RichTextEditor.AlignRight />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Undo />
|
||||||
|
<RichTextEditor.Redo />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
</RichTextEditor.Toolbar>
|
||||||
|
|
||||||
|
<RichTextEditor.Content />
|
||||||
|
</RichTextEditor>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/app/admin/(dashboard)/_com/header.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Grid, GridCol, Paper, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconSearch } from '@tabler/icons-react'; // Sesuaikan jika kamu pakai icon lain
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
|
|
||||||
|
const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }: { title: string, placeholder?: string, searchIcon?: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
|
<Title order={3}>{title}</Title>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
|
<Paper radius={"lg"} bg={colors['white-1']}>
|
||||||
|
<TextInput
|
||||||
|
radius="lg"
|
||||||
|
placeholder={placeholder}
|
||||||
|
leftSection={searchIcon}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderSearch;
|
||||||
30
src/app/admin/(dashboard)/_com/judulList.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Grid, GridCol, Button, Text } from '@mantine/core';
|
||||||
|
import { IconCircleDashedPlus } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const JudulList = ({ title = "", href = "#" }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleNavigate = () => {
|
||||||
|
router.push(href);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid align="center" mb={10}>
|
||||||
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>{title}</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 1 }} ta="right">
|
||||||
|
<Button onClick={handleNavigate} bg={colors['blue-button']}>
|
||||||
|
<IconCircleDashedPlus size={25} />
|
||||||
|
</Button>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JudulList;
|
||||||
41
src/app/admin/(dashboard)/_com/jusulListTab.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Grid, GridCol, Button, Text, Paper, TextInput } from '@mantine/core';
|
||||||
|
import { IconCircleDashedPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const JudulListTab = ({ title = "", href = "#", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleNavigate = () => {
|
||||||
|
router.push(href);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid mb={10}>
|
||||||
|
<GridCol span={{ base: 12, md: 8 }}>
|
||||||
|
<Text fz={{base: "md", md: "xl"}} fw={"bold"}>{title}</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 9, md: 3}} ta="right">
|
||||||
|
<Paper radius={"lg"} bg={colors['white-1']}>
|
||||||
|
<TextInput
|
||||||
|
radius="lg"
|
||||||
|
placeholder={placeholder}
|
||||||
|
leftSection={searchIcon}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 3, md: 1}} ta="right">
|
||||||
|
<Button onClick={handleNavigate} bg={colors['blue-button']}>
|
||||||
|
<IconCircleDashedPlus size={25} />
|
||||||
|
</Button>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JudulListTab;
|
||||||
36
src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// components/modal/ModalKonfirmasiHapus.tsx
|
||||||
|
import colors from "@/con/colors"
|
||||||
|
import { Modal, Text, Button, Flex } from "@mantine/core"
|
||||||
|
|
||||||
|
interface ModalKonfirmasiHapusProps {
|
||||||
|
opened: boolean
|
||||||
|
loading?: boolean
|
||||||
|
onClose: () => void
|
||||||
|
onConfirm: () => void
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModalKonfirmasiHapus({
|
||||||
|
opened,
|
||||||
|
loading = false,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
text,
|
||||||
|
}: ModalKonfirmasiHapusProps) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Konfirmasi Hapus"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Text mb="md">{text}</Text>
|
||||||
|
<Flex justify="flex-end" gap="sm">
|
||||||
|
<Button style={{color: "white"}} bg={colors['blue-button']} variant="default" onClick={onClose}>Batal</Button>
|
||||||
|
<Button color="red" onClick={onConfirm} loading={loading}>
|
||||||
|
Yakin Hapus
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// 1. Schema validasi dengan Zod
|
||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
image: z.string().url().min(3, "Image minimal 3 karakter"),
|
|
||||||
content: z.string().min(3, "Content minimal 3 karakter"),
|
content: z.string().min(3, "Content minimal 3 karakter"),
|
||||||
katagoryBeritaId: z.string().nonempty(),
|
kategoriBeritaId: z.string().nonempty(),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 2. Default value form berita (hindari uncontrolled input)
|
||||||
|
const defaultForm = {
|
||||||
|
judul: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
content: "",
|
||||||
|
kategoriBeritaId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Kategori proxy
|
||||||
const category = proxy({
|
const category = proxy({
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: [] as Prisma.KategoriBeritaGetPayload<{ omit: { isActive: true } }>[],
|
||||||
| null
|
|
||||||
| Prisma.KatagoryBeritaGetPayload<{ omit: { isActive: true } }>[],
|
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.desa.berita.category["find-many"].get();
|
const res = await ApiFetch.api.desa.berita.category["find-many"].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
@@ -27,23 +35,12 @@ const category = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type BeritaForm = Prisma.BeritaGetPayload<{
|
// 4. Berita proxy
|
||||||
select: {
|
|
||||||
judul: true;
|
|
||||||
deskripsi: true;
|
|
||||||
image: true;
|
|
||||||
content: true;
|
|
||||||
katagoryBeritaId: true;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const berita = proxy({
|
const berita = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {} as BeritaForm,
|
form: { ...defaultForm }, // ✅ ini kunci fix-nya
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
berita.create.form.image =
|
|
||||||
"https://www.shutterstock.com/image-vector/lower-news-live-streaming-breaking-600nw-2535984111.jpg";
|
|
||||||
const cek = templateForm.safeParse(berita.create.form);
|
const cek = templateForm.safeParse(berita.create.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
@@ -51,6 +48,7 @@ const berita = proxy({
|
|||||||
.join("\n")}] required`;
|
.join("\n")}] required`;
|
||||||
return toast.error(err);
|
return toast.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
berita.create.loading = true;
|
berita.create.loading = true;
|
||||||
const res = await ApiFetch.api.desa.berita["create"].post(
|
const res = await ApiFetch.api.desa.berita["create"].post(
|
||||||
@@ -58,33 +56,200 @@ const berita = proxy({
|
|||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
berita.findMany.load();
|
berita.findMany.load();
|
||||||
return toast.success("succes create");
|
return toast.success("Berita berhasil disimpan!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return toast.error("failed create");
|
return toast.error("Gagal menyimpan berita");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log((error as Error).message);
|
console.log((error as Error).message);
|
||||||
} finally {
|
} finally {
|
||||||
berita.create.loading = false;
|
berita.create.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
resetForm() {
|
||||||
|
berita.create.form = { ...defaultForm };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.BeritaGetPayload<{ omit: { isActive: true } }>[]
|
| Prisma.BeritaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
kategoriBerita: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.desa.berita["find-many"].get();
|
const res = await ApiFetch.api.desa.berita["find-many"].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
berita.findMany.data = (res.data?.data as any) ?? [];
|
berita.findMany.data = (res.data?.data ) ?? [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.BeritaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
kategoriBerita: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/berita/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
berita.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch berita:', res.statusText);
|
||||||
|
berita.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching berita:', error);
|
||||||
|
berita.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
berita.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/desa/berita/delete/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Berita berhasil dihapus");
|
||||||
|
await berita.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus berita");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus berita");
|
||||||
|
} finally {
|
||||||
|
berita.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/berita/${id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
judul: data.judul,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
content: data.content,
|
||||||
|
kategoriBeritaId: data.kategoriBeritaId || "",
|
||||||
|
imageId: data.imageId || "",
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading berita:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(berita.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
berita.edit.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/desa/berita/${this.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
judul: this.form.judul,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
content: this.form.content,
|
||||||
|
kategoriBeritaId: this.form.kategoriBeritaId || null,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update berita");
|
||||||
|
await berita.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update berita");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating berita:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update berita");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
berita.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
berita.edit.id = "";
|
||||||
|
berita.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 5. State global
|
||||||
const stateDashboardBerita = proxy({
|
const stateDashboardBerita = proxy({
|
||||||
category,
|
category,
|
||||||
berita,
|
berita,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stateDashboardBerita;
|
export default stateDashboardBerita;
|
||||||
|
|||||||
223
src/app/admin/(dashboard)/_state/desa/potensi.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
deskripsi: z.string().min(1).max(50),
|
||||||
|
kategori: z.string().min(1).max(50),
|
||||||
|
imageId: z.string().min(1).max(50),
|
||||||
|
content: z.string().min(1).max(5000),
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
kategori: "",
|
||||||
|
imageId: "",
|
||||||
|
content: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
const potensiDesaState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(potensiDesaState.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
potensiDesaState.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.potensi["create"].post(potensiDesaState.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
potensiDesaState.findMany.load();
|
||||||
|
return toast.success("Potensi berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan potensi");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
potensiDesaState.create.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PotensiDesaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
}
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.desa.potensi["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
potensiDesaState.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PotensiDesaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
}
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/potensi/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
potensiDesaState.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch potensi:', res.statusText);
|
||||||
|
potensiDesaState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching potensi:', error);
|
||||||
|
potensiDesaState.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
potensiDesaState.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/desa/potensi/del/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Potensi berhasil dihapus");
|
||||||
|
await potensiDesaState.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus potensi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus potensi");
|
||||||
|
} finally {
|
||||||
|
potensiDesaState.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/desa/potensi/${id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
kategori: data.kategori,
|
||||||
|
imageId: data.imageId || "",
|
||||||
|
content: data.content,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading potensi:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(potensiDesaState.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
potensiDesaState.edit.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/desa/potensi/${this.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
kategori: this.form.kategori,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
content: this.form.content,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update potensi");
|
||||||
|
await potensiDesaState.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update potensi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating potensi:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update potensi");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
potensiDesaState.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
potensiDesaState.edit.id = "";
|
||||||
|
potensiDesaState.edit.form = { ...defaultForm };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default potensiDesaState
|
||||||
|
|
||||||
|
|
||||||
239
src/app/admin/(dashboard)/_state/desa/profile.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* Sejarah */
|
||||||
|
const templateFormSejarahForm = z.object({
|
||||||
|
sejarah: z.string().min(3, "Sejarah minimal 3 karakter"),
|
||||||
|
})
|
||||||
|
|
||||||
|
type SejarahForm = Prisma.ProfileDesaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
sejarah: true;
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
|
||||||
|
const Sejarah = proxy({
|
||||||
|
findById: {
|
||||||
|
data: null as SejarahForm | null,
|
||||||
|
loading: false,
|
||||||
|
initialize() {
|
||||||
|
Sejarah.findById.data = {
|
||||||
|
id: "",
|
||||||
|
sejarah: "",
|
||||||
|
} as SejarahForm;
|
||||||
|
},
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
Sejarah.findById.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
|
||||||
|
query: { id },
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
Sejarah.findById.data = {
|
||||||
|
id: id,
|
||||||
|
sejarah: res.data?.data?.sejarah ?? ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal mengambil data sejarah");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data sejarah");
|
||||||
|
} finally {
|
||||||
|
Sejarah.findById.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
loading: false,
|
||||||
|
async save(data: SejarahForm) {
|
||||||
|
const cek = templateFormSejarahForm.safeParse(data);
|
||||||
|
if (!cek.success) {
|
||||||
|
const errors = cek.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Sejarah.update.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile.sejarah["update"].post(data);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Berhasil update sejarah");
|
||||||
|
await Sejarah.findById.load(data.id);
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal update sejarah");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat update sejarah");
|
||||||
|
} finally {
|
||||||
|
Sejarah.update.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Visi Misi Desa */
|
||||||
|
const templateFormVisiForm = z.object({
|
||||||
|
visi: z.string().min(3, "Visi minimal 3 karakter"),
|
||||||
|
misi: z.string().min(3, "Misi minimal 3 karakter")
|
||||||
|
})
|
||||||
|
|
||||||
|
type VisiMisiDesaForm = Prisma.ProfileDesaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
visi: true;
|
||||||
|
misi: true;
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
|
||||||
|
const VisiMisiDesa = proxy({
|
||||||
|
findById: {
|
||||||
|
data: null as VisiMisiDesaForm | null,
|
||||||
|
loading: false,
|
||||||
|
initialize() {
|
||||||
|
VisiMisiDesa.findById.data = {
|
||||||
|
id: "",
|
||||||
|
visi: "",
|
||||||
|
misi: ""
|
||||||
|
} as VisiMisiDesaForm;
|
||||||
|
},
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
VisiMisiDesa.findById.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
|
||||||
|
query: { id },
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
VisiMisiDesa.findById.data = {
|
||||||
|
id: id,
|
||||||
|
visi: res.data?.data?.visi ?? "",
|
||||||
|
misi: res.data?.data?.misi ?? ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal mengambil data visi misi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data visi misi");
|
||||||
|
} finally {
|
||||||
|
VisiMisiDesa.findById.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
loading: false,
|
||||||
|
async save(data: VisiMisiDesaForm) {
|
||||||
|
const cek = templateFormVisiForm.safeParse(data);
|
||||||
|
if (!cek.success) {
|
||||||
|
const errors = cek.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
VisiMisiDesa.update.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile.visimisiDesa["update"].post(data);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Berhasil update visi misi");
|
||||||
|
await VisiMisiDesa.findById.load(data.id);
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal update visi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat update visi misi");
|
||||||
|
} finally {
|
||||||
|
VisiMisiDesa.update.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/* Lambang Desa */
|
||||||
|
const templateFormLambangDesaForm = z.object({
|
||||||
|
lambang: z.string().min(3, "Lambang minimal 3 karakter"),
|
||||||
|
})
|
||||||
|
|
||||||
|
type LambangDesaForm = Prisma.ProfileDesaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
lambang: true;
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
|
||||||
|
const LambangDesa = proxy({
|
||||||
|
findById: {
|
||||||
|
data: null as LambangDesaForm | null,
|
||||||
|
loading: false,
|
||||||
|
initialize() {
|
||||||
|
LambangDesa.findById.data = {
|
||||||
|
id: "",
|
||||||
|
lambang: "",
|
||||||
|
} as LambangDesaForm;
|
||||||
|
},
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
LambangDesa.findById.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
|
||||||
|
query: { id },
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
LambangDesa.findById.data = {
|
||||||
|
id: id,
|
||||||
|
lambang: res.data?.data?.lambang ?? ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal mengambil data lambang desa");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data lambang desa");
|
||||||
|
} finally {
|
||||||
|
LambangDesa.findById.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
loading: false,
|
||||||
|
async save(data: LambangDesaForm) {
|
||||||
|
const cek = templateFormLambangDesaForm.safeParse(data);
|
||||||
|
if (!cek.success) {
|
||||||
|
const errors = cek.error.issues
|
||||||
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
|
.join(", ");
|
||||||
|
toast.error(`Form tidak valid: ${errors}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
LambangDesa.update.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa.profile.lambangDesa["update"].post(data);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Berhasil update lambang desa");
|
||||||
|
await LambangDesa.findById.load(data.id);
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal update lambang desa");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error((error as Error).message);
|
||||||
|
toast.error("Terjadi kesalahan saat update lambang desa");
|
||||||
|
} finally {
|
||||||
|
LambangDesa.update.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateProfileDesa = {
|
||||||
|
Sejarah,
|
||||||
|
VisiMisiDesa,
|
||||||
|
LambangDesa,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default stateProfileDesa;
|
||||||
@@ -56,6 +56,7 @@ const caraMemperolehSalinanInformasi = proxy({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log(caraMemperolehSalinanInformasi)
|
||||||
|
|
||||||
type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{
|
type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
@@ -70,12 +71,12 @@ type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const permohonanInformasiPublikForm = proxy({
|
const statepermohonanInformasiPublik = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {} as PermohonanInformasiPublikForm,
|
form: {} as PermohonanInformasiPublikForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create(){
|
async create(){
|
||||||
const cek = templateForm.safeParse(permohonanInformasiPublikForm.create.form);
|
const cek = templateForm.safeParse(statepermohonanInformasiPublik.create.form);
|
||||||
if(!cek.success) {
|
if(!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -83,38 +84,42 @@ const permohonanInformasiPublikForm = proxy({
|
|||||||
return toast.error(err);
|
return toast.error(err);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
permohonanInformasiPublikForm.create.loading = true;
|
statepermohonanInformasiPublik.create.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(permohonanInformasiPublikForm.create.form);
|
const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(statepermohonanInformasiPublik.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
permohonanInformasiPublikForm.findMany.load();
|
statepermohonanInformasiPublik.findMany.load();
|
||||||
return toast.success("success create");
|
return toast.success("success create");
|
||||||
}
|
}
|
||||||
return toast.error("failed create");
|
return toast.error("failed create");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log((error as Error).message);
|
console.log((error as Error).message);
|
||||||
} finally {
|
} finally {
|
||||||
permohonanInformasiPublikForm.create.loading = false;
|
statepermohonanInformasiPublik.create.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.PermohonanInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
|
| Prisma.PermohonanInformasiPublikGetPayload<{ include: {
|
||||||
|
caraMemperolehSalinanInformasi: true,
|
||||||
|
jenisInformasiDiminta: true,
|
||||||
|
caraMemperolehInformasi: true,
|
||||||
|
} }>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get();
|
const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
permohonanInformasiPublikForm.findMany.data = res.data?.data ?? [];
|
statepermohonanInformasiPublik.findMany.data = res.data?.data ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const statePermohonanInformasi = proxy({
|
const statepermohonanInformasiPublikForm = proxy({
|
||||||
permohonanInformasiPublikForm,
|
statepermohonanInformasiPublik,
|
||||||
jenisInformasiDiminta,
|
jenisInformasiDiminta,
|
||||||
caraMemperolehInformasi,
|
caraMemperolehInformasi,
|
||||||
caraMemperolehSalinanInformasi
|
caraMemperolehSalinanInformasi,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default statePermohonanInformasi;
|
export default statepermohonanInformasiPublikForm;
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ type PermohonanKeberatanInformasiForm = Prisma.FormulirPermohonanKeberatanGetPay
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const permohonanKeberatanInformasiForm = proxy({
|
const permohonanKeberatanInformasi = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {} as PermohonanKeberatanInformasiForm,
|
form: {} as PermohonanKeberatanInformasiForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create(){
|
async create(){
|
||||||
const cek = templateForm.safeParse(permohonanKeberatanInformasiForm.create.form);
|
const cek = templateForm.safeParse(permohonanKeberatanInformasi.create.form);
|
||||||
if(!cek.success) {
|
if(!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")}`)
|
||||||
@@ -33,17 +33,17 @@ const permohonanKeberatanInformasiForm = proxy({
|
|||||||
return toast.error(err);
|
return toast.error(err);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
permohonanKeberatanInformasiForm.create.loading = true;
|
permohonanKeberatanInformasi.create.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasiForm.create.form);
|
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasi.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
permohonanKeberatanInformasiForm.findMany.load();
|
permohonanKeberatanInformasi.findMany.load();
|
||||||
return toast.success("success create");
|
return toast.success("success create");
|
||||||
}
|
}
|
||||||
return toast.error("failed create");
|
return toast.error("failed create");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log((error as Error).message);
|
console.log((error as Error).message);
|
||||||
} finally {
|
} finally {
|
||||||
permohonanKeberatanInformasiForm.create.loading = false;
|
permohonanKeberatanInformasi.create.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -54,15 +54,11 @@ const permohonanKeberatanInformasiForm = proxy({
|
|||||||
async load() {
|
async load() {
|
||||||
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get();
|
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get();
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
permohonanKeberatanInformasiForm.findMany.data = res.data?.data ?? [];
|
permohonanKeberatanInformasi.findMany.data = res.data?.data ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const statePermohonanKeberatan = proxy({
|
export default permohonanKeberatanInformasi;
|
||||||
permohonanKeberatanInformasiForm,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default statePermohonanKeberatan;
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { toast } from "react-toastify";
|
|||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// Schema validasi form
|
/**
|
||||||
|
* Schema validasi form ProfilePPID menggunakan Zod.
|
||||||
|
*/
|
||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||||
biodata: z.string().min(3, "Biodata minimal 3 karakter"),
|
biodata: z.string().min(3, "Biodata minimal 3 karakter"),
|
||||||
@@ -13,7 +15,9 @@ const templateForm = z.object({
|
|||||||
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
|
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Type ambil dari Prisma
|
/**
|
||||||
|
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
|
||||||
|
*/
|
||||||
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
id: true;
|
id: true;
|
||||||
@@ -26,11 +30,23 @@ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Proxy utama
|
/**
|
||||||
|
* State utama ProfilePPID yang mencakup fitur:
|
||||||
|
* - Ambil data berdasarkan ID
|
||||||
|
* - Update data
|
||||||
|
* - Upload gambar
|
||||||
|
*/
|
||||||
const stateProfilePPID = proxy({
|
const stateProfilePPID = proxy({
|
||||||
|
/**
|
||||||
|
* Bagian untuk ambil data berdasarkan ID
|
||||||
|
*/
|
||||||
findById: {
|
findById: {
|
||||||
data: null as ProfilePPIDForm | null,
|
data: null as ProfilePPIDForm | null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inisialisasi data kosong ke dalam state.
|
||||||
|
*/
|
||||||
initialize() {
|
initialize() {
|
||||||
stateProfilePPID.findById.data = {
|
stateProfilePPID.findById.data = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -39,15 +55,21 @@ const stateProfilePPID = proxy({
|
|||||||
riwayat: '',
|
riwayat: '',
|
||||||
pengalaman: '',
|
pengalaman: '',
|
||||||
unggulan: '',
|
unggulan: '',
|
||||||
imageUrl:''
|
imageUrl: ''
|
||||||
} as ProfilePPIDForm;
|
} as ProfilePPIDForm;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mengambil data profil berdasarkan ID.
|
||||||
|
* @param {string} id - ID dari profile yang ingin diambil.
|
||||||
|
*/
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
stateProfilePPID.findById.loading = true;
|
stateProfilePPID.findById.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
|
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
|
||||||
query: { id },
|
query: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
stateProfilePPID.findById.data = res.data?.data ?? null;
|
stateProfilePPID.findById.data = res.data?.data ?? null;
|
||||||
} else {
|
} else {
|
||||||
@@ -62,10 +84,19 @@ const stateProfilePPID = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bagian untuk update data profile
|
||||||
|
*/
|
||||||
update: {
|
update: {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Melakukan validasi dan menyimpan perubahan data profile ke server.
|
||||||
|
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
|
||||||
|
*/
|
||||||
async save(data: ProfilePPIDForm) {
|
async save(data: ProfilePPIDForm) {
|
||||||
const cek = templateForm.safeParse(data);
|
const cek = templateForm.safeParse(data);
|
||||||
|
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const errors = cek.error.issues
|
const errors = cek.error.issues
|
||||||
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||||
@@ -77,6 +108,7 @@ const stateProfilePPID = proxy({
|
|||||||
try {
|
try {
|
||||||
stateProfilePPID.update.loading = true;
|
stateProfilePPID.update.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
|
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success("Berhasil update profile");
|
toast.success("Berhasil update profile");
|
||||||
await stateProfilePPID.findById.load(data.id);
|
await stateProfilePPID.findById.load(data.id);
|
||||||
@@ -91,13 +123,24 @@ const stateProfilePPID = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bagian untuk upload gambar profil
|
||||||
|
*/
|
||||||
uploadImage: {
|
uploadImage: {
|
||||||
loading: false,
|
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) {
|
async save(file: File, id: string) {
|
||||||
if (!file || !id) {
|
if (!file || !id) {
|
||||||
toast.error("File atau ID harus disertakan");
|
toast.error("File atau ID harus disertakan");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
stateProfilePPID.uploadImage.loading = true;
|
stateProfilePPID.uploadImage.loading = true;
|
||||||
|
|
||||||
@@ -106,6 +149,7 @@ const stateProfilePPID = proxy({
|
|||||||
form.append("id", id);
|
form.append("id", id);
|
||||||
|
|
||||||
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
|
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success("Berhasil mengunggah gambar");
|
toast.success("Berhasil mengunggah gambar");
|
||||||
await stateProfilePPID.findById.load(id);
|
await stateProfilePPID.findById.load(id);
|
||||||
@@ -118,8 +162,11 @@ const stateProfilePPID = proxy({
|
|||||||
} finally {
|
} finally {
|
||||||
stateProfilePPID.uploadImage.loading = false;
|
stateProfilePPID.uploadImage.loading = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
|
||||||
|
*/
|
||||||
export default stateProfilePPID;
|
export default stateProfilePPID;
|
||||||
|
|||||||
93
src/app/admin/(dashboard)/desa/_com/desaEditor.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Button, Stack } from '@mantine/core';
|
||||||
|
import { Link, RichTextEditor } from '@mantine/tiptap';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
|
import Superscript from '@tiptap/extension-superscript';
|
||||||
|
import TextAlign from '@tiptap/extension-text-align';
|
||||||
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import { useEditor } from '@tiptap/react';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
|
||||||
|
const content =
|
||||||
|
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup><sup /></sup> and <sub><sub /></sub> tags)</li><li>Ordered and bullet lists</li><li>Text align </li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
|
||||||
|
|
||||||
|
export function DesaEditor({showSubmit = true} : {
|
||||||
|
showSubmit: boolean
|
||||||
|
}) {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link,
|
||||||
|
Superscript,
|
||||||
|
SubScript,
|
||||||
|
Highlight,
|
||||||
|
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||||
|
],
|
||||||
|
immediatelyRender: false,
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<RichTextEditor editor={editor}>
|
||||||
|
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Bold />
|
||||||
|
<RichTextEditor.Italic />
|
||||||
|
<RichTextEditor.Underline />
|
||||||
|
<RichTextEditor.Strikethrough />
|
||||||
|
<RichTextEditor.ClearFormatting />
|
||||||
|
<RichTextEditor.Highlight />
|
||||||
|
<RichTextEditor.Code />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.H1 />
|
||||||
|
<RichTextEditor.H2 />
|
||||||
|
<RichTextEditor.H3 />
|
||||||
|
<RichTextEditor.H4 />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Blockquote />
|
||||||
|
<RichTextEditor.Hr />
|
||||||
|
<RichTextEditor.BulletList />
|
||||||
|
<RichTextEditor.OrderedList />
|
||||||
|
<RichTextEditor.Subscript />
|
||||||
|
<RichTextEditor.Superscript />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link />
|
||||||
|
<RichTextEditor.Unlink />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.AlignLeft />
|
||||||
|
<RichTextEditor.AlignCenter />
|
||||||
|
<RichTextEditor.AlignJustify />
|
||||||
|
<RichTextEditor.AlignRight />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Undo />
|
||||||
|
<RichTextEditor.Redo />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
</RichTextEditor.Toolbar>
|
||||||
|
|
||||||
|
<RichTextEditor.Content />
|
||||||
|
</RichTextEditor>
|
||||||
|
{showSubmit && (
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
95
src/app/admin/(dashboard)/desa/_com/desaEditorText.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
'use client'
|
||||||
|
import { Button, Stack } from '@mantine/core';
|
||||||
|
import { Link, RichTextEditor } from '@mantine/tiptap';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
|
import Superscript from '@tiptap/extension-superscript';
|
||||||
|
import TextAlign from '@tiptap/extension-text-align';
|
||||||
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import { useEditor } from '@tiptap/react';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
|
||||||
|
|
||||||
|
function DesaEditorText({ onSubmit, onChange, showSubmit = true, initialContent = '', }: {
|
||||||
|
onSubmit?: (val: string) => void,
|
||||||
|
onChange: (val: string) => void,
|
||||||
|
showSubmit?: boolean,
|
||||||
|
initialContent?: string }) {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link,
|
||||||
|
Superscript,
|
||||||
|
SubScript,
|
||||||
|
Highlight,
|
||||||
|
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||||
|
],
|
||||||
|
immediatelyRender: false,
|
||||||
|
content: initialContent,
|
||||||
|
onUpdate : ({editor}) => {
|
||||||
|
onChange(editor.getHTML())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<RichTextEditor editor={editor}>
|
||||||
|
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Bold />
|
||||||
|
<RichTextEditor.Italic />
|
||||||
|
<RichTextEditor.Underline />
|
||||||
|
<RichTextEditor.Strikethrough />
|
||||||
|
<RichTextEditor.ClearFormatting />
|
||||||
|
<RichTextEditor.Highlight />
|
||||||
|
<RichTextEditor.Code />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.H1 />
|
||||||
|
<RichTextEditor.H2 />
|
||||||
|
<RichTextEditor.H3 />
|
||||||
|
<RichTextEditor.H4 />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Blockquote />
|
||||||
|
<RichTextEditor.Hr />
|
||||||
|
<RichTextEditor.BulletList />
|
||||||
|
<RichTextEditor.OrderedList />
|
||||||
|
<RichTextEditor.Subscript />
|
||||||
|
<RichTextEditor.Superscript />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link />
|
||||||
|
<RichTextEditor.Unlink />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.AlignLeft />
|
||||||
|
<RichTextEditor.AlignCenter />
|
||||||
|
<RichTextEditor.AlignJustify />
|
||||||
|
<RichTextEditor.AlignRight />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Undo />
|
||||||
|
<RichTextEditor.Redo />
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
|
</RichTextEditor.Toolbar>
|
||||||
|
|
||||||
|
<RichTextEditor.Content />
|
||||||
|
</RichTextEditor>
|
||||||
|
{showSubmit && (
|
||||||
|
<Button onClick={() => {
|
||||||
|
if (!editor) return
|
||||||
|
onSubmit?.(editor?.getHTML())
|
||||||
|
}}>Submit</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DesaEditorText;
|
||||||
241
src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
|
|
||||||
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { FileInput } from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import stateDashboardBerita from "../../../../_state/desa/berita";
|
||||||
|
|
||||||
|
function EditBerita() {
|
||||||
|
const beritaState = useProxy(stateDashboardBerita);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
judul: beritaState.berita.edit.form.judul || '',
|
||||||
|
deskripsi: beritaState.berita.edit.form.deskripsi || '',
|
||||||
|
kategoriBeritaId: beritaState.berita.edit.form.kategoriBeritaId || '',
|
||||||
|
content: beritaState.berita.edit.form.content || '',
|
||||||
|
imageId: beritaState.berita.edit.form.imageId || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load berita by id saat pertama kali
|
||||||
|
useEffect(() => {
|
||||||
|
const loadBerita = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await stateDashboardBerita.berita.edit.load(id); // akses langsung, bukan dari proxy
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
judul: data.judul || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
kategoriBeritaId: data.kategoriBeritaId || '',
|
||||||
|
content: data.content || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading berita:", error);
|
||||||
|
toast.error("Gagal memuat data berita");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadBerita();
|
||||||
|
}, [params?.id]); // ✅ hapus beritaState dari dependency
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update global state with form data
|
||||||
|
beritaState.berita.edit.form = {
|
||||||
|
...beritaState.berita.edit.form,
|
||||||
|
judul: formData.judul,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
content: formData.content,
|
||||||
|
kategoriBeritaId: formData.kategoriBeritaId || '',
|
||||||
|
imageId: formData.imageId // Keep existing imageId if not changed
|
||||||
|
};
|
||||||
|
|
||||||
|
// Jika ada file baru, upload
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update imageId in global state
|
||||||
|
beritaState.berita.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await beritaState.berita.edit.update();
|
||||||
|
toast.success("Berita berhasil diperbarui!");
|
||||||
|
router.push("/admin/desa/berita");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating berita:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui berita");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Berita</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.judul}
|
||||||
|
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectCategory
|
||||||
|
value={formData.kategoriBeritaId}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
kategoriBeritaId: val?.id || ''
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.content}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||||
|
beritaState.berita.edit.form.content = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit}>Edit Berita</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectCategoryProps {
|
||||||
|
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||||
|
select: {
|
||||||
|
name: true;
|
||||||
|
id: true;
|
||||||
|
};
|
||||||
|
}> | null) => void;
|
||||||
|
value?: string | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectCategory({
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
}: SelectCategoryProps) {
|
||||||
|
const categoryState = useProxy(stateDashboardBerita.category);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
categoryState.findMany.load().then(() => {
|
||||||
|
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!categoryState.findMany.data) {
|
||||||
|
return <Skeleton height={38} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const selectedValue = value || defaultValue;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
data={categoryState.findMany.data.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
|
value={selectedValue || null}
|
||||||
|
onChange={(val: string | null) => {
|
||||||
|
if (val) {
|
||||||
|
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||||
|
if (selected) {
|
||||||
|
onChange(selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onChange(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditBerita;
|
||||||
120
src/app/admin/(dashboard)/desa/berita/[id]/page.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
'use client'
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||||
|
|
||||||
|
function DetailBerita() {
|
||||||
|
const beritaState = useProxy(stateDashboardBerita)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
beritaState.berita.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
beritaState.berita.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/berita")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!beritaState.berita.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
{Array.from({ length: 10 }).map((_, k) => (
|
||||||
|
<Skeleton key={k} h={40} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Berita</Text>
|
||||||
|
{beritaState.berita.findUnique.data ? (
|
||||||
|
<Paper key={beritaState.berita.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Kategori</Text>
|
||||||
|
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.kategoriBerita?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{beritaState.berita.findUnique.data?.judul}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} >{beritaState.berita.findUnique.data?.deskripsi}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
||||||
|
<Image w={{ base: 150, md: 150, lg: 150 }} src={beritaState.berita.findUnique.data?.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"lg"}>Konten</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: beritaState.berita.findUnique.data?.content }} />
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (beritaState.berita.findUnique.data) {
|
||||||
|
setSelectedId(beritaState.berita.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={beritaState.berita.delete.loading || !beritaState.berita.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (beritaState.berita.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!beritaState.berita.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailBerita;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import { RichTextEditor, Link } from '@mantine/tiptap';
|
import { RichTextEditor, Link } from '@mantine/tiptap';
|
||||||
import { useEditor } from '@tiptap/react';
|
import { useEditor } from '@tiptap/react';
|
||||||
@@ -8,28 +9,77 @@ import TextAlign from '@tiptap/extension-text-align';
|
|||||||
import Superscript from '@tiptap/extension-superscript';
|
import Superscript from '@tiptap/extension-superscript';
|
||||||
import SubScript from '@tiptap/extension-subscript';
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
import { Button, Stack } from '@mantine/core';
|
import { Button, Stack } from '@mantine/core';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const content =
|
// const content =
|
||||||
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup><sup /></sup> and <sub><sub /></sub> tags)</li><li>Ordered and bullet lists</li><li>Text align </li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
|
// '<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup><sup /></sup> and <sub><sub /></sub> tags)</li><li>Ordered and bullet lists</li><li>Text align </li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
|
||||||
|
|
||||||
export function BeritaEditor({ onSubmit }: { onSubmit: (val: string) => void }) {
|
export function BeritaEditor({
|
||||||
const editor = useEditor({
|
onEditorReady,
|
||||||
extensions: [
|
showSubmit = true,
|
||||||
StarterKit,
|
onSubmit,
|
||||||
Underline,
|
initialContent = '',
|
||||||
Link,
|
onUpdate,
|
||||||
Superscript,
|
}: {
|
||||||
SubScript,
|
onEditorReady?: (editor: any | null) => void;
|
||||||
Highlight,
|
onSubmit?: (val: string) => void;
|
||||||
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
showSubmit?: boolean;
|
||||||
],
|
initialContent?: string;
|
||||||
content,
|
onUpdate?: (content: string) => void;
|
||||||
immediatelyRender: false
|
}) {
|
||||||
});
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
if (!editor) {
|
|
||||||
return null;
|
const editor = useEditor({
|
||||||
}
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Link,
|
||||||
|
Superscript,
|
||||||
|
SubScript,
|
||||||
|
Highlight,
|
||||||
|
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||||
|
],
|
||||||
|
content: initialContent || '<p></p>',
|
||||||
|
onUpdate: ({ editor }) => {
|
||||||
|
if (onUpdate) {
|
||||||
|
onUpdate(editor.getHTML());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: 'prose max-w-none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onSelectionUpdate: () => {
|
||||||
|
if (!isReady && editor) {
|
||||||
|
setIsReady(true);
|
||||||
|
onEditorReady?.(editor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediatelyRender: false
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editor) {
|
||||||
|
// Set initial content when component mounts
|
||||||
|
editor.commands.setContent(initialContent || '<p></p>');
|
||||||
|
|
||||||
|
// Mark as mounted and notify parent
|
||||||
|
if (!mounted) {
|
||||||
|
setMounted(true);
|
||||||
|
onEditorReady?.(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (editor) {
|
||||||
|
editor.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [editor, initialContent, mounted, onEditorReady]);
|
||||||
|
|
||||||
|
if (!editor) return <div>Loading editor...</div>;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,10 +132,12 @@ export function BeritaEditor({ onSubmit }: { onSubmit: (val: string) => void })
|
|||||||
|
|
||||||
<RichTextEditor.Content />
|
<RichTextEditor.Content />
|
||||||
</RichTextEditor>
|
</RichTextEditor>
|
||||||
<Button onClick={() => {
|
{showSubmit && (
|
||||||
if (!editor) return
|
<Button onClick={() => {
|
||||||
onSubmit(editor?.getHTML())
|
if (!editor) return
|
||||||
}}>Submit</Button>
|
onSubmit?.(editor?.getHTML())
|
||||||
|
}}>Submit</Button>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
168
src/app/admin/(dashboard)/desa/berita/create/page.tsx
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Select, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||||
|
|
||||||
|
export default function CreateBerita() {
|
||||||
|
const beritaState = useProxy(stateDashboardBerita);
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
// Reset state di valtio
|
||||||
|
beritaState.berita.create.form = {
|
||||||
|
judul: "",
|
||||||
|
deskripsi: "",
|
||||||
|
kategoriBeritaId: "",
|
||||||
|
imageId: "",
|
||||||
|
content: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset state lokal
|
||||||
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload gambar dulu
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simpan ID gambar ke form
|
||||||
|
beritaState.berita.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
// Submit data berita
|
||||||
|
await beritaState.berita.create.create();
|
||||||
|
|
||||||
|
// Reset form setelah submit
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/desa/berita")
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Create Berita</Title>
|
||||||
|
<TextInput
|
||||||
|
value={beritaState.berita.create.form.judul}
|
||||||
|
onChange={(val) => {
|
||||||
|
beritaState.berita.create.form.judul = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
<SelectCategory
|
||||||
|
onChange={(val) => {
|
||||||
|
beritaState.berita.create.form.kategoriBeritaId = val.id;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={beritaState.berita.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
beritaState.berita.create.form.deskripsi = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={beritaState.berita.create.form.content}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
beritaState.berita.create.form.content = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
function SelectCategory({
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||||
|
select: {
|
||||||
|
name: true;
|
||||||
|
id: true;
|
||||||
|
};
|
||||||
|
}>) => void;
|
||||||
|
}) {
|
||||||
|
const categoryState = useProxy(stateDashboardBerita.category);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
categoryState.findMany.load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!categoryState.findMany.data) {
|
||||||
|
return <Skeleton height={38} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
data={categoryState.findMany.data.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
|
onChange={(val) => {
|
||||||
|
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||||
|
if (selected) {
|
||||||
|
onChange(selected);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
searchable
|
||||||
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +1,189 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Center, Group, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { Prisma } from '@prisma/client';
|
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { IconImageInPicture } from '@tabler/icons-react';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
import stateDashboardBerita from '../../_state/desa/berita';
|
import stateDashboardBerita from '../../_state/desa/berita';
|
||||||
import { BeritaEditor } from './_com/BeritaEditor';
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Box>
|
||||||
<SimpleGrid cols={{base: 1, md: 2}}>
|
<Grid>
|
||||||
<BeritaCreate />
|
<GridCol span={{ base: 12, md: 9 }}>
|
||||||
<BeritaList />
|
<Title order={3}>Berita</Title>
|
||||||
</SimpleGrid>
|
</GridCol>
|
||||||
</Stack>
|
<GridCol span={{ base: 12, md: 3 }}>
|
||||||
|
<Paper radius={"lg"} bg={colors['white-1']}>
|
||||||
|
<TextInput
|
||||||
|
radius={"lg"}
|
||||||
|
placeholder='pencarian'
|
||||||
|
leftSection={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
<BeritaList />
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// function BeritaList() {
|
||||||
|
// const beritaState = useProxy(stateDashboardBerita)
|
||||||
|
// useShallowEffect(() => {
|
||||||
|
// beritaState.berita.findMany.load()
|
||||||
|
// }, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// const router = useRouter()
|
||||||
|
|
||||||
|
// if (!beritaState.berita.findMany.data) return <Stack py={10}>
|
||||||
|
// {Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
|
||||||
|
// </Stack>
|
||||||
|
// return (
|
||||||
|
// <Box py={10}>
|
||||||
|
// <Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
// <Stack>
|
||||||
|
// <Text fz={"xl"} fw={"bold"}>List Berita</Text>
|
||||||
|
// <SimpleGrid cols={{ base: 1, md: 4 }}>
|
||||||
|
// {beritaState.berita.findMany.data?.map((item) => (
|
||||||
|
// <Paper key={item.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
// <Box >
|
||||||
|
// <Flex justify="flex-end" mt={10}>
|
||||||
|
// <ActionIcon
|
||||||
|
// onClick={() => beritaState.berita.delete.byId(item.id)}
|
||||||
|
// disabled={beritaState.berita.delete.loading}
|
||||||
|
// color={colors['blue-button']} variant='transparent'>
|
||||||
|
// <IconX size={20} />
|
||||||
|
// </ActionIcon>
|
||||||
|
// <ActionIcon onClick={() => {
|
||||||
|
// router.push("/desa/berita/edit");
|
||||||
|
// }} color={colors['blue-button']} variant='transparent'>
|
||||||
|
// <IconEdit size={20} />
|
||||||
|
// </ActionIcon>
|
||||||
|
// </Flex>
|
||||||
|
// <Text fw={"bold"} fz={"sm"}>
|
||||||
|
// Kategori
|
||||||
|
// </Text>
|
||||||
|
// <Text>{item.kategoriBerita?.name}</Text>
|
||||||
|
// <Text fw={"bold"} fz={"sm"}>
|
||||||
|
// Judul
|
||||||
|
// </Text>
|
||||||
|
// <Text>{item.judul}</Text>
|
||||||
|
// <Text lineClamp={1} fw={"bold"} fz={"sm"}>
|
||||||
|
// Deskripsi
|
||||||
|
// </Text>
|
||||||
|
// <Text size='sm' lineClamp={2}>{item.deskripsi}</Text>
|
||||||
|
// <Text fw={"bold"} fz={"sm"}>
|
||||||
|
// Gambar
|
||||||
|
// </Text>
|
||||||
|
// <Image w={{ base: 100, md: 150 }} src={item.image?.link} alt="gambar" />
|
||||||
|
// </Box>
|
||||||
|
// </Paper>
|
||||||
|
// ))}
|
||||||
|
// </SimpleGrid>
|
||||||
|
// </Stack>
|
||||||
|
// </Paper>
|
||||||
|
// </Box>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
function BeritaList() {
|
function BeritaList() {
|
||||||
const beritaState = useProxy(stateDashboardBerita)
|
const beritaState = useProxy(stateDashboardBerita)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
beritaState.berita.findMany.load()
|
beritaState.berita.findMany.load()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!beritaState.berita.findMany.data) return <Stack>
|
const router = useRouter()
|
||||||
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
|
|
||||||
</Stack>
|
|
||||||
return <Stack>
|
|
||||||
<Text>News List</Text>
|
|
||||||
{beritaState.berita.findMany.data?.map((item) => (
|
|
||||||
<Text key={item.id}>{item.judul}</Text>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
|
|
||||||
function BeritaCreate() {
|
const handleHapus = () => {
|
||||||
const beritaState = useProxy(stateDashboardBerita)
|
if (selectedId) {
|
||||||
return <Stack gap={"md"}>
|
beritaState.berita.delete.byId(selectedId)
|
||||||
<Text>Create Some News</Text>
|
setModalHapus(false)
|
||||||
<SelectCategory onChange={(val) => {
|
setSelectedId(null)
|
||||||
beritaState.berita.create.form.katagoryBeritaId = val.id
|
|
||||||
}} />
|
|
||||||
<TextInput onChange={(val) => {
|
|
||||||
beritaState.berita.create.form.judul = val.target.value
|
|
||||||
}} label={"Judul"} placeholder='masukkan judul' />
|
|
||||||
<TextInput onChange={(val) => {
|
|
||||||
beritaState.berita.create.form.deskripsi = val.target.value
|
|
||||||
}} label={"Deskripsi"} placeholder='masukkan deskripsi' />
|
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
<BeritaEditor onSubmit={(val) => {
|
|
||||||
|
|
||||||
beritaState.berita.create.form.content = val
|
|
||||||
beritaState.berita.create.create()
|
|
||||||
}} />
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectCategory({ onChange }: {
|
|
||||||
onChange: (value: Prisma.KatagoryBeritaGetPayload<{
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
id: true
|
|
||||||
}
|
}
|
||||||
}>) => void
|
}
|
||||||
}) {
|
|
||||||
const beritaState = useProxy(stateDashboardBerita)
|
|
||||||
useShallowEffect(() => {
|
|
||||||
beritaState.category.findMany.load()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!beritaState.category.findMany.data) return <Skeleton h={40} />
|
if (!beritaState.berita.findMany.data) {
|
||||||
return <Group>
|
return (
|
||||||
<Select placeholder='pilih katagori' label={"select katagori"} data={beritaState.category.findMany.data.map((item) => ({
|
<Stack py={10}>
|
||||||
value: item.id,
|
<Skeleton h={500} />
|
||||||
label: item.name
|
</Stack>
|
||||||
}))} onChange={(v) => {
|
)
|
||||||
const data = beritaState.category.findMany.data?.find((item) => item.id === v)
|
}
|
||||||
if (!data) return
|
|
||||||
onChange(data)
|
return (
|
||||||
}} />
|
<Box py={10}>
|
||||||
</Group>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
|
<Button onClick={() => router.push("/admin/desa/berita/create")} bg={colors['blue-button']}>
|
||||||
|
<IconCircleDashedPlus size={25} />
|
||||||
|
</Button>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w={250}>Judul</TableTh>
|
||||||
|
<TableTh w={250}>Kategori</TableTh>
|
||||||
|
<TableTh w={250}>Image</TableTh>
|
||||||
|
<TableTh w={200}>Detail</TableTh>
|
||||||
|
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody >
|
||||||
|
{beritaState.berita.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd >
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd >{item.kategoriBerita?.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={item.image?.link} alt="gambar" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table> </Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
import React from 'react';
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
||||||
|
import Foto from './ui/foto/page';
|
||||||
|
import Video from './ui/video/page';
|
||||||
|
|
||||||
function Page() {
|
function Gallery() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
Gallery
|
<Stack gap={"xs"}>
|
||||||
</div>
|
<Title order={3}>Gallery</Title>
|
||||||
|
<Tabs color={colors['blue-button']} variant="pills" defaultValue="foto">
|
||||||
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
|
<TabsTab value="foto" leftSection={<IconPhoto size={12} />}>
|
||||||
|
Foto
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="video" leftSection={<IconVideo size={12} />}>
|
||||||
|
Video
|
||||||
|
</TabsTab>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsPanel value="foto">
|
||||||
|
<Foto/>
|
||||||
|
</TabsPanel>
|
||||||
|
|
||||||
|
<TabsPanel value="video">
|
||||||
|
<Video/>
|
||||||
|
</TabsPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Gallery;
|
||||||
|
|||||||
17
src/app/admin/(dashboard)/desa/gallery/ui/foto/listPage.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListFoto() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>List Foto</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListFoto;
|
||||||
52
src/app/admin/(dashboard)/desa/gallery/ui/foto/page.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconUpload } from '@tabler/icons-react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListFoto from './listPage';
|
||||||
|
|
||||||
|
function Foto() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Foto</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Tanggal Foto</Text>}
|
||||||
|
placeholder="2022-01-01"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul Foto</Text>}
|
||||||
|
placeholder="Judul Foto"
|
||||||
|
/>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Upload Foto</Text>
|
||||||
|
<Box bg={colors['BG-trans']} p={"md"}>
|
||||||
|
<Center>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi Foto</Text>
|
||||||
|
<DesaEditor
|
||||||
|
showSubmit={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListFoto/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Foto;
|
||||||
17
src/app/admin/(dashboard)/desa/gallery/ui/video/listPage.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListVideo() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>List Video</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListVideo;
|
||||||
52
src/app/admin/(dashboard)/desa/gallery/ui/video/page.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconUpload } from '@tabler/icons-react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListVideo from './listPage';
|
||||||
|
|
||||||
|
function Video() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Video</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Tanggal Video</Text>}
|
||||||
|
placeholder="2022-01-01"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul Video</Text>}
|
||||||
|
placeholder="Judul Video"
|
||||||
|
/>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Upload Video</Text>
|
||||||
|
<Box bg={colors['BG-trans']} p={"md"}>
|
||||||
|
<Center>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi Video</Text>
|
||||||
|
<DesaEditor
|
||||||
|
showSubmit={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListVideo/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Video;
|
||||||
@@ -1,11 +1,48 @@
|
|||||||
import React from 'react';
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
|
import SuratKeterangan from './ui/surat_keterangan/page';
|
||||||
|
import PerizinanUsaha from './ui/perizinan_usaha/page';
|
||||||
|
import TelunjukSaktiDesa from './ui/telunjuk_sakti_desa/page';
|
||||||
|
import PendudukNonPermanent from './ui/penduduk_non_permanent/page';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box py={10}>
|
||||||
Layanan
|
<Stack >
|
||||||
</div>
|
<Title order={3}>Layanan</Title>
|
||||||
|
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Pelayanan Surat Keterangan"}>
|
||||||
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
|
<TabsTab value="Pelayanan Surat Keterangan">
|
||||||
|
Pelayanan Surat Keterangan
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Pelayanan Perizinan Berusaha">
|
||||||
|
Pelayanan Perizinan Berusaha
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Pelayanan Telunjuk Sakti Desa">
|
||||||
|
Pelayanan Telunjuk Sakti Desa
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Pelayanan Penduduk Non-Permanent">
|
||||||
|
Pelayanan Penduduk Non-Permanent
|
||||||
|
</TabsTab>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsPanel value="Pelayanan Surat Keterangan">
|
||||||
|
<SuratKeterangan />
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Pelayanan Perizinan Berusaha">
|
||||||
|
<PerizinanUsaha />
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Pelayanan Telunjuk Sakti Desa">
|
||||||
|
<TelunjukSaktiDesa />
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Pelayanan Penduduk Non-Permanent">
|
||||||
|
<PendudukNonPermanent />
|
||||||
|
</TabsPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListPendudukNonPermanent() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Penduduk Non-Permanent</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListPendudukNonPermanent;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListPendudukNonPermanent from './listPage';
|
||||||
|
|
||||||
|
function PendudukNonPermanent() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Penduduk Non-Permanent</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Penduduk Non-Permanent</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListPendudukNonPermanent />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PendudukNonPermanent;
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListPerizinanUsaha() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>List Perizinan Usaha</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListPerizinanUsaha;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import ListPerizinanUsaha from './listPage';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
|
||||||
|
function PerizinanUsaha() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Pelayanan Perizinan Usaha</Title>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi Perizinan Usaha</Text>
|
||||||
|
<DesaEditor
|
||||||
|
showSubmit={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListPerizinanUsaha />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PerizinanUsaha;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListSuratKeterangan() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Surat Keterangan</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListSuratKeterangan;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Button, Group, TextInput, Text, Center, Flex } from '@mantine/core';
|
||||||
|
import { IconUpload } from '@tabler/icons-react';
|
||||||
|
import React from 'react';
|
||||||
|
import ListSuratKeterangan from './listPage';
|
||||||
|
|
||||||
|
function SuratKeterangan() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Pelayanan Surat Keterangan</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
|
||||||
|
placeholder='masukkan nama surat keterangan'
|
||||||
|
/>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Upload Gambar Surat Keterangan</Text>
|
||||||
|
<Box bg={colors['BG-trans']} p={"md"}>
|
||||||
|
<Center>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
<Flex>
|
||||||
|
<Text fz={"xs"} c={"red"}>*</Text>
|
||||||
|
<Text fz={"xs"} c={"dimmed"} fw={"bold"}>Upload foto untuk konten surat keterangan</Text>
|
||||||
|
</Flex>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListSuratKeterangan />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SuratKeterangan;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListTelunjukSaktiDesa() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Telunjuk Sakti Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListTelunjukSaktiDesa;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListTelunjukSaktiDesa from './listPage';
|
||||||
|
|
||||||
|
function TelunjukSaktiDesa() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Telunjuk Sakti Desa</Title>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi Telunjuk Sakti Desa</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListTelunjukSaktiDesa />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TelunjukSaktiDesa;
|
||||||
@@ -1,10 +1,33 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Penghargaan from './ui/penghargaan/page';
|
||||||
|
import GambarPerhargaan from './ui/gambar_perhargaan/page';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box py={10}>
|
||||||
Penghargaan
|
<Stack>
|
||||||
</div>
|
<Title order={3}>Penghargaan</Title>
|
||||||
|
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Penghargaan"}>
|
||||||
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
|
<TabsTab value="Penghargaan">
|
||||||
|
Penghargaan
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Gambar Penghargaan">
|
||||||
|
Gambar Penghargaan
|
||||||
|
</TabsTab>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsPanel value="Penghargaan">
|
||||||
|
<Penghargaan/>
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Gambar Penghargaan">
|
||||||
|
<GambarPerhargaan/>
|
||||||
|
</TabsPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListGambarPenghargaan() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Gambar Penghargaan</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListGambarPenghargaan;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, SimpleGrid, Stack, Title, Text, Group, Button, TextInput, Center } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListGambarPenghargaan from './listPage';
|
||||||
|
import { IconUpload } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
|
||||||
|
function GambarPerhargaan() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Tambah Gambar Penghargaan</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Judul Gambar Penghargaan"
|
||||||
|
placeholder='masukkan judul gambar penghargaan'
|
||||||
|
/>
|
||||||
|
<Text fw={"bold"}>Deskripsi Gambar Penghargaan</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"}>Upload Gambar Penghargaan</Text>
|
||||||
|
<Box bg={colors['BG-trans']} p={"md"}>
|
||||||
|
<Center>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Center>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListGambarPenghargaan />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GambarPerhargaan;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListPenghargaan() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Penghargaan</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListPenghargaan;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
import ListPenghargaan from './listPage';
|
||||||
|
|
||||||
|
function Penghargaan() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Stack>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Penghargaan</Title>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi Penghargaan</Text>
|
||||||
|
<DesaEditor
|
||||||
|
showSubmit={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListPenghargaan/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Penghargaan;
|
||||||
@@ -1,62 +1,75 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Group, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
import { Box, Group, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateDesaPengumuman from '../../_state/desa/pengumuman';
|
import stateDesaPengumuman from '../../_state/desa/pengumuman';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { BeritaEditor } from '../berita/_com/BeritaEditor';
|
import { BeritaEditor } from '../berita/_com/BeritaEditor';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Box>
|
||||||
|
<Title order={3}>Pengumuman</Title>
|
||||||
<SimpleGrid cols={{
|
<SimpleGrid cols={{
|
||||||
base: 1, md: 2
|
base: 1, md: 2
|
||||||
}}>
|
}}>
|
||||||
<PengumumanList />
|
|
||||||
<PengumumanCreate />
|
<PengumumanCreate />
|
||||||
|
<PengumumanList />
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PengumumanCreate() {
|
||||||
|
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<SelectCategory onChange={(val) => {
|
||||||
|
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id
|
||||||
|
}} />
|
||||||
|
<TextInput onChange={(val) => {
|
||||||
|
pengumumanState.pengumuman.create.form.judul = val.target.value
|
||||||
|
}} label={<Text fz={"sm"} fw={"bold"}>Judul</Text>} placeholder='masukkan judul' />
|
||||||
|
<TextInput onChange={(val) => {
|
||||||
|
pengumumanState.pengumuman.create.form.deskripsi = val.target.value
|
||||||
|
}} label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>} placeholder='masukkan deskripsi' />
|
||||||
|
<BeritaEditor onSubmit={(val) => {
|
||||||
|
pengumumanState.pengumuman.create.form.content = val
|
||||||
|
pengumumanState.pengumuman.create.create()
|
||||||
|
}} />
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function PengumumanList() {
|
function PengumumanList() {
|
||||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pengumumanState.pengumuman.findMany.load()
|
pengumumanState.pengumuman.findMany.load()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!pengumumanState.pengumuman.findMany.data) return <Stack>
|
if (!pengumumanState.pengumuman.findMany.data) return <Stack py={10}>
|
||||||
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
|
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
|
||||||
</Stack>
|
</Stack>
|
||||||
return <Stack>
|
return (
|
||||||
<Text>Announcement List</Text>
|
<Box py={10}>
|
||||||
{pengumumanState.pengumuman.findMany.data?.map((item) => (
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Text key={item.id}>{item.judul}</Text>
|
<Stack gap={"xs"}>
|
||||||
))}
|
<Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
|
||||||
</Stack>;
|
{pengumumanState.pengumuman.findMany.data?.map((item) => (
|
||||||
}
|
<Text key={item.id}>{item.judul}</Text>
|
||||||
|
))}
|
||||||
function PengumumanCreate() {
|
</Stack>
|
||||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
return <Stack gap={"md"}>
|
|
||||||
<Text>Create Some Announcement</Text>
|
|
||||||
<SelectCategory onChange={(val) => {
|
|
||||||
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id
|
|
||||||
}} />
|
|
||||||
<TextInput onChange={(val) => {
|
|
||||||
pengumumanState.pengumuman.create.form.judul = val.target.value
|
|
||||||
}} label={"Judul"} placeholder='masukkan judul' />
|
|
||||||
<TextInput onChange={(val) => {
|
|
||||||
pengumumanState.pengumuman.create.form.deskripsi = val.target.value
|
|
||||||
}} label={"Deskripsi"} placeholder='masukkan deskripsi' />
|
|
||||||
<BeritaEditor onSubmit={(val) => {
|
|
||||||
pengumumanState.pengumuman.create.form.content = val
|
|
||||||
pengumumanState.pengumuman.create.create()
|
|
||||||
}} />
|
|
||||||
</Stack>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectCategory({ onChange }: {
|
function SelectCategory({ onChange }: {
|
||||||
@@ -75,7 +88,7 @@ function SelectCategory({ onChange }: {
|
|||||||
if (!pengumumanState.category.findMany.data) return <Skeleton h={40} />
|
if (!pengumumanState.category.findMany.data) return <Skeleton h={40} />
|
||||||
return <Group>
|
return <Group>
|
||||||
{/* {JSON.stringify(pengumumanState.category.findMany.data)} */}
|
{/* {JSON.stringify(pengumumanState.category.findMany.data)} */}
|
||||||
<Select placeholder='pilih kategori' label={"select katagori"} data={pengumumanState.category.findMany.data.map((item) => ({
|
<Select placeholder='pilih kategori' label={<Text fz={"sm"} fw={"bold"}>Pilih Kategori</Text>} data={pengumumanState.category.findMany.data.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
label: item.name
|
label: item.name
|
||||||
}))} onChange={(v) => {
|
}))} onChange={(v) => {
|
||||||
|
|||||||
128
src/app/admin/(dashboard)/desa/potensi/create/page.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import potensiDesaState from '../../../_state/desa/potensi';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
|
||||||
|
|
||||||
|
function CreatePotensi() {
|
||||||
|
const potensiState = useProxy(potensiDesaState);
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) return toast.warn('Pilih file gambar terlebih dahulu');
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error('Gagal upload gambar');
|
||||||
|
}
|
||||||
|
|
||||||
|
potensiState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
await potensiState.create.create();
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
router.push('/admin/desa/potensi');
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
potensiState.create.form = {
|
||||||
|
name: '',
|
||||||
|
deskripsi: '',
|
||||||
|
kategori: '',
|
||||||
|
imageId: '',
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Create Potensi</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={potensiState.create.form.name}
|
||||||
|
onChange={(val) => (potensiState.create.form.name = val.target.value)}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={potensiState.create.form.deskripsi}
|
||||||
|
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
|
||||||
|
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={potensiState.create.form.kategori}
|
||||||
|
onChange={(val) => (potensiState.create.form.kategori = val.target.value)}
|
||||||
|
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
||||||
|
placeholder="masukkan kategori"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Konten</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={potensiState.create.form.content}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
potensiState.create.form.content = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||||
|
Simpan Potensi
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreatePotensi;
|
||||||
121
src/app/admin/(dashboard)/desa/potensi/detail/[id]/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import potensiDesaState from '../../../../_state/desa/potensi';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
export default function DetailPotensi() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const potensiState = useProxy(potensiDesaState)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
potensiState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
potensiState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/desa/potensi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!potensiState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
{Array.from({ length: 10 }).map((_, k) => (
|
||||||
|
<Skeleton key={k} h={40} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Potensi</Text>
|
||||||
|
{potensiState.findUnique.data ? (
|
||||||
|
<Paper key={potensiState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{potensiState.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
|
||||||
|
<Text fz={"lg"}>{potensiState.findUnique.data.kategori}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"}>{potensiState.findUnique.data.deskripsi}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={potensiState.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: potensiState.findUnique.data.content }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (potensiState.findUnique.data) {
|
||||||
|
setSelectedId(potensiState.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={potensiState.delete.loading || !potensiState.findUnique.data}
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (potensiState.findUnique.data) {
|
||||||
|
router.push(`/admin/desa/potensi/edit/${potensiState.findUnique.data.id}`)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!potensiState.findUnique.data}
|
||||||
|
color="green"
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus potensi ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
169
src/app/admin/(dashboard)/desa/potensi/edit/[id]/page.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from "@mantine/core";
|
||||||
|
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { useProxy } from "valtio/utils";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function EditPotensi() {
|
||||||
|
const potensiState = useProxy(potensiDesaState)
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
deskripsi: '',
|
||||||
|
kategori: '',
|
||||||
|
content: '',
|
||||||
|
imageId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPotensi = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await potensiDesaState.edit.load(id); // ambil data dari API
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
kategori: data.kategori || '',
|
||||||
|
content: data.content || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading potensi:", error);
|
||||||
|
toast.error("Gagal memuat data potensi");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPotensi();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// Sinkronkan semua data dari formData ke state global
|
||||||
|
potensiState.edit.form = {
|
||||||
|
...potensiState.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
kategori: formData.kategori,
|
||||||
|
content: formData.content,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
potensiState.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await potensiState.edit.update();
|
||||||
|
toast.success("Potensi berhasil diperbarui!");
|
||||||
|
router.push("/admin/desa/potensi");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating potensi:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat memperbarui potensi");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Edit Potensi</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
setFormData((prev) => ({ ...prev, name: val }));
|
||||||
|
potensiState.edit.form.name = val;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
setFormData((prev) => ({ ...prev, deskripsi: val }));
|
||||||
|
potensiState.edit.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.kategori}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
setFormData((prev) => ({ ...prev, kategori: val }));
|
||||||
|
potensiState.edit.form.kategori = val;
|
||||||
|
}}
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||||
|
placeholder="masukkan kategori"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.content}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||||
|
potensiState.edit.form.content = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Edit Potensi</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPotensi;
|
||||||
@@ -1,11 +1,89 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
function Page() {
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
import potensiDesaState from '../../_state/desa/potensi';
|
||||||
|
|
||||||
|
|
||||||
|
function Potensi() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
Potensi
|
<HeaderSearch
|
||||||
</div>
|
title='Potensi Desa'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
/>
|
||||||
|
<ListPotensi />
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
function ListPotensi() {
|
||||||
|
const potensiState = useProxy(potensiDesaState)
|
||||||
|
useShallowEffect(() => {
|
||||||
|
potensiState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
if (!potensiState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Potensi'
|
||||||
|
href='/admin/desa/potensi/create'
|
||||||
|
/>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Judul</TableTh>
|
||||||
|
<TableTh>Kategori</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{potensiState.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={100}>
|
||||||
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
|
</Box></TableTd>
|
||||||
|
<TableTd>{item.kategori}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/desa/potensi/detail/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Potensi;
|
||||||
|
|||||||
@@ -1,10 +1,52 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Stack, Title, Tabs, TabsList, TabsTab, TabsPanel } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SejarahDesa from './ui/sejarah_desa/page';
|
||||||
|
import VisiMisiDesa from './ui/visi_misi_desa/page';
|
||||||
|
import LambangDesa from './ui/lambang_desa/page';
|
||||||
|
import MaskotDesa from './ui/maskot_desa/page';
|
||||||
|
import ProfilePerbekel from './ui/profile_perbekel/page';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Stack >
|
||||||
Profile
|
<Title order={3}>Profile Desa</Title>
|
||||||
</div>
|
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Sejarah Desa"}>
|
||||||
|
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||||
|
<TabsTab value="Sejarah Desa">
|
||||||
|
Sejarah Desa
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Visi Misi Desa">
|
||||||
|
Visi Misi Desa
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Lambang Desa">
|
||||||
|
Lambang Desa
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Maskot Desa">
|
||||||
|
Maskot Desa
|
||||||
|
</TabsTab>
|
||||||
|
<TabsTab value="Profile Perbekel">
|
||||||
|
Profile Perbekel
|
||||||
|
</TabsTab>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsPanel value="Sejarah Desa">
|
||||||
|
<SejarahDesa/>
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Visi Misi Desa">
|
||||||
|
<VisiMisiDesa/>
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Lambang Desa">
|
||||||
|
<LambangDesa/>
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Maskot Desa">
|
||||||
|
<MaskotDesa/>
|
||||||
|
</TabsPanel>
|
||||||
|
<TabsPanel value="Profile Perbekel">
|
||||||
|
<ProfilePerbekel/>
|
||||||
|
</TabsPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
|
||||||
|
function LambangDesa() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Lambang Desa</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Lambang Desa</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Lambang Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LambangDesa;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
|
||||||
|
function MaskotDesa() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Maskot Desa</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Maskot Desa</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Maskot Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MaskotDesa;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, TextInput, Text } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
|
||||||
|
function ProfilePerbekel() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Profil Perbekel</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Nama Perbekel"
|
||||||
|
placeholder="masukkan nama perbekel"
|
||||||
|
/>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Biodata</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Pengalaman</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Pengalaman Organisasi</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Program Unggulan</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Profil Perbekel</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfilePerbekel;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ListPage() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Sejarah Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListPage;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
'use client'
|
||||||
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import DesaEditorText from '../../../_com/desaEditorText';
|
||||||
|
import ListPage from './listPage';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
|
|
||||||
|
function SejarahDesa() {
|
||||||
|
const stateSejarah = useProxy(stateProfileDesa.Sejarah)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (!stateSejarah.findById.data) {
|
||||||
|
stateSejarah.findById.initialize()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (stateSejarah.findById.data?.id && stateSejarah.findById.data.sejarah) {
|
||||||
|
stateSejarah.update.save({
|
||||||
|
id: stateSejarah.findById.data.id,
|
||||||
|
sejarah: stateSejarah.findById.data.sejarah
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Sejarah Desa</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Sejarah Desa</Text>
|
||||||
|
<DesaEditorText
|
||||||
|
key={stateSejarah.findById.data?.id ?? 'new'}
|
||||||
|
showSubmit={false}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (stateSejarah.findById.data) {
|
||||||
|
stateSejarah.findById.data.sejarah = val
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
initialContent={stateSejarah.findById.data?.sejarah ?? ""}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={submit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<ListPage />
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SejarahDesa;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||||
|
import { DesaEditor } from '../../../_com/desaEditor';
|
||||||
|
|
||||||
|
function VisiMisiDesa() {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Visi Desa</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Visi Desa</Text>
|
||||||
|
<DesaEditor showSubmit={false} />
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Visi Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
<SimpleGrid py={30} cols={{ base: 1, md: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Misi Desa</Title>
|
||||||
|
<Text fw={"bold"}>Deskripsi Misi Desa</Text>
|
||||||
|
<DesaEditor showSubmit={false}/>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>List Misi Desa</Title>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VisiMisiDesa;
|
||||||