Compare commits
5 Commits
nico/16-ju
...
nico/18-ju
| Author | SHA1 | Date | |
|---|---|---|---|
| d2f53ff69b | |||
| 40f0294595 | |||
| 6ed0246cea | |||
| af726043bd | |||
| f4888b53ab |
@@ -23,7 +23,7 @@
|
||||
"@mantine/charts": "^7.17.1",
|
||||
"@mantine/core": "^7.17.4",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"@mantine/dropzone": "^7.17.0",
|
||||
"@mantine/dropzone": "^8.1.1",
|
||||
"@mantine/form": "^8.1.0",
|
||||
"@mantine/hooks": "^7.17.4",
|
||||
"@mantine/tiptap": "^7.17.4",
|
||||
|
||||
7
prisma/data/desa/profile/lambang_desa.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": "edit",
|
||||
"judul": "Lambang Desa",
|
||||
"deskripsi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>"
|
||||
}
|
||||
]
|
||||
7
prisma/data/desa/profile/maskot_desa.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": "edit",
|
||||
"judul": "Maskot Desa",
|
||||
"deskripsi" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
|
||||
"pengalaman": "<ul><li>2021 - 2027: Perbekel Desa Darmasaba</li><li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li><li>2020 - Sekarang: Founder Ugawa Record Music Studio</li><li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li></ul>",
|
||||
"pengalamanOrganisasi": "<ul> <li>1996 – 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 – 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 – 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 – 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 – Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 – Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 – 2028: Komite Tetap Advokasi – Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"sejarah" : "<p>Asal – usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>",
|
||||
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
|
||||
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
|
||||
"lambang" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
|
||||
"maskot" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>",
|
||||
"profilPerbekelId" : "1"
|
||||
}
|
||||
]
|
||||
7
prisma/data/desa/profile/sejarah_desa.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": "edit",
|
||||
"judul": "Sejarah Desa",
|
||||
"deskripsi": "<p>Asal – usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>"
|
||||
}
|
||||
]
|
||||
7
prisma/data/desa/profile/visi_misi_desa.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id" : "edit",
|
||||
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
|
||||
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"id": "edit",
|
||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
|
||||
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
|
||||
|
||||
193
prisma/migrations/20250616155255_16_jun/migration.sql
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `nomor` on the `DaftarInformasiPublik` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `image` on the `GalleryFoto` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `video` on the `GalleryVideo` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `videosId` on the `GalleryVideo` table. All the data in the column will be lost.
|
||||
- The primary key for the `IndeksKepuasanMasyarakat` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to drop the column `profilPerbekelId` on the `ProfileDesa` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `imageUrl` on the `ProfilePPID` table. All the data in the column will be lost.
|
||||
- You are about to drop the `Images` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Videos` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Added the required column `deskripsi` to the `GalleryFoto` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `deskripsi` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `linkVideo` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "GalleryFoto" DROP CONSTRAINT "GalleryFoto_imagesId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "GalleryVideo" DROP CONSTRAINT "GalleryVideo_videosId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "ProfileDesa" DROP CONSTRAINT "ProfileDesa_profilPerbekelId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "GalleryVideo_videosId_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "nomor";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "GalleryFoto" DROP COLUMN "image",
|
||||
ADD COLUMN "deskripsi" TEXT NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "GalleryVideo" DROP COLUMN "video",
|
||||
DROP COLUMN "videosId",
|
||||
ADD COLUMN "deskripsi" TEXT NOT NULL,
|
||||
ADD COLUMN "linkVideo" TEXT NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "IndeksKepuasanMasyarakat" DROP CONSTRAINT "IndeksKepuasanMasyarakat_pkey",
|
||||
ALTER COLUMN "id" DROP DEFAULT,
|
||||
ALTER COLUMN "id" SET DATA TYPE TEXT,
|
||||
ADD CONSTRAINT "IndeksKepuasanMasyarakat_pkey" PRIMARY KEY ("id");
|
||||
DROP SEQUENCE "IndeksKepuasanMasyarakat_id_seq";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProfileDesa" DROP COLUMN "profilPerbekelId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProfilePPID" DROP COLUMN "imageUrl",
|
||||
ADD COLUMN "imageId" TEXT;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Images";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Videos";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StrukturPPID" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"imageId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "StrukturPPID_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProfileDesaImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"profileDesaId" TEXT NOT NULL,
|
||||
"imageId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ProfileDesaImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PelayananSuratKeterangan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"imageId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "PelayananSuratKeterangan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PelayananTelunjukSaktiDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"link" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "PelayananTelunjukSaktiDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PelayananPerizinanBerusaha" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"link" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "PelayananPerizinanBerusaha_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PelayananPendudukNonPermanen" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "PelayananPendudukNonPermanen_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Penghargaan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"juara" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"imageId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "Penghargaan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Posyandu" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nomor" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"imageId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "Posyandu_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StrukturPPID" ADD CONSTRAINT "StrukturPPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProfilePPID" ADD CONSTRAINT "ProfilePPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_profileDesaId_fkey" FOREIGN KEY ("profileDesaId") REFERENCES "ProfileDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "GalleryFoto" ADD CONSTRAINT "GalleryFoto_imagesId_fkey" FOREIGN KEY ("imagesId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PelayananSuratKeterangan" ADD CONSTRAINT "PelayananSuratKeterangan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Penghargaan" ADD CONSTRAINT "Penghargaan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Posyandu" ADD CONSTRAINT "Posyandu_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
78
prisma/migrations/20250617083234_17jun/migration.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `profileDesaId` on the `ProfileDesaImage` table. All the data in the column will be lost.
|
||||
- You are about to drop the `ProfileDesa` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Added the required column `maskotDesaId` to the `ProfileDesaImage` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "ProfileDesaImage" DROP CONSTRAINT "ProfileDesaImage_profileDesaId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProfilPerbekel" ADD COLUMN "imageId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProfileDesaImage" DROP COLUMN "profileDesaId",
|
||||
ADD COLUMN "maskotDesaId" TEXT NOT NULL;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "ProfileDesa";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SejarahDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"judul" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "SejarahDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VisiMisiDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"visi" TEXT NOT NULL,
|
||||
"misi" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "VisiMisiDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "LambangDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"judul" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "LambangDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MaskotDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"judul" TEXT NOT NULL,
|
||||
"deskripsi" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "MaskotDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_maskotDesaId_fkey" FOREIGN KEY ("maskotDesaId") REFERENCES "MaskotDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProfilPerbekel" ADD CONSTRAINT "ProfilPerbekel_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -50,27 +50,26 @@ model AppMenuChild {
|
||||
// ========================================= FILE STORAGE ========================================= //
|
||||
|
||||
model FileStorage {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
ProfilePPID ProfilePPID[]
|
||||
StrukturPPID StrukturPPID[]
|
||||
|
||||
GalleryFoto GalleryFoto[]
|
||||
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
realName String
|
||||
path String
|
||||
mimeType String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
link String
|
||||
Berita Berita[]
|
||||
PotensiDesa PotensiDesa[]
|
||||
Posyandu Posyandu[]
|
||||
StrukturPPID StrukturPPID[]
|
||||
GalleryFoto GalleryFoto[]
|
||||
PelayananSuratKeterangan PelayananSuratKeterangan[]
|
||||
|
||||
Penghargaan Penghargaan[]
|
||||
Penghargaan Penghargaan[]
|
||||
ProfileDesaImage ProfileDesaImage[]
|
||||
ProfilePPID ProfilePPID[]
|
||||
ProfilPerbekel ProfilPerbekel[]
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
@@ -117,6 +116,7 @@ model ProfilePPID {
|
||||
riwayat String @db.Text
|
||||
pengalaman String @db.Text
|
||||
unggulan String @db.Text
|
||||
imageUrl String?
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
@@ -248,32 +248,68 @@ model GrafikBerdasarkanUmur {
|
||||
|
||||
// ========================================= MENU DESA ========================================= //
|
||||
// ========================================= PROFILE DESA ========================================= //
|
||||
model ProfileDesa {
|
||||
id String @id @default(cuid())
|
||||
sejarah String @db.Text
|
||||
visi String @db.Text
|
||||
misi String @db.Text
|
||||
lambang String @db.Text
|
||||
maskot String @db.Text
|
||||
ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id])
|
||||
profilPerbekelId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
model SejarahDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String @db.Text
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model VisiMisiDesa {
|
||||
id String @id @default(cuid())
|
||||
visi String @db.Text
|
||||
misi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model LambangDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model MaskotDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
images ProfileDesaImage[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model ProfileDesaImage {
|
||||
id String @id @default(cuid())
|
||||
label String
|
||||
image FileStorage @relation(fields: [imageId], references: [id])
|
||||
imageId String
|
||||
MaskotDesa MaskotDesa @relation(fields: [maskotDesaId], references: [id])
|
||||
maskotDesaId String
|
||||
}
|
||||
|
||||
model ProfilPerbekel {
|
||||
id String @id @default(cuid())
|
||||
biodata String @db.Text
|
||||
pengalaman String @db.Text
|
||||
pengalamanOrganisasi String @db.Text
|
||||
programUnggulan String @db.Text
|
||||
ProfileDesa ProfileDesa[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
biodata String @db.Text
|
||||
pengalaman String @db.Text
|
||||
pengalamanOrganisasi String @db.Text
|
||||
programUnggulan String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= BERITA ========================================= //
|
||||
@@ -412,16 +448,16 @@ model PelayananPendudukNonPermanen {
|
||||
|
||||
// ========================================= PENGHARGAAN ========================================= //
|
||||
model Penghargaan {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
juara String
|
||||
deskripsi String @db.Text
|
||||
deskripsi String @db.Text
|
||||
image FileStorage @relation(fields: [imageId], references: [id])
|
||||
imageId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= MENU KESEHATAN ========================================= //
|
||||
|
||||
106
prisma/seed.ts
@@ -12,6 +12,11 @@ import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||
import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
|
||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
|
||||
import lambangDesa from "./data/desa/profile/lambang_desa.json";
|
||||
import maskotDesa from "./data/desa/profile/maskot_desa.json";
|
||||
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
|
||||
|
||||
(async () => {
|
||||
for (const l of layanan) {
|
||||
@@ -30,6 +35,104 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
|
||||
|
||||
console.log("layanan success ...");
|
||||
|
||||
for (const l of sejarahDesa) {
|
||||
await prisma.sejarahDesa.upsert({
|
||||
where: {
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
create: {
|
||||
id: l.id,
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("sejarah desa success ...");
|
||||
|
||||
for (const l of maskotDesa) {
|
||||
await prisma.maskotDesa.upsert({
|
||||
where: {
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
create: {
|
||||
id: l.id,
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("maskot desa success ...");
|
||||
|
||||
for (const l of lambangDesa) {
|
||||
await prisma.lambangDesa.upsert({
|
||||
where: {
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
create: {
|
||||
id: l.id,
|
||||
judul: l.judul,
|
||||
deskripsi: l.deskripsi,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("lambang desa success ...");
|
||||
|
||||
for (const c of profilPerbekel) {
|
||||
await prisma.profilPerbekel.upsert({
|
||||
where: { id: c.id },
|
||||
update: {
|
||||
biodata: c.biodata,
|
||||
pengalaman: c.pengalaman,
|
||||
pengalamanOrganisasi: c.pengalamanOrganisasi,
|
||||
programUnggulan: c.programUnggulan,
|
||||
// imageId tidak di-update
|
||||
},
|
||||
create: {
|
||||
id: c.id,
|
||||
biodata: c.biodata,
|
||||
pengalaman: c.pengalaman,
|
||||
pengalamanOrganisasi: c.pengalamanOrganisasi,
|
||||
programUnggulan: c.programUnggulan,
|
||||
// imageId tidak di-create
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
|
||||
|
||||
for (const l of visiMisiDesa) {
|
||||
await prisma.visiMisiDesa.upsert({
|
||||
where: {
|
||||
id: l.id,
|
||||
},
|
||||
update: {
|
||||
visi: l.visi,
|
||||
misi: l.misi,
|
||||
},
|
||||
create: {
|
||||
id: l.id,
|
||||
visi: l.visi,
|
||||
misi: l.misi,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("visi misi desa success ...");
|
||||
|
||||
for (const l of pelayananPerizinanBerusaha) {
|
||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||
where: {
|
||||
@@ -67,8 +170,7 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("pelayanan perizinan berusaha success ...");
|
||||
console.log("pelayanan penduduk non permanen success ...");
|
||||
|
||||
for (const s of strukturPPID) {
|
||||
await prisma.strukturPPID.upsert({
|
||||
|
||||
BIN
public/bungapudak.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
public/darmasaba-icon.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
public/klimakstari.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/pohonpudak.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |
BIN
public/tarisekar.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
@@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
|
||||
const templateForm = z.object({
|
||||
name: z.string().min(1).max(50),
|
||||
deskripsi: z.string().min(1).max(50),
|
||||
deskripsi: z.string().min(1).max(5000),
|
||||
kategori: z.string().min(1).max(50),
|
||||
imageId: z.string().min(1).max(50),
|
||||
content: z.string().min(1).max(5000),
|
||||
|
||||
@@ -125,16 +125,6 @@ function EditBerita() {
|
||||
placeholder="masukkan judul"
|
||||
/>
|
||||
|
||||
<SelectCategory
|
||||
value={formData.kategoriBeritaId}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
kategoriBeritaId: val?.id || ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
value={formData.deskripsi}
|
||||
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
||||
@@ -174,6 +164,16 @@ function EditBerita() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<SelectCategory
|
||||
value={formData.kategoriBeritaId}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
kategoriBeritaId: val?.id || ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Edit Berita</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -63,9 +63,9 @@ export default function CreateBerita() {
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
@@ -79,8 +79,9 @@ export default function CreateBerita() {
|
||||
placeholder="masukkan judul"
|
||||
/>
|
||||
<SelectCategory
|
||||
value={beritaState.berita.create.form.kategoriBeritaId}
|
||||
onChange={(val) => {
|
||||
beritaState.berita.create.form.kategoriBeritaId = val.id;
|
||||
beritaState.berita.create.form.kategoriBeritaId = val?.id || "";
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
@@ -114,10 +115,10 @@ export default function CreateBerita() {
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||
<CreateEditor
|
||||
value={beritaState.berita.create.form.content}
|
||||
onChange={(htmlContent) => {
|
||||
beritaState.berita.create.form.content = htmlContent;
|
||||
}}
|
||||
value={beritaState.berita.create.form.content}
|
||||
onChange={(htmlContent) => {
|
||||
beritaState.berita.create.form.content = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
|
||||
@@ -126,26 +127,37 @@ export default function CreateBerita() {
|
||||
</Box>
|
||||
);
|
||||
|
||||
function SelectCategory({
|
||||
onChange,
|
||||
}: {
|
||||
interface SelectCategoryProps {
|
||||
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
id: true;
|
||||
};
|
||||
}>) => void;
|
||||
}) {
|
||||
}> | null) => void;
|
||||
value?: string | null;
|
||||
defaultValue?: string | null;
|
||||
}
|
||||
|
||||
function SelectCategory({
|
||||
onChange,
|
||||
value,
|
||||
defaultValue,
|
||||
}: SelectCategoryProps) {
|
||||
const categoryState = useProxy(stateDashboardBerita.category);
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
categoryState.findMany.load();
|
||||
categoryState.findMany.load().then(() => {
|
||||
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
if (!categoryState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const selectedValue = value || defaultValue;
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||
@@ -154,13 +166,19 @@ export default function CreateBerita() {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(val) => {
|
||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
onChange(selected);
|
||||
value={selectedValue || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
onChange(selected);
|
||||
}
|
||||
} else {
|
||||
onChange(null);
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
export function convertYoutubeUrlToEmbed(url: string): string | null {
|
||||
const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
|
||||
const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
|
||||
export function convertYoutubeUrlToEmbed(url: string) {
|
||||
const videoIdMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
|
||||
return videoIdMatch ? `https://www.youtube.com/embed/${videoIdMatch[1]}` : null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// (url: string): string | null {
|
||||
// const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
|
||||
// const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
|
||||
|
||||
const matchWatch = url.match(watchRegex);
|
||||
const matchShort = url.match(shortRegex);
|
||||
// const matchWatch = url.match(watchRegex);
|
||||
// const matchShort = url.match(shortRegex);
|
||||
|
||||
if (matchWatch) {
|
||||
return `https://www.youtube.com/embed/${matchWatch[1]}`;
|
||||
}
|
||||
// if (matchWatch) {
|
||||
// return `https://www.youtube.com/embed/${matchWatch[1]}`;
|
||||
// }
|
||||
|
||||
if (matchShort) {
|
||||
return `https://www.youtube.com/embed/${matchShort[1]}`;
|
||||
}
|
||||
// if (matchShort) {
|
||||
// return `https://www.youtube.com/embed/${matchShort[1]}`;
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
// return null;
|
||||
// }
|
||||
|
||||
@@ -11,18 +11,16 @@ import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
||||
|
||||
|
||||
|
||||
function EditVideo() {
|
||||
const router = useRouter();
|
||||
const [embedLink, setEmbedLink] = useState("");
|
||||
const router = useRouter();
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const params = useParams()
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: videoState.findUnique.data?.name || '',
|
||||
deskripsi: videoState.findUnique.data?.deskripsi || '',
|
||||
linkVideo: videoState.findUnique.data?.linkVideo || '',
|
||||
})
|
||||
name: '',
|
||||
deskripsi: '',
|
||||
linkVideo: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadVideo = async () => {
|
||||
@@ -36,8 +34,6 @@ function EditVideo() {
|
||||
deskripsi: data.deskripsi || '',
|
||||
linkVideo: data.linkVideo || '',
|
||||
});
|
||||
const embed = convertYoutubeUrlToEmbed(data.linkVideo);
|
||||
setEmbedLink(embed || "");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading video:', error);
|
||||
@@ -47,7 +43,15 @@ function EditVideo() {
|
||||
loadVideo();
|
||||
}, [params?.id]);
|
||||
|
||||
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||
if (!converted) {
|
||||
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
videoState.update.form = {
|
||||
...videoState.update.form,
|
||||
@@ -55,11 +59,6 @@ function EditVideo() {
|
||||
deskripsi: formData.deskripsi,
|
||||
linkVideo: formData.linkVideo,
|
||||
};
|
||||
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||
if (!converted) {
|
||||
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||
return;
|
||||
}
|
||||
await videoState.update.update();
|
||||
toast.success('Video berhasil diperbarui!');
|
||||
router.push('/admin/desa/gallery/video');
|
||||
@@ -80,29 +79,23 @@ function EditVideo() {
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Video</Title>
|
||||
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||
placeholder='Masukkan judul video'
|
||||
value={formData.name}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
name: val.target.value,
|
||||
})
|
||||
setFormData({ ...formData, name: val.target.value });
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<TextInput
|
||||
label="Link Video YouTube"
|
||||
placeholder="https://www.youtube.com/watch?v=abc123"
|
||||
value={formData.linkVideo}
|
||||
onChange={(e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
linkVideo: e.currentTarget.value,
|
||||
})
|
||||
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
||||
setEmbedLink(embed || "");
|
||||
setFormData({ ...formData, linkVideo: e.currentTarget.value });
|
||||
}}
|
||||
required
|
||||
/>
|
||||
@@ -118,18 +111,17 @@ function EditVideo() {
|
||||
></iframe>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
deskripsi: val,
|
||||
})
|
||||
setFormData({ ...formData, deskripsi: val });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
|
||||
@@ -16,7 +16,7 @@ function CreateVideo() {
|
||||
const videoState = useProxy(stateGallery.video)
|
||||
const router = useRouter();
|
||||
const [link, setLink] = useState("");
|
||||
const [embedLink, setEmbedLink] = useState("");
|
||||
const embedLink = convertYoutubeUrlToEmbed(link);
|
||||
|
||||
const resetForm = () => {
|
||||
videoState.create.form = {
|
||||
@@ -26,15 +26,17 @@ function CreateVideo() {
|
||||
};
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
const converted = convertYoutubeUrlToEmbed(videoState.create.form.linkVideo);
|
||||
if (!converted) {
|
||||
if (!embedLink) {
|
||||
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||
return;
|
||||
}
|
||||
|
||||
videoState.create.form.linkVideo = embedLink; // pastikan diset di sini juga (jaga-jaga)
|
||||
await videoState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/gallery/video")
|
||||
router.push("/admin/desa/gallery/video");
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -63,8 +65,6 @@ function CreateVideo() {
|
||||
value={link}
|
||||
onChange={(e) => {
|
||||
setLink(e.currentTarget.value);
|
||||
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
||||
setEmbedLink(embed || "");
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Profile Desa",
|
||||
value: "profiledesa",
|
||||
href: "/admin/desa/profile/profile-desa"
|
||||
},
|
||||
{
|
||||
label: "Profile Perbekel",
|
||||
value: "profileperbekel",
|
||||
href: "/admin/desa/profile/profile-perbekel"
|
||||
}
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Profile Desa</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsDetail;
|
||||
@@ -0,0 +1,71 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabsEdit({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Sejarah Desa",
|
||||
value: "sejarahdesa",
|
||||
href: "/admin/desa/profile/edit/sejarah_desa"
|
||||
},
|
||||
{
|
||||
label: "Visi Misi Desa",
|
||||
value: "visimisidesa",
|
||||
href: "/admin/desa/profile/edit/visi_misi_desa"
|
||||
},
|
||||
{
|
||||
label: "Lambang Desa",
|
||||
value: "lambangdesa",
|
||||
href: "/admin/desa/profile/edit/lambang_desa"
|
||||
},
|
||||
{
|
||||
label: "Maskot Desa",
|
||||
value: "maskotdesa",
|
||||
href: "/admin/desa/profile/edit/maskot_desa"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Tabs color={colors['blue-button']} variant='default' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsEdit;
|
||||
11
src/app/admin/(dashboard)/desa/profile/layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import LayoutTabsDetail from "./_lib/layoutTabsDetail"
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabsDetail>
|
||||
{children}
|
||||
</LayoutTabsDetail>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Title, Tabs, TabsList, TabsTab, TabsPanel } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import SejarahDesa from './ui/sejarah_desa/page';
|
||||
import VisiMisiDesa from './ui/visi_misi_desa/page';
|
||||
import LambangDesa from './ui/lambang_desa/page';
|
||||
import MaskotDesa from './ui/maskot_desa/page';
|
||||
import ProfilePerbekel from './ui/profile_perbekel/page';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Stack >
|
||||
<Title order={3}>Profile Desa</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Sejarah Desa"}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
<TabsTab value="Sejarah Desa">
|
||||
Sejarah Desa
|
||||
</TabsTab>
|
||||
<TabsTab value="Visi Misi Desa">
|
||||
Visi Misi Desa
|
||||
</TabsTab>
|
||||
<TabsTab value="Lambang Desa">
|
||||
Lambang Desa
|
||||
</TabsTab>
|
||||
<TabsTab value="Maskot Desa">
|
||||
Maskot Desa
|
||||
</TabsTab>
|
||||
<TabsTab value="Profile Perbekel">
|
||||
Profile Perbekel
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
|
||||
<TabsPanel value="Sejarah Desa">
|
||||
<SejarahDesa/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Visi Misi Desa">
|
||||
<VisiMisiDesa/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Lambang Desa">
|
||||
<LambangDesa/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Maskot Desa">
|
||||
<MaskotDesa/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Profile Perbekel">
|
||||
<ProfilePerbekel/>
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,126 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
const lambangState = useProxy(stateProfileDesa.lambangDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await lambangState.findUnique.load(id);
|
||||
if (data) {
|
||||
lambangState.update.initialize(data);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
lambangState.update.reset();
|
||||
lambangState.findUnique.reset(); // opsional: reset juga data lama
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !lambangState.update.form.judul.trim()) {
|
||||
toast.error("Judul wajib diisi");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const success = await lambangState.update.submit()
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update lambang desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update lambang desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
if (
|
||||
lambangState.findUnique.loading ||
|
||||
!lambangState.findUnique.data ||
|
||||
lambangState.update.loading
|
||||
) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Lambang Desa</Title>
|
||||
<TextInput
|
||||
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
|
||||
placeholder="Judul"
|
||||
value={lambangState.update.form.judul}
|
||||
onChange={(e) => lambangState.update.form.judul = e.currentTarget.value}
|
||||
error={!lambangState.update.form.judul && "Judul wajib diisi"}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||
<EditEditor
|
||||
value={lambangState.update.form.deskripsi}
|
||||
onChange={(val) => lambangState.update.form.deskripsi = val}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,244 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
const maskotState = useProxy(stateProfileDesa.maskotDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
|
||||
const [images, setImages] = useState<
|
||||
Array<{ file: File; preview: string; label: string }>
|
||||
>([]);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
judul: maskotState.update.form.judul || '',
|
||||
deskripsi: maskotState.update.form.deskripsi || '',
|
||||
images: [] as Array<{ label: string; imageId: string }>
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await maskotState.findUnique.load(id);
|
||||
if (data) {
|
||||
// 🔥 INI YANG KURANG!
|
||||
maskotState.update.initialize(data);
|
||||
|
||||
setFormData({
|
||||
judul: data.judul || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
images: (data.images || []).map((img: any) => ({
|
||||
label: img.label,
|
||||
imageId: img.image?.id ?? '',
|
||||
})),
|
||||
});
|
||||
|
||||
if (data?.images?.length > 0 && data.images[0].image?.link) {
|
||||
setImages(data.images.map((img: any) => ({
|
||||
file: null,
|
||||
preview: img.image.link,
|
||||
label: img.label,
|
||||
})));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error("Gagal memuat data berita");
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [params?.id]);
|
||||
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const uploadedImages = [];
|
||||
|
||||
// Upload semua gambar baru
|
||||
for (const img of images) {
|
||||
if (!img.file || !(img.file instanceof File)) {
|
||||
toast.error("File tidak valid untuk di-upload");
|
||||
continue; // atau return kalau kamu mau hentikan semua
|
||||
}
|
||||
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file: img.file,
|
||||
name: img.file.name,
|
||||
});
|
||||
|
||||
const uploaded = res.data?.data;
|
||||
if (!uploaded?.id) {
|
||||
toast.error("Gagal upload salah satu gambar");
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedImages.push({
|
||||
imageId: uploaded.id,
|
||||
label: img.label || 'main',
|
||||
});
|
||||
}
|
||||
|
||||
// Update ke global state
|
||||
maskotState.update.updateField("judul", formData.judul);
|
||||
maskotState.update.updateField("deskripsi", formData.deskripsi);
|
||||
maskotState.update.updateField("images", uploadedImages);
|
||||
|
||||
const success = await maskotState.update.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success("Maskot berhasil diperbarui!");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error update maskot:", error);
|
||||
toast.error("Gagal update maskot");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '100%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Maskot Desa</Title>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
|
||||
placeholder="Masukkan judul"
|
||||
value={formData.judul}
|
||||
onChange={(val) => setFormData({ ...formData, judul: val.currentTarget.value })}
|
||||
/>
|
||||
<Box w={{ base: '100%', md: '50%' }}>
|
||||
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box w={{ base: '100%', md: '50%' }}>
|
||||
<Dropzone
|
||||
|
||||
onDrop={(files) => {
|
||||
const newImages = files.map((file) => ({
|
||||
file,
|
||||
preview: URL.createObjectURL(file),
|
||||
label: '',
|
||||
}));
|
||||
setImages((prev) => [...prev, ...newImages]);
|
||||
}}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag images here or click to select files
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Attach as many files as you like, each file should not exceed 5mb
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
</Box>
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 2, md: 4 }} >
|
||||
{images.map((img, index) => (
|
||||
<Box key={index} mb="md">
|
||||
<Paper p="sm" radius="md" withBorder style={{ position: 'relative', maxWidth: 300 }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="light"
|
||||
onClick={() => {
|
||||
const updated = [...images];
|
||||
updated.splice(index, 1);
|
||||
setImages(updated);
|
||||
}}
|
||||
>
|
||||
Hapus
|
||||
</Button>
|
||||
</Group>
|
||||
<Image
|
||||
src={img.preview}
|
||||
alt={`Preview ${index}`}
|
||||
width={280}
|
||||
height={180}
|
||||
fit="cover"
|
||||
radius="sm"
|
||||
/>
|
||||
<TextInput
|
||||
label={`Label Gambar ${index + 1}`}
|
||||
placeholder="Contoh: Logo, Maskot, Dll"
|
||||
value={img.label}
|
||||
onChange={(e) => {
|
||||
const updated = [...images];
|
||||
updated[index].label = e.currentTarget.value;
|
||||
setImages(updated);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,126 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
const sejarahState = useProxy(stateProfileDesa.sejarahDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await sejarahState.findUnique.load(id);
|
||||
if (data) {
|
||||
sejarahState.update.initialize(data);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
sejarahState.update.reset();
|
||||
sejarahState.findUnique.reset(); // opsional: reset juga data lama
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !sejarahState.update.form.judul.trim()) {
|
||||
toast.error("Judul wajib diisi");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const success = await sejarahState.update.submit()
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update sejarah desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update sejarah desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
if (
|
||||
sejarahState.findUnique.loading ||
|
||||
!sejarahState.findUnique.data ||
|
||||
sejarahState.update.loading
|
||||
) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Sejarah Desa</Title>
|
||||
<TextInput
|
||||
label={<Text fz={"md"} fw={"bold"}>Judul</Text>}
|
||||
placeholder="Judul"
|
||||
value={sejarahState.update.form.judul}
|
||||
onChange={(e) => sejarahState.update.form.judul = e.currentTarget.value}
|
||||
error={!sejarahState.update.form.judul && "Judul wajib diisi"}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||
<EditEditor
|
||||
value={sejarahState.update.form.deskripsi}
|
||||
onChange={(val) => sejarahState.update.form.deskripsi = val}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,124 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function Page() {
|
||||
const visiMisiState = useProxy(stateProfileDesa.visiMisiDesa)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await visiMisiState.findUnique.load(id);
|
||||
if (data) {
|
||||
visiMisiState.update.initialize(data);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
visiMisiState.update.reset();
|
||||
visiMisiState.findUnique.reset(); // opsional: reset juga data lama
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !visiMisiState.update.form.visi.trim()) {
|
||||
toast.error("Visi wajib diisi");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const success = await visiMisiState.update.submit()
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update sejarah desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update sejarah desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
if (
|
||||
visiMisiState.findUnique.loading ||
|
||||
!visiMisiState.findUnique.data ||
|
||||
visiMisiState.update.loading
|
||||
) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Visi Misi Desa</Title>
|
||||
<Text fz={"md"} fw={"bold"}>Visi</Text>
|
||||
<EditEditor
|
||||
value={visiMisiState.update.form.visi}
|
||||
onChange={(val) => visiMisiState.update.form.visi = val}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Misi</Text>
|
||||
<EditEditor
|
||||
value={visiMisiState.update.form.misi}
|
||||
onChange={(val) => visiMisiState.update.form.misi = val}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
171
src/app/admin/(dashboard)/desa/profile/profile-desa/page.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client'
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Card, Center, Grid, GridCol, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import stateProfileDesa from '../../../_state/desa/profile';
|
||||
import { useEffect } from 'react';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter();
|
||||
const snap = useSnapshot(stateProfileDesa);
|
||||
|
||||
// Panggil load data sekali saat komponen mount
|
||||
useEffect(() => {
|
||||
stateProfileDesa.sejarahDesa.findUnique.load("edit");
|
||||
stateProfileDesa.visiMisiDesa.findUnique.load("edit");
|
||||
stateProfileDesa.lambangDesa.findUnique.load("edit");
|
||||
stateProfileDesa.maskotDesa.findUnique.load("edit");
|
||||
}, []);
|
||||
|
||||
const sejarah = snap.sejarahDesa.findUnique.data;
|
||||
const visiMisi = snap.visiMisiDesa.findUnique.data;
|
||||
const lambang = snap.lambangDesa.findUnique.data;
|
||||
const maskot = snap.maskotDesa.findUnique.data;
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"lg"}>
|
||||
<Title order={2}>Preview Profile Desa</Title>
|
||||
{/* Sejarah Desa */}
|
||||
{sejarah && (
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"md"} bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Sejarah Desa</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/desa/profile/profile-desa/${sejarah.id}/sejarah_desa`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
||||
</Center>
|
||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>{sejarah.judul}</Text>
|
||||
</Box>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: sejarah.deskripsi }} />
|
||||
</Paper>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Visi Misi Desa */}
|
||||
{visiMisi && (
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"md"} bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Visi Misi Desa</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${visiMisi.id}/visi_misi_desa`)} bg={colors['blue-button']}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
||||
</Center>
|
||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Visi Misi Desa</Text>
|
||||
</Box>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<Text fw={"bold"} fz={{ base: "lg", md: "h2" }}>Visi Desa</Text>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: visiMisi.visi }} />
|
||||
<Text fw={"bold"} fz={{ base: "lg", md: "h2" }}>Misi Desa</Text>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: visiMisi.misi }} />
|
||||
</Paper>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Lambang Desa */}
|
||||
{lambang && (
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"md"} bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Lambang Desa</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${lambang.id}/lambang_desa`)} bg={colors['blue-button']}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
||||
</Center>
|
||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Lambang Desa</Text>
|
||||
</Box>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: lambang.deskripsi }} />
|
||||
</Paper>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Maskot Desa */}
|
||||
{maskot && (
|
||||
<Box>
|
||||
<Stack gap={'lg'}>
|
||||
<Paper p={"md"} bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={2}>Preview Maskot Desa</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push(`/admin/desa/profile/profile-desa/${maskot.id}/maskot_desa`)} bg={colors['blue-button']}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Center>
|
||||
<Image src={"/pudak-icon.png"} alt="" w={{ base: 200, md: 300 }} />
|
||||
</Center>
|
||||
<Text c={colors['blue-button']} ta={"center"} fw={"bold"} fz={"2.5rem"}>Maskot Desa</Text>
|
||||
</Box>
|
||||
<Paper p={"xl"} bg={colors['white-trans-1']}>
|
||||
<Text fz={{ base: "md", md: "h3" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: maskot.deskripsi }} />
|
||||
<Group wrap="wrap" gap="md">
|
||||
{maskot.images.map((img, index) => (
|
||||
<Card key={index} p="xs" w={220}>
|
||||
<Image
|
||||
src={img.image.link}
|
||||
alt={img.label}
|
||||
w={200}
|
||||
h={200}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
style={{ border: '1px solid #ccc', objectFit: 'cover' }}
|
||||
/>
|
||||
<Text ta="center" mt="xs" fw="bold">{img.label}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Group>
|
||||
</Paper>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function ProfilePerbekel() {
|
||||
const perbekelState = useProxy(stateProfileDesa.profilPerbekel)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/desa/profile/profile-perbekel");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await perbekelState.findUnique.load(id);
|
||||
if (data) {
|
||||
perbekelState.edit.initialize(data);
|
||||
}
|
||||
if (data?.image?.link) {
|
||||
setPreviewImage(data.image.link);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
perbekelState.edit.reset();
|
||||
perbekelState.findUnique.reset(); // opsional: reset juga data lama
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
const handleFileChange = (newFile: File | null) => {
|
||||
if (!newFile) {
|
||||
setFile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setFile(newFile);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setPreviewImage(event.target?.result as string);
|
||||
};
|
||||
reader.readAsDataURL(newFile);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
|
||||
toast.error("Biodata wajib diisi");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
if (file) {
|
||||
const uploadResponse = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||
const uploaded = uploadResponse.data?.data;
|
||||
|
||||
if (!uploaded?.id) {
|
||||
toast.error("Gagal upload gambar");
|
||||
return;
|
||||
}
|
||||
|
||||
perbekelState.edit.form.imageId = uploaded.id;
|
||||
}
|
||||
const success = await perbekelState.edit.submit()
|
||||
if (success) {
|
||||
toast.success("Data berhasil disimpan");
|
||||
router.push("/admin/desa/profile/profile-perbekel");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error update sejarah desa:", error);
|
||||
toast.error("Terjadi kesalahan saat update sejarah desa");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
if (
|
||||
perbekelState.findUnique.loading ||
|
||||
!perbekelState.findUnique.data ||
|
||||
perbekelState.edit.loading
|
||||
) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Stack gap={'xs'}>
|
||||
<Group>
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Group>
|
||||
<Paper bg={colors['white-1']} p={'xs'} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Profil Perbekel</Title>
|
||||
<Text fz={"md"} fw={"bold"}>Biodata</Text>
|
||||
<EditEditor
|
||||
value={perbekelState.edit.form.biodata}
|
||||
onChange={(val) => perbekelState.edit.form.biodata = val}
|
||||
/>
|
||||
{/* File Upload */}
|
||||
<FileInput
|
||||
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
|
||||
value={file}
|
||||
onChange={handleFileChange}
|
||||
accept="image/*"
|
||||
/>
|
||||
|
||||
{/* Preview Gambar */}
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
||||
{previewImage ? (
|
||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
||||
) : (
|
||||
<Center w={200} h={200} bg="gray.2">
|
||||
<Stack align="center" gap="xs">
|
||||
<IconImageInPicture size={48} color="gray" />
|
||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Pengalaman</Text>
|
||||
<EditEditor
|
||||
value={perbekelState.edit.form.pengalaman}
|
||||
onChange={(val) => perbekelState.edit.form.pengalaman = val}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Pengalaman Organisasi</Text>
|
||||
<EditEditor
|
||||
value={perbekelState.edit.form.pengalamanOrganisasi}
|
||||
onChange={(val) => perbekelState.edit.form.pengalamanOrganisasi = val}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Program Unggulan</Text>
|
||||
<EditEditor
|
||||
value={perbekelState.edit.form.programUnggulan}
|
||||
onChange={(val) => perbekelState.edit.form.programUnggulan = val}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePerbekel;
|
||||
112
src/app/admin/(dashboard)/desa/profile/profile-perbekel/page.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
'use client'
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import stateProfileDesa from '../../../_state/desa/profile';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter();
|
||||
const snap = useSnapshot(stateProfileDesa);
|
||||
|
||||
// Panggil load data sekali saat komponen mount
|
||||
useEffect(() => {
|
||||
stateProfileDesa.profilPerbekel.findUnique.load("edit");
|
||||
}, []);
|
||||
|
||||
const perbekel = snap.profilPerbekel.findUnique.data;
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={3}>Preview Profile PPID</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/desa/profile/profile-perbekel/${snap.profilPerbekel.findUnique.data?.id}`)}>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
{perbekel && (
|
||||
<Box >
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 12 }}>
|
||||
<Center>
|
||||
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 12 }}>
|
||||
<Text ta={"center"} fz={{ base: "1.2rem", md: "1.8rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Divider my={"md"} color={colors['blue-button']} />
|
||||
{/* biodata perbekel */}
|
||||
<Box px={{ base: 0, md: 50 }} pb={30}>
|
||||
<Box pb={20} px={{ base: 0, md: 50 }}>
|
||||
<Paper bg={colors['BG-trans']} w={{ base: "100%", md: "100%" }}>
|
||||
<Stack gap={0}>
|
||||
<Center>
|
||||
<Image
|
||||
pt={{ base: 0, md: 90 }}
|
||||
src={perbekel.image?.link || "/perbekel.png"}
|
||||
w={{ base: 250, md: 350 }}
|
||||
alt='Foto Profil PPID'
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/perbekel.png";
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
<Paper
|
||||
bg={colors['blue-button']}
|
||||
py={20}
|
||||
className="glass3"
|
||||
px={{ base: 10, md: 10 }}
|
||||
|
||||
>
|
||||
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.2rem", md: "1.6rem" }}>
|
||||
I.B. Surya Prabhawa Manuaba, S.H.,M.H.,NL.P.
|
||||
</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box pt={10}>
|
||||
<Box>
|
||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Biodata</Text>
|
||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.biodata }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman</Text>
|
||||
<Text fz={{ base: "1rem", md: "1.5rem" }} dangerouslySetInnerHTML={{ __html: perbekel.pengalaman }} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box pb={30}>
|
||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Pengalaman Organisasi</Text>
|
||||
<Box px={20}>
|
||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.pengalamanOrganisasi }} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box pb={20}>
|
||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Program Kerja Unggulan</Text>
|
||||
<Box px={20}>
|
||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: perbekel.programUnggulan }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,39 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { DesaEditor } from '../../../_com/desaEditor';
|
||||
|
||||
function LambangDesa() {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Lambang Desa</Title>
|
||||
<Text fw={"bold"}>Deskripsi Lambang Desa</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Lambang Desa</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default LambangDesa;
|
||||
@@ -1,39 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { DesaEditor } from '../../../_com/desaEditor';
|
||||
|
||||
function MaskotDesa() {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Maskot Desa</Title>
|
||||
<Text fw={"bold"}>Deskripsi Maskot Desa</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Maskot Desa</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MaskotDesa;
|
||||
@@ -1,49 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, TextInput, Text } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { DesaEditor } from '../../../_com/desaEditor';
|
||||
|
||||
function ProfilePerbekel() {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Profil Perbekel</Title>
|
||||
<TextInput
|
||||
label="Nama Perbekel"
|
||||
placeholder="masukkan nama perbekel"
|
||||
/>
|
||||
<Text fz={"sm"} fw={"bold"}>Biodata</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Text fz={"sm"} fw={"bold"}>Pengalaman</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Text fz={"sm"} fw={"bold"}>Pengalaman Organisasi</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Text fz={"sm"} fw={"bold"}>Program Unggulan</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Profil Perbekel</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePerbekel;
|
||||
@@ -1,17 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Paper, Stack, Title } from '@mantine/core';
|
||||
import React from 'react';
|
||||
|
||||
function ListPage() {
|
||||
return (
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Sejarah Desa</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListPage;
|
||||
@@ -1,65 +0,0 @@
|
||||
'use client'
|
||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import DesaEditorText from '../../../_com/desaEditorText';
|
||||
import ListPage from './listPage';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
|
||||
function SejarahDesa() {
|
||||
const stateSejarah = useProxy(stateProfileDesa.Sejarah)
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (!stateSejarah.findById.data) {
|
||||
stateSejarah.findById.initialize()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const submit = () => {
|
||||
if (stateSejarah.findById.data?.id && stateSejarah.findById.data.sejarah) {
|
||||
stateSejarah.update.save({
|
||||
id: stateSejarah.findById.data.id,
|
||||
sejarah: stateSejarah.findById.data.sejarah
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Sejarah Desa</Title>
|
||||
<Text fw={"bold"}>Deskripsi Sejarah Desa</Text>
|
||||
<DesaEditorText
|
||||
key={stateSejarah.findById.data?.id ?? 'new'}
|
||||
showSubmit={false}
|
||||
onChange={(val) => {
|
||||
if (stateSejarah.findById.data) {
|
||||
stateSejarah.findById.data.sejarah = val
|
||||
}
|
||||
}}
|
||||
initialContent={stateSejarah.findById.data?.sejarah ?? ""}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<ListPage />
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default SejarahDesa;
|
||||
@@ -1,64 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
|
||||
import { DesaEditor } from '../../../_com/desaEditor';
|
||||
|
||||
function VisiMisiDesa() {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Visi Desa</Title>
|
||||
<Text fw={"bold"}>Deskripsi Visi Desa</Text>
|
||||
<DesaEditor showSubmit={false} />
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Visi Desa</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
<SimpleGrid py={30} cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Misi Desa</Title>
|
||||
<Text fw={"bold"}>Deskripsi Misi Desa</Text>
|
||||
<DesaEditor showSubmit={false}/>
|
||||
<Group>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>List Misi Desa</Title>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default VisiMisiDesa;
|
||||
@@ -12,7 +12,7 @@ function Page() {
|
||||
const router = useRouter()
|
||||
const allList = useProxy(stateProfilePPID)
|
||||
useShallowEffect(() => {
|
||||
allList.profile.load("1") // Assuming "1" is your default ID, adjust as needed
|
||||
allList.profile.load("edit") // Assuming "1" is your default ID, adjust as needed
|
||||
}, [])
|
||||
|
||||
if (!allList.profile.data) {
|
||||
@@ -45,7 +45,7 @@ function Page() {
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 12 }}>
|
||||
<Center>
|
||||
<Image src={"/api/img/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
||||
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
||||
</Center>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 12 }}>
|
||||
@@ -62,7 +62,7 @@ function Page() {
|
||||
<Center>
|
||||
<Image
|
||||
pt={{ base: 0, md: 90 }}
|
||||
src={item.image?.link}
|
||||
src={item.image?.link || "/perbekel.png"}
|
||||
w={{ base: 250, md: 350 }}
|
||||
alt='Foto Profil PPID'
|
||||
onError={(e) => {
|
||||
|
||||
@@ -51,7 +51,7 @@ function VisiMisiPPIDList() {
|
||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box pb={30}>
|
||||
<Center>
|
||||
<Image src={"/api/img/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
||||
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
||||
</Center>
|
||||
<Text ta={"center"} fz={{ base: "h2", md: "2.5rem" }} fw={"bold"}>
|
||||
MOTO PPID DESA DARMASABA
|
||||
|
||||
@@ -108,7 +108,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Desa_1",
|
||||
name: "Profile",
|
||||
path: "/admin/desa/profile"
|
||||
path: "/admin/desa/profile/profile-desa"
|
||||
},
|
||||
{
|
||||
id: "Desa_2",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function profilePerbekelFindById(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const data = await prisma.profilPerbekel.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Data berhasil ditemukan",
|
||||
data,
|
||||
}, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile Perbekel:", error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data profile Perbekel",
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import profilePerbekelFindById from "./find-by-id";
|
||||
import profilePerbekelUpdate from "./update";
|
||||
|
||||
const ProfilPerbekel = new Elysia({
|
||||
prefix: "/profileperbekel",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await profilePerbekelFindById(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await profilePerbekelUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
biodata: t.String(),
|
||||
pengalaman: t.String(),
|
||||
pengalamanOrganisasi: t.String(),
|
||||
programUnggulan: t.String(),
|
||||
imageId: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default ProfilPerbekel;
|
||||
@@ -1,33 +1,118 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
|
||||
type FormCreate = Prisma.ProfilPerbekelGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
biodata: true;
|
||||
pengalaman: true;
|
||||
pengalamanOrganisasi: true;
|
||||
programUnggulan: true;
|
||||
}
|
||||
}>
|
||||
type FormUpdate = Prisma.ProfilPerbekelGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
biodata: true;
|
||||
pengalaman: true;
|
||||
pengalamanOrganisasi: true;
|
||||
programUnggulan: true;
|
||||
imageId: true;
|
||||
};
|
||||
}>;
|
||||
export default async function profilePerbekelUpdate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||
|
||||
await prisma.profilPerbekel.update({
|
||||
where: {
|
||||
id: body.id
|
||||
},
|
||||
const { biodata, pengalaman, pengalamanOrganisasi, programUnggulan, imageId } = body;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const exisitng = await prisma.profilPerbekel.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
image: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!exisitng) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}),
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (exisitng.imageId !== imageId) {
|
||||
const oldImage = exisitng.image;
|
||||
if (oldImage) {
|
||||
try {
|
||||
const filePath = path.join(oldImage.path, oldImage.name);
|
||||
await fs.unlink(filePath);
|
||||
await prisma.fileStorage.delete({
|
||||
where: { id: oldImage.id },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus gambar lama:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await prisma.profilPerbekel.update({
|
||||
where: {id},
|
||||
data: {
|
||||
biodata: body.biodata,
|
||||
pengalaman: body.pengalaman,
|
||||
pengalamanOrganisasi: body.pengalamanOrganisasi,
|
||||
programUnggulan: body.programUnggulan,
|
||||
biodata,
|
||||
pengalaman,
|
||||
pengalamanOrganisasi,
|
||||
programUnggulan,
|
||||
imageId,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Profile Perbekel Berhasil Diupdate",
|
||||
}
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Data berhasil diperbarui",
|
||||
data: updated,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error updating profile Perbekel:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengupdate profile Perbekel",
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function profileDesaFindById(context: Context) {
|
||||
try {
|
||||
const id = context?.params?.slugs?.[0];
|
||||
|
||||
// If no ID provided, get the first profile
|
||||
if (!id) {
|
||||
const data = await prisma.profileDesa.findFirst();
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
const data = await prisma.profileDesa.findUniqueOrThrow({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching profileDesa:", error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,18 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import lambangDesaUpdate from "./lambangDesa/update";
|
||||
import maskotDesaUpdate from "./maskotDesa/update";
|
||||
import profilePerbekelUpdate from "../profilePerbekel/update";
|
||||
import sejarahDesaUpdate from "./sejarah/update";
|
||||
import visimisiDesaUpdate from "./visimisiDesa/update";
|
||||
import profileDesaFindById from "./find-by-id";
|
||||
import SejarahDesa from "./sejarah";
|
||||
import VisiMisiDesa from "./visi-misi";
|
||||
import LambangDesa from "./lambang-desa";
|
||||
import MaskotDesa from "./maskot-desa";
|
||||
import Elysia from "elysia";
|
||||
import ProfilPerbekel from "../profilePerbekel";
|
||||
|
||||
const ProfileDesa = new Elysia({
|
||||
prefix: "/profile",
|
||||
tags: ["Desa/Profile"]
|
||||
})
|
||||
.get("/find-by-id", profileDesaFindById)
|
||||
.post("/profilePerbekel/update", profilePerbekelUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
biodata: t.String(),
|
||||
pengalaman: t.String(),
|
||||
pengalamanOrganisasi: t.String(),
|
||||
programUnggulan: t.String(),
|
||||
})
|
||||
})
|
||||
.post("/visimisiDesa/update", visimisiDesaUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
visi: t.String(),
|
||||
misi: t.String(),
|
||||
})
|
||||
})
|
||||
.post("/sejarah/update", sejarahDesaUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
sejarah: t.String(),
|
||||
})
|
||||
})
|
||||
.post("/lambangDesa/update", lambangDesaUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
lambang: t.String(),
|
||||
})
|
||||
})
|
||||
.post("/maskotDesa/update", maskotDesaUpdate, {
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
maskot: t.String(),
|
||||
})
|
||||
prefix: "/profile",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.use(SejarahDesa)
|
||||
.use(VisiMisiDesa)
|
||||
.use(LambangDesa)
|
||||
.use(MaskotDesa)
|
||||
.use(ProfilPerbekel);
|
||||
|
||||
|
||||
|
||||
export default ProfileDesa
|
||||
export default ProfileDesa;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function lambangDesaFindById(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split("/");
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== "string") {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await prisma.lambangDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
data,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Gagal mengambil data lambang desa:", error);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
|
||||
import lambangDesaFindById from "./find-by-id";
|
||||
import lambangDesaUpdate from "./update";
|
||||
|
||||
const LambangDesa = new Elysia({
|
||||
prefix: "/lambang",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await lambangDesaFindById(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await lambangDesaUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default LambangDesa;
|
||||
@@ -0,0 +1,50 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function lambangDesaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await prisma.lambangDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}), { status: 404 });
|
||||
}
|
||||
|
||||
const updated = await prisma.lambangDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Berhasil memperbarui data",
|
||||
data: updated,
|
||||
}), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Gagal memperbarui data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}), { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.ProfileDesaGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
lambang: true;
|
||||
}
|
||||
}>
|
||||
|
||||
export default async function lambangDesaUpdate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.profileDesa.update({
|
||||
where: {
|
||||
id: body.id
|
||||
},
|
||||
data: {
|
||||
lambang: body.lambang,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Profile Desa Berhasil Diupdate",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function maskotDesaFindById(request: Request){
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
const data = await prisma.maskotDesa.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
images: {
|
||||
include: {
|
||||
image: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if(!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}, {status: 404})
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Berhasil mengambil data berdasarkan ID",
|
||||
data,
|
||||
}, {status: 200})
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}, {status: 500})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import maskotDesaUpdate from "./update";
|
||||
import maskotDesaFindById from "./find-by-id";
|
||||
import Elysia, { t } from "elysia";
|
||||
|
||||
const MaskotDesa = new Elysia({
|
||||
prefix: "/maskot",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await maskotDesaFindById(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await maskotDesaUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
images: t.Array(
|
||||
t.Object({
|
||||
imageId: t.String(),
|
||||
label: t.String(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}
|
||||
);
|
||||
export default MaskotDesa;
|
||||
@@ -0,0 +1,78 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function maskotDesaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
images: { label: string; imageId: string }[];
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await prisma.maskotDesa.findUnique({
|
||||
where: { id },
|
||||
include: { images: { include: { image: true } } }
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}), { status: 404 });
|
||||
}
|
||||
|
||||
// Hapus semua gambar lama (dan file-nya jika perlu)
|
||||
for (const old of existing.images) {
|
||||
try {
|
||||
await prisma.fileStorage.delete({ where: { id: old.imageId } });
|
||||
// opsional: hapus file dari disk juga kalau kamu simpan file fisik
|
||||
// await fs.unlink(path.join(old.image.path, old.image.name));
|
||||
} catch (error) {
|
||||
console.warn("Gagal hapus gambar lama:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update profile & re-create images
|
||||
const updated = await prisma.maskotDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
images: {
|
||||
deleteMany: {},
|
||||
create: body.images.map((img) => ({
|
||||
label: img.label,
|
||||
imageId: img.imageId
|
||||
}))
|
||||
}
|
||||
},
|
||||
include: {
|
||||
images: {
|
||||
include: {
|
||||
image: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Data berhasil diperbarui",
|
||||
data: updated,
|
||||
}), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Gagal update MaskotDesa:", error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat update",
|
||||
}), { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.ProfileDesaGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
maskot: true;
|
||||
}
|
||||
}>
|
||||
|
||||
export default async function maskotDesaUpdate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.profileDesa.update({
|
||||
where: {
|
||||
id: body.id
|
||||
},
|
||||
data: {
|
||||
maskot: body.maskot,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Profile Desa Berhasil Diupdate",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function sejarahDesaFindById(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
const data = await prisma.sejarahDesa.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}, {status: 404})
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
data,
|
||||
}, {status: 200})
|
||||
} catch (error) {
|
||||
console.error("Gagal mengambil data sejarah desa:", error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data",
|
||||
}, {status: 500})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import sejarahDesaFindById from "./find-by-id";
|
||||
import sejarahDesaUpdate from "./update";
|
||||
|
||||
const SejarahDesa = new Elysia({
|
||||
prefix: "/sejarah",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await sejarahDesaFindById(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await sejarahDesaUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default SejarahDesa;
|
||||
@@ -1,29 +1,50 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.ProfileDesaGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
sejarah: true;
|
||||
}
|
||||
}>
|
||||
|
||||
export default async function sejarahDesaUpdate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
};
|
||||
|
||||
await prisma.profileDesa.update({
|
||||
where: {
|
||||
id: body.id
|
||||
},
|
||||
data: {
|
||||
sejarah: body.sejarah,
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), { status: 400 });
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Profile Desa Berhasil Diupdate",
|
||||
const existing = await prisma.sejarahDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}), { status: 404 });
|
||||
}
|
||||
|
||||
const updated = await prisma.sejarahDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Berhasil memperbarui data",
|
||||
data: updated,
|
||||
}), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Gagal memperbarui data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}), { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function visiMisiDesaFindById(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, {status: 400})
|
||||
}
|
||||
|
||||
const data = await prisma.visiMisiDesa.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}, {status: 404})
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Data ditemukan",
|
||||
data: data,
|
||||
}, {status: 200})
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal menemukan data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}, {status: 500})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import visiMisiDesaUpdate from "./update";
|
||||
import visiMisiDesaFindById from "./find-by-id";
|
||||
|
||||
const VisiMisiDesa = new Elysia({
|
||||
prefix: "/visi-misi",
|
||||
tags: ["Desa/Profile"],
|
||||
})
|
||||
.get("/:id", async (context) => {
|
||||
const response = await visiMisiDesaFindById(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await visiMisiDesaUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
visi: t.String(),
|
||||
misi: t.String(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default VisiMisiDesa;
|
||||
@@ -0,0 +1,50 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function visiMisiDesaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as {
|
||||
visi: string;
|
||||
misi: string;
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
}), { status: 400 });
|
||||
}
|
||||
|
||||
const existing = await prisma.visiMisiDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
}), { status: 404 });
|
||||
}
|
||||
|
||||
const updated = await prisma.visiMisiDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
visi: body.visi,
|
||||
misi: body.misi,
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: "Berhasil memperbarui data",
|
||||
data: updated,
|
||||
}), { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: "Gagal memperbarui data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}), { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.ProfileDesaGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
visi: true;
|
||||
misi: true;
|
||||
}
|
||||
}>
|
||||
export default async function visimisiDesaUpdate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.profileDesa.update({
|
||||
where: {
|
||||
id: body.id
|
||||
},
|
||||
data: {
|
||||
visi: body.visi,
|
||||
misi: body.misi,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Profile Desa Berhasil Diupdate",
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import '@mantine/charts/styles.css';
|
||||
import '@mantine/dates/styles.css';
|
||||
import '@mantine/tiptap/styles.css';
|
||||
|
||||
|
||||
import LoadDataFirstClient from "@/app/darmasaba/_com/LoadDataFirstClient";
|
||||
import {
|
||||
ColorSchemeScript,
|
||||
|
||||