Compare commits
10 Commits
nico/16-ju
...
nico/20-ju
| Author | SHA1 | Date | |
|---|---|---|---|
| fc08b2e790 | |||
| 4a5524ce88 | |||
| 899883ca2a | |||
| 10ecc13ad7 | |||
| 58f538425c | |||
| d2f53ff69b | |||
| 40f0294595 | |||
| 6ed0246cea | |||
| af726043bd | |||
| f4888b53ab |
@@ -23,7 +23,7 @@
|
|||||||
"@mantine/charts": "^7.17.1",
|
"@mantine/charts": "^7.17.1",
|
||||||
"@mantine/core": "^7.17.4",
|
"@mantine/core": "^7.17.4",
|
||||||
"@mantine/dates": "^8.1.0",
|
"@mantine/dates": "^8.1.0",
|
||||||
"@mantine/dropzone": "^7.17.0",
|
"@mantine/dropzone": "^8.1.1",
|
||||||
"@mantine/form": "^8.1.0",
|
"@mantine/form": "^8.1.0",
|
||||||
"@mantine/hooks": "^7.17.4",
|
"@mantine/hooks": "^7.17.4",
|
||||||
"@mantine/tiptap": "^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>",
|
"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>",
|
"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>",
|
"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.",
|
"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>",
|
"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>",
|
"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,32 @@ model AppMenuChild {
|
|||||||
// ========================================= FILE STORAGE ========================================= //
|
// ========================================= FILE STORAGE ========================================= //
|
||||||
|
|
||||||
model FileStorage {
|
model FileStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
realName String
|
realName String
|
||||||
path String
|
path String
|
||||||
mimeType String
|
mimeType String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
link String
|
link String
|
||||||
Berita Berita[]
|
Berita Berita[]
|
||||||
PotensiDesa PotensiDesa[]
|
PotensiDesa PotensiDesa[]
|
||||||
Posyandu Posyandu[]
|
Posyandu Posyandu[]
|
||||||
ProfilePPID ProfilePPID[]
|
StrukturPPID StrukturPPID[]
|
||||||
StrukturPPID StrukturPPID[]
|
GalleryFoto GalleryFoto[]
|
||||||
|
|
||||||
GalleryFoto GalleryFoto[]
|
|
||||||
|
|
||||||
PelayananSuratKeterangan PelayananSuratKeterangan[]
|
PelayananSuratKeterangan PelayananSuratKeterangan[]
|
||||||
|
Penghargaan Penghargaan[]
|
||||||
|
ProfileDesaImage ProfileDesaImage[]
|
||||||
|
ProfilePPID ProfilePPID[]
|
||||||
|
ProfilPerbekel ProfilPerbekel[]
|
||||||
|
Puskesmas Puskesmas[]
|
||||||
|
ProgramKesehatan ProgramKesehatan[]
|
||||||
|
PenangananDarurat PenangananDarurat[]
|
||||||
|
KontakDarurat KontakDarurat[]
|
||||||
|
|
||||||
Penghargaan Penghargaan[]
|
InfoWabahPenyakit InfoWabahPenyakit[]
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU PPID ========================================= //
|
//========================================= MENU PPID ========================================= //
|
||||||
@@ -117,6 +122,7 @@ model ProfilePPID {
|
|||||||
riwayat String @db.Text
|
riwayat String @db.Text
|
||||||
pengalaman String @db.Text
|
pengalaman String @db.Text
|
||||||
unggulan String @db.Text
|
unggulan String @db.Text
|
||||||
|
imageUrl String?
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -248,32 +254,68 @@ model GrafikBerdasarkanUmur {
|
|||||||
|
|
||||||
// ========================================= MENU DESA ========================================= //
|
// ========================================= MENU DESA ========================================= //
|
||||||
// ========================================= PROFILE DESA ========================================= //
|
// ========================================= PROFILE DESA ========================================= //
|
||||||
model ProfileDesa {
|
model SejarahDesa {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
sejarah String @db.Text
|
judul String @db.Text
|
||||||
visi String @db.Text
|
deskripsi String @db.Text
|
||||||
misi String @db.Text
|
createdAt DateTime @default(now())
|
||||||
lambang String @db.Text
|
updatedAt DateTime @updatedAt
|
||||||
maskot String @db.Text
|
deletedAt DateTime @default(now())
|
||||||
ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id])
|
isActive Boolean @default(true)
|
||||||
profilPerbekelId String?
|
}
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
model VisiMisiDesa {
|
||||||
deletedAt DateTime @default(now())
|
id String @id @default(cuid())
|
||||||
isActive Boolean @default(true)
|
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 {
|
model ProfilPerbekel {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
biodata String @db.Text
|
biodata String @db.Text
|
||||||
pengalaman String @db.Text
|
pengalaman String @db.Text
|
||||||
pengalamanOrganisasi String @db.Text
|
pengalamanOrganisasi String @db.Text
|
||||||
programUnggulan String @db.Text
|
programUnggulan String @db.Text
|
||||||
ProfileDesa ProfileDesa[]
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
imageId String?
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
deletedAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
isActive Boolean @default(true)
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= BERITA ========================================= //
|
// ========================================= BERITA ========================================= //
|
||||||
@@ -412,16 +454,16 @@ model PelayananPendudukNonPermanen {
|
|||||||
|
|
||||||
// ========================================= PENGHARGAAN ========================================= //
|
// ========================================= PENGHARGAAN ========================================= //
|
||||||
model Penghargaan {
|
model Penghargaan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
juara String
|
juara String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
imageId String
|
imageId String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= MENU KESEHATAN ========================================= //
|
// ========================================= MENU KESEHATAN ========================================= //
|
||||||
@@ -687,3 +729,98 @@ model Posyandu {
|
|||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= PUSKESMAS ========================================= //
|
||||||
|
model Puskesmas {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
alamat String
|
||||||
|
jam JamOperasional @relation(fields: [jamId], references: [id])
|
||||||
|
jamId String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
kontak KontakPuskesmas @relation(fields: [kontakId], references: [id])
|
||||||
|
kontakId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model JamOperasional {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
workDays String
|
||||||
|
weekDays String
|
||||||
|
holiday String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Puskesmas Puskesmas[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model KontakPuskesmas {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
kontakPuskesmas String
|
||||||
|
email String
|
||||||
|
facebook String
|
||||||
|
kontakUGD String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Puskesmas Puskesmas[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================= PROGRAM KESSEHATAN ========================================= //
|
||||||
|
model ProgramKesehatan {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsiSingkat String
|
||||||
|
deskripsi String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================= PENANGANAN DARURAT ========================================= //
|
||||||
|
model PenangananDarurat {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================= KONTAK DARURAT ========================================= //
|
||||||
|
model KontakDarurat {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsi String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
// ========================================= INFO WABAH PENYAKIT ========================================= //
|
||||||
|
model InfoWabahPenyakit {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
deskripsiSingkat String
|
||||||
|
deskripsiLengkap String
|
||||||
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
|
imageId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|||||||
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 strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
|
||||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.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 () => {
|
(async () => {
|
||||||
for (const l of layanan) {
|
for (const l of layanan) {
|
||||||
@@ -30,6 +35,104 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
|
|||||||
|
|
||||||
console.log("layanan success ...");
|
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) {
|
for (const l of pelayananPerizinanBerusaha) {
|
||||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -67,8 +170,7 @@ import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNo
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.log("pelayanan penduduk non permanen success ...");
|
||||||
console.log("pelayanan perizinan berusaha success ...");
|
|
||||||
|
|
||||||
for (const s of strukturPPID) {
|
for (const s of strukturPPID) {
|
||||||
await prisma.strukturPPID.upsert({
|
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({
|
const templateForm = z.object({
|
||||||
name: z.string().min(1).max(50),
|
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),
|
kategori: z.string().min(1).max(50),
|
||||||
imageId: z.string().min(1).max(50),
|
imageId: z.string().min(1).max(50),
|
||||||
content: z.string().min(1).max(5000),
|
content: z.string().min(1).max(5000),
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
|
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
|
||||||
|
deskripsiLengkap: z.string().min(3, "Deskripsi lengkap minimal 3 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsiSingkat: "",
|
||||||
|
deskripsiLengkap: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const infoWabahPenyakit = proxy({
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(infoWabahPenyakit.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
infoWabahPenyakit.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
|
||||||
|
"create"
|
||||||
|
].post(infoWabahPenyakit.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
infoWabahPenyakit.findMany.load();
|
||||||
|
return toast.success("Info wabah penyakit berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan info wabah penyakit");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
infoWabahPenyakit.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
infoWabahPenyakit.create.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.InfoWabahPenyakitGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/infowabahpenyakit/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
infoWabahPenyakit.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch info wabah penyakit:", res.statusText);
|
||||||
|
infoWabahPenyakit.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching info wabah penyakit:", error);
|
||||||
|
infoWabahPenyakit.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
infoWabahPenyakit.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/infowabahpenyakit/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Info wabah penyakit berhasil dihapus");
|
||||||
|
await infoWabahPenyakit.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus info wabah penyakit");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus info wabah penyakit");
|
||||||
|
} finally {
|
||||||
|
infoWabahPenyakit.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/infowabahpenyakit/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsiSingkat: data.deskripsiSingkat,
|
||||||
|
deskripsiLengkap: data.deskripsiLengkap,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching info wabah penyakit:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(infoWabahPenyakit.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
infoWabahPenyakit.edit.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/infowabahpenyakit/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsiSingkat: this.form.deskripsiSingkat,
|
||||||
|
deskripsiLengkap: this.form.deskripsiLengkap,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Info wabah penyakit berhasil diupdate");
|
||||||
|
await infoWabahPenyakit.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update info wabah penyakit");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate info wabah penyakit");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
infoWabahPenyakit.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
infoWabahPenyakit.edit.id = "";
|
||||||
|
infoWabahPenyakit.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default infoWabahPenyakit;
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
const kontakDarurat = proxy({
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.KontakDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.kontakdarurat[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
kontakDarurat.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create:{
|
||||||
|
form: {...defaultForm},
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(kontakDarurat.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kontakDarurat.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.kontakdarurat[
|
||||||
|
"create"
|
||||||
|
].post(kontakDarurat.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
kontakDarurat.findMany.load();
|
||||||
|
return toast.success("Kontak Darurat berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan kontak darurat");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
kontakDarurat.create.form = {...defaultForm};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KontakDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kontakDarurat.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
kontakDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
kontakDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
try {
|
||||||
|
kontakDarurat.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Kontak darurat berhasil dihapus");
|
||||||
|
await kontakDarurat.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus kontak darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching kontak darurat:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(kontakDarurat.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kontakDarurat.edit.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Kontak darurat berhasil diupdate");
|
||||||
|
await kontakDarurat.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update kontak darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
kontakDarurat.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
kontakDarurat.edit.id = "";
|
||||||
|
kontakDarurat.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kontakDarurat
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
const penangananDarurat = proxy({
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.PenangananDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.penanganandarurat[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
penangananDarurat.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create:{
|
||||||
|
form: {...defaultForm},
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(penangananDarurat.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
penangananDarurat.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.penanganandarurat[
|
||||||
|
"create"
|
||||||
|
].post(penangananDarurat.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
penangananDarurat.findMany.load();
|
||||||
|
return toast.success("Penanganan Darurat berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan penanganan darurat");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
penangananDarurat.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
penangananDarurat.create.form = {...defaultForm};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PenangananDaruratGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/penanganandarurat/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
penangananDarurat.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
penangananDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
penangananDarurat.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
try {
|
||||||
|
penangananDarurat.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/penanganandarurat/del/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Penanganan darurat berhasil dihapus");
|
||||||
|
await penangananDarurat.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus penanganan darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus penanganan darurat");
|
||||||
|
} finally {
|
||||||
|
penangananDarurat.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/penanganandarurat/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching penanganan darurat:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(penangananDarurat.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
penangananDarurat.edit.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/penanganandarurat/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Penanganan darurat berhasil diupdate");
|
||||||
|
await penangananDarurat.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update penanganan darurat");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate penanganan darurat");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
penangananDarurat.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
penangananDarurat.edit.id = "";
|
||||||
|
penangananDarurat.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default penangananDarurat
|
||||||
217
src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
nomor: z.string().min(1, { message: "Nomor is required" }),
|
||||||
|
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
nomor: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const posyandustate = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(posyandustate.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
posyandustate.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.posyandu["create"].post(posyandustate.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
posyandustate.findMany.load();
|
||||||
|
return toast.success("Posyandu berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan posyandu");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
posyandustate.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm(){
|
||||||
|
posyandustate.create.form = { ...defaultForm };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PosyanduGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
}
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
posyandustate.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.PosyanduGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
}
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/posyandu/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
posyandustate.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch posyandu:", res.statusText);
|
||||||
|
posyandustate.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching posyandu:", error);
|
||||||
|
posyandustate.findUnique.data = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
try {
|
||||||
|
posyandustate.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/posyandu/del/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Posyandu berhasil dihapus");
|
||||||
|
await posyandustate.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus posyandu");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus posyandu");
|
||||||
|
} finally {
|
||||||
|
posyandustate.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: {...defaultForm},
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if(!id){
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/posyandu/${id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!response.ok){
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if(result?.success){
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
nomor: data.nomor,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId || "",
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching posyandu:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(posyandustate.edit.form);
|
||||||
|
if(!cek.success){
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
posyandustate.edit.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/posyandu/${this.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
nomor: this.form.nomor,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!response.ok){
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if(result.success){
|
||||||
|
toast.success(result.message || "Posyandu berhasil diperbarui");
|
||||||
|
await posyandustate.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching posyandu:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
posyandustate.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
posyandustate.edit.id = "";
|
||||||
|
posyandustate.edit.form = {...defaultForm};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default posyandustate;
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(3, "Judul minimal 3 karakter"),
|
||||||
|
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
|
||||||
|
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
deskripsiSingkat: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const programKesehatan = proxy({
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.ProgramKesehatanGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.programkesehatan[
|
||||||
|
"find-many"
|
||||||
|
].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
programKesehatan.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(programKesehatan.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
programKesehatan.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.kesehatan.programkesehatan[
|
||||||
|
"create"
|
||||||
|
].post(programKesehatan.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
programKesehatan.findMany.load();
|
||||||
|
return toast.success("Program Kesehatan berhasil disimpan!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast.error("Gagal menyimpan program kesehatan");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
programKesehatan.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
programKesehatan.create.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.ProgramKesehatanGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/kesehatan/programkesehatan/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
programKesehatan.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch program kesehatan:", res.statusText);
|
||||||
|
programKesehatan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching program kesehatan:", error);
|
||||||
|
programKesehatan.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
programKesehatan.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Program kesehatan berhasil dihapus");
|
||||||
|
await programKesehatan.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus program kesehatan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus program kesehatan");
|
||||||
|
} finally {
|
||||||
|
programKesehatan.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/kesehatan/programkesehatan/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
name: data.name,
|
||||||
|
deskripsiSingkat: data.deskripsiSingkat,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching program kesehatan:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateForm.safeParse(programKesehatan.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
programKesehatan.edit.loading = true;
|
||||||
|
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
deskripsiSingkat: this.form.deskripsiSingkat,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message || "Program kesehatan berhasil diupdate");
|
||||||
|
await programKesehatan.findMany.load();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update program kesehatan");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal update:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
programKesehatan.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
programKesehatan.edit.id = "";
|
||||||
|
programKesehatan.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default programKesehatan;
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Validasi form
|
||||||
|
const templateForm = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
alamat: z.string().min(1),
|
||||||
|
imageId: z.string().min(1),
|
||||||
|
jam: z.object({
|
||||||
|
workDays: z.string().min(1),
|
||||||
|
weekDays: z.string().min(1),
|
||||||
|
holiday: z.string().min(1),
|
||||||
|
}),
|
||||||
|
kontak: z.object({
|
||||||
|
kontakPuskesmas: z.string().min(1),
|
||||||
|
email: z.string().min(1),
|
||||||
|
facebook: z.string().min(1),
|
||||||
|
kontakUGD: z.string().min(1),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default form
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
alamat: "",
|
||||||
|
imageId: "",
|
||||||
|
jam: {
|
||||||
|
workDays: "",
|
||||||
|
weekDays: "",
|
||||||
|
holiday: "",
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: "",
|
||||||
|
email: "",
|
||||||
|
facebook: "",
|
||||||
|
kontakUGD: "",
|
||||||
|
},
|
||||||
|
image: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const puskesmasState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async submit() {
|
||||||
|
const cek = templateForm.safeParse(puskesmasState.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
puskesmasState.create.loading = true;
|
||||||
|
|
||||||
|
console.log('Form data:', puskesmasState.create.form);
|
||||||
|
interface ErrorResponse {
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
errors?: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: puskesmasState.create.form.name,
|
||||||
|
alamat: puskesmasState.create.form.alamat,
|
||||||
|
imageId: puskesmasState.create.form.imageId,
|
||||||
|
jam: {
|
||||||
|
workDays: puskesmasState.create.form.jam.workDays,
|
||||||
|
weekDays: puskesmasState.create.form.jam.weekDays,
|
||||||
|
holiday: puskesmasState.create.form.jam.holiday,
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: puskesmasState.create.form.kontak.kontakPuskesmas,
|
||||||
|
email: puskesmasState.create.form.kontak.email,
|
||||||
|
facebook: puskesmasState.create.form.kontak.facebook,
|
||||||
|
kontakUGD: puskesmasState.create.form.kontak.kontakUGD,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log('Sending payload:', JSON.stringify(payload, null, 2));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.kesehatan.puskesmas.create.post(payload);
|
||||||
|
console.log('API Response:', res);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
await puskesmasState.findMany.load();
|
||||||
|
toast.success("Berhasil menambahkan puskesmas");
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
console.error('API Error Response:', {
|
||||||
|
status: res.status,
|
||||||
|
data: res.data
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorData = res.data as ErrorResponse;
|
||||||
|
let errorMessage = 'Gagal menambahkan puskesmas';
|
||||||
|
|
||||||
|
if (errorData?.message) {
|
||||||
|
errorMessage = errorData.message;
|
||||||
|
} else if (errorData?.error) {
|
||||||
|
errorMessage = errorData.error;
|
||||||
|
} else if (errorData?.errors) {
|
||||||
|
errorMessage = Object.entries(errorData.errors)
|
||||||
|
.map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
|
||||||
|
.join('; ');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Extracted error message:', errorMessage);
|
||||||
|
toast.error(errorMessage);
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in API call:', {
|
||||||
|
error,
|
||||||
|
errorString: String(error),
|
||||||
|
jsonError: error instanceof Error ? {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
} : 'Not an Error instance'
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in puskesmas submit:", {
|
||||||
|
error,
|
||||||
|
errorString: String(error),
|
||||||
|
errorType: typeof error,
|
||||||
|
isErrorInstance: error instanceof Error,
|
||||||
|
errorDetails: error instanceof Error ? {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
cause: error.cause
|
||||||
|
} : null
|
||||||
|
});
|
||||||
|
|
||||||
|
let errorMessage = "Terjadi kesalahan saat menambahkan puskesmas";
|
||||||
|
if (error instanceof Error) {
|
||||||
|
errorMessage = error.message || errorMessage;
|
||||||
|
} else if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
errorMessage = String((error as { message: unknown }).message);
|
||||||
|
} else if (typeof error === 'string') {
|
||||||
|
errorMessage = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Displaying error to user:', errorMessage);
|
||||||
|
toast.error(errorMessage);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
puskesmasState.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
puskesmasState.create.form = { ...defaultForm };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
findMany: {
|
||||||
|
data: null as Prisma.PuskesmasGetPayload<{
|
||||||
|
include: { image: true; jam: true; kontak: true };
|
||||||
|
}>[] | null,
|
||||||
|
async load() {
|
||||||
|
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
puskesmasState.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.PuskesmasGetPayload<{
|
||||||
|
include: { image: true; jam: true; kontak: true };
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
puskesmasState.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
toast.error("Gagal load data puskesmas");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
|
||||||
|
if (!res.ok) {
|
||||||
|
toast.error("Gagal memuat data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
const data = result.data;
|
||||||
|
|
||||||
|
puskesmasState.edit.id = data.id;
|
||||||
|
puskesmasState.edit.form = {
|
||||||
|
name: data.name,
|
||||||
|
alamat: data.alamat,
|
||||||
|
imageId: data.imageId,
|
||||||
|
jam: {
|
||||||
|
workDays: data.jam.workDays,
|
||||||
|
weekDays: data.jam.weekDays,
|
||||||
|
holiday: data.jam.holiday,
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: data.kontak.kontakPuskesmas,
|
||||||
|
email: data.kontak.email,
|
||||||
|
facebook: data.kontak.facebook,
|
||||||
|
kontakUGD: data.kontak.kontakUGD,
|
||||||
|
},
|
||||||
|
image: data.image,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
const cek = templateForm.safeParse(puskesmasState.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
puskesmasState.edit.loading = true;
|
||||||
|
const payload = {
|
||||||
|
name: puskesmasState.edit.form.name,
|
||||||
|
alamat: puskesmasState.edit.form.alamat,
|
||||||
|
imageId: puskesmasState.edit.form.imageId,
|
||||||
|
jam: { ...puskesmasState.edit.form.jam },
|
||||||
|
kontak: { ...puskesmasState.edit.form.kontak }
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch(`/api/kesehatan/puskesmas/${puskesmasState.edit.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const error = await res.json();
|
||||||
|
throw new Error(error.message || "Update gagal");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Berhasil update puskesmas");
|
||||||
|
await puskesmasState.findMany.load();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err instanceof Error ? err.message : "Terjadi kesalahan saat update");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
puskesmasState.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
puskesmasState.edit.id = "";
|
||||||
|
puskesmasState.edit.form = { ...defaultForm };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
try {
|
||||||
|
puskesmasState.delete.loading = true;
|
||||||
|
const res = await fetch(`/api/kesehatan/puskesmas/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
if (res.ok && result.success) {
|
||||||
|
toast.success("Puskesmas berhasil dihapus");
|
||||||
|
await puskesmasState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus");
|
||||||
|
} finally {
|
||||||
|
puskesmasState.delete.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default puskesmasState;
|
||||||
@@ -5,9 +5,9 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateGrafikJenisKelamin = z.object({
|
const templateGrafikJenisKelamin = z.object({
|
||||||
laki: z.string().min(2, "Data laki-laki harus diisi"),
|
laki: z.string().min(1, "Data laki-laki harus diisi"),
|
||||||
perempuan: z.string().min(2, "Data perempuan harus diisi"),
|
perempuan: z.string().min(1, "Data perempuan harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateGrafikUmur = z.object({
|
const templateGrafikUmur = z.object({
|
||||||
remaja: z.string().min(2, "Data remaja harus diisi"),
|
remaja: z.string().min(1, "Data remaja harus diisi"),
|
||||||
dewasa: z.string().min(2, "Data dewasa harus diisi"),
|
dewasa: z.string().min(1, "Data dewasa harus diisi"),
|
||||||
orangtua: z.string().min(2, "Data orangtua harus diisi"),
|
orangtua: z.string().min(1, "Data orangtua harus diisi"),
|
||||||
lansia: z.string().min(2, "Data lansia harus diisi"),
|
lansia: z.string().min(1, "Data lansia harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const templateGrafikHasilKepuasanMasyarakat = z.object({
|
const templateGrafikHasilKepuasanMasyarakat = z.object({
|
||||||
label: z.string().min(2, "Label harus diisi"),
|
label: z.string().min(2, "Label harus diisi"),
|
||||||
kepuasan: z.string().min(2, "Kepuasan harus diisi"),
|
kepuasan: z.string().min(1, "Kepuasan harus diisi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||||
|
|||||||
@@ -125,16 +125,6 @@ function EditBerita() {
|
|||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectCategory
|
|
||||||
value={formData.kategoriBeritaId}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
kategoriBeritaId: val?.id || ''
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
|
||||||
@@ -174,6 +164,16 @@ function EditBerita() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<SelectCategory
|
||||||
|
value={formData.kategoriBeritaId}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
kategoriBeritaId: val?.id || ''
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button onClick={handleSubmit}>Edit Berita</Button>
|
<Button onClick={handleSubmit}>Edit Berita</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ export default function CreateBerita() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
@@ -79,8 +79,9 @@ export default function CreateBerita() {
|
|||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
<SelectCategory
|
<SelectCategory
|
||||||
|
value={beritaState.berita.create.form.kategoriBeritaId}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
beritaState.berita.create.form.kategoriBeritaId = val.id;
|
beritaState.berita.create.form.kategoriBeritaId = val?.id || "";
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -114,10 +115,10 @@ export default function CreateBerita() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={beritaState.berita.create.form.content}
|
value={beritaState.berita.create.form.content}
|
||||||
onChange={(htmlContent) => {
|
onChange={(htmlContent) => {
|
||||||
beritaState.berita.create.form.content = htmlContent;
|
beritaState.berita.create.form.content = htmlContent;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
|
||||||
@@ -126,26 +127,37 @@ export default function CreateBerita() {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
function SelectCategory({
|
interface SelectCategoryProps {
|
||||||
onChange,
|
|
||||||
}: {
|
|
||||||
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
name: true;
|
name: true;
|
||||||
id: true;
|
id: true;
|
||||||
};
|
};
|
||||||
}>) => void;
|
}> | null) => void;
|
||||||
}) {
|
value?: string | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectCategory({
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
}: SelectCategoryProps) {
|
||||||
const categoryState = useProxy(stateDashboardBerita.category);
|
const categoryState = useProxy(stateDashboardBerita.category);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
categoryState.findMany.load();
|
categoryState.findMany.load().then(() => {
|
||||||
|
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!categoryState.findMany.data) {
|
if (!categoryState.findMany.data) {
|
||||||
return <Skeleton height={38} />;
|
return <Skeleton height={38} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const selectedValue = value || defaultValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||||
@@ -154,13 +166,19 @@ export default function CreateBerita() {
|
|||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
}))}
|
}))}
|
||||||
onChange={(val) => {
|
value={selectedValue || null}
|
||||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
onChange={(val: string | null) => {
|
||||||
if (selected) {
|
if (val) {
|
||||||
onChange(selected);
|
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||||
|
if (selected) {
|
||||||
|
onChange(selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onChange(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
searchable
|
searchable
|
||||||
|
clearable
|
||||||
nothingFoundMessage="Tidak ditemukan"
|
nothingFoundMessage="Tidak ditemukan"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
export function convertYoutubeUrlToEmbed(url: string): string | null {
|
export function convertYoutubeUrlToEmbed(url: string) {
|
||||||
const watchRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/;
|
const videoIdMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
|
||||||
const shortRegex = /(?:https?:\/\/)?youtu\.be\/([^?]+)/;
|
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 matchWatch = url.match(watchRegex);
|
||||||
const matchShort = url.match(shortRegex);
|
// const matchShort = url.match(shortRegex);
|
||||||
|
|
||||||
if (matchWatch) {
|
// if (matchWatch) {
|
||||||
return `https://www.youtube.com/embed/${matchWatch[1]}`;
|
// return `https://www.youtube.com/embed/${matchWatch[1]}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (matchShort) {
|
// if (matchShort) {
|
||||||
return `https://www.youtube.com/embed/${matchShort[1]}`;
|
// 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 { useProxy } from 'valtio/utils';
|
||||||
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
import { convertYoutubeUrlToEmbed } from '../../../lib/youtube-utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function EditVideo() {
|
function EditVideo() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [embedLink, setEmbedLink] = useState("");
|
|
||||||
const videoState = useProxy(stateGallery.video)
|
const videoState = useProxy(stateGallery.video)
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: videoState.findUnique.data?.name || '',
|
name: '',
|
||||||
deskripsi: videoState.findUnique.data?.deskripsi || '',
|
deskripsi: '',
|
||||||
linkVideo: videoState.findUnique.data?.linkVideo || '',
|
linkVideo: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadVideo = async () => {
|
const loadVideo = async () => {
|
||||||
@@ -36,8 +34,6 @@ function EditVideo() {
|
|||||||
deskripsi: data.deskripsi || '',
|
deskripsi: data.deskripsi || '',
|
||||||
linkVideo: data.linkVideo || '',
|
linkVideo: data.linkVideo || '',
|
||||||
});
|
});
|
||||||
const embed = convertYoutubeUrlToEmbed(data.linkVideo);
|
|
||||||
setEmbedLink(embed || "");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading video:', error);
|
console.error('Error loading video:', error);
|
||||||
@@ -47,7 +43,15 @@ function EditVideo() {
|
|||||||
loadVideo();
|
loadVideo();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const embedLink = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
||||||
|
if (!converted) {
|
||||||
|
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
videoState.update.form = {
|
videoState.update.form = {
|
||||||
...videoState.update.form,
|
...videoState.update.form,
|
||||||
@@ -55,11 +59,6 @@ function EditVideo() {
|
|||||||
deskripsi: formData.deskripsi,
|
deskripsi: formData.deskripsi,
|
||||||
linkVideo: formData.linkVideo,
|
linkVideo: formData.linkVideo,
|
||||||
};
|
};
|
||||||
const converted = convertYoutubeUrlToEmbed(formData.linkVideo);
|
|
||||||
if (!converted) {
|
|
||||||
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await videoState.update.update();
|
await videoState.update.update();
|
||||||
toast.success('Video berhasil diperbarui!');
|
toast.success('Video berhasil diperbarui!');
|
||||||
router.push('/admin/desa/gallery/video');
|
router.push('/admin/desa/gallery/video');
|
||||||
@@ -80,29 +79,23 @@ function EditVideo() {
|
|||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Edit Video</Title>
|
<Title order={4}>Edit Video</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Judul Video</Text>}
|
||||||
placeholder='Masukkan judul video'
|
placeholder='Masukkan judul video'
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
setFormData({
|
setFormData({ ...formData, name: val.target.value });
|
||||||
...formData,
|
|
||||||
name: val.target.value,
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Link Video YouTube"
|
label="Link Video YouTube"
|
||||||
placeholder="https://www.youtube.com/watch?v=abc123"
|
placeholder="https://www.youtube.com/watch?v=abc123"
|
||||||
value={formData.linkVideo}
|
value={formData.linkVideo}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormData({
|
setFormData({ ...formData, linkVideo: e.currentTarget.value });
|
||||||
...formData,
|
|
||||||
linkVideo: e.currentTarget.value,
|
|
||||||
})
|
|
||||||
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
|
||||||
setEmbedLink(embed || "");
|
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -118,18 +111,17 @@ function EditVideo() {
|
|||||||
></iframe>
|
></iframe>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Video</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
setFormData({
|
setFormData({ ...formData, deskripsi: val });
|
||||||
...formData,
|
|
||||||
deskripsi: val,
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function CreateVideo() {
|
|||||||
const videoState = useProxy(stateGallery.video)
|
const videoState = useProxy(stateGallery.video)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [link, setLink] = useState("");
|
const [link, setLink] = useState("");
|
||||||
const [embedLink, setEmbedLink] = useState("");
|
const embedLink = convertYoutubeUrlToEmbed(link);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
videoState.create.form = {
|
videoState.create.form = {
|
||||||
@@ -26,15 +26,17 @@ function CreateVideo() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const converted = convertYoutubeUrlToEmbed(videoState.create.form.linkVideo);
|
if (!embedLink) {
|
||||||
if (!converted) {
|
|
||||||
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
toast.error("Link YouTube tidak valid. Pastikan formatnya benar.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoState.create.form.linkVideo = embedLink; // pastikan diset di sini juga (jaga-jaga)
|
||||||
await videoState.create.create();
|
await videoState.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/desa/gallery/video")
|
router.push("/admin/desa/gallery/video");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -63,8 +65,6 @@ function CreateVideo() {
|
|||||||
value={link}
|
value={link}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setLink(e.currentTarget.value);
|
setLink(e.currentTarget.value);
|
||||||
const embed = convertYoutubeUrlToEmbed(e.currentTarget.value);
|
|
||||||
setEmbedLink(embed || "");
|
|
||||||
}}
|
}}
|
||||||
required
|
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;
|
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function EditInfoWabahPenyakit() {
|
||||||
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: infoWabahPenyakitState.edit.form.name || '',
|
||||||
|
deskripsiSingkat: infoWabahPenyakitState.edit.form.deskripsiSingkat || '',
|
||||||
|
deskripsi: infoWabahPenyakitState.edit.form.deskripsiLengkap || '',
|
||||||
|
imageId: infoWabahPenyakitState.edit.form.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadInfoWabahPenyakit = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await infoWabahPenyakitState.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsiSingkat: data.deskripsiSingkat || '',
|
||||||
|
deskripsi: data.deskripsiLengkap || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading program kesehatan:", error);
|
||||||
|
toast.error("Gagal memuat data program kesehatan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadInfoWabahPenyakit();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
infoWabahPenyakitState.edit.form = {
|
||||||
|
...infoWabahPenyakitState.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsiSingkat: formData.deskripsiSingkat,
|
||||||
|
deskripsiLengkap: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
infoWabahPenyakitState.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await infoWabahPenyakitState.edit.update();
|
||||||
|
toast.success("Info wabah penyakit berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/info-wabah-penyakit");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating info wabah penyakit:", error);
|
||||||
|
toast.error("Gagal memuat data info wabah penyakit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.deskripsiSingkat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditInfoWabahPenyakit;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailInfoWabahPenyakit() {
|
||||||
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
infoWabahPenyakitState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
infoWabahPenyakitState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/info-wabah-penyakit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!infoWabahPenyakitState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={400} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Info Wabah Penyakit</Text>
|
||||||
|
{infoWabahPenyakitState.findUnique.data ? (
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{infoWabahPenyakitState.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi Singkat</Text>
|
||||||
|
<Text fz={"lg"}>{infoWabahPenyakitState.findUnique.data.deskripsiSingkat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: infoWabahPenyakitState.findUnique.data.deskripsiLengkap }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={infoWabahPenyakitState.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color="red" onClick={() => {
|
||||||
|
if (infoWabahPenyakitState.findUnique.data) {
|
||||||
|
setSelectedId(infoWabahPenyakitState.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={infoWabahPenyakitState.delete.loading || !infoWabahPenyakitState.findUnique.data}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${infoWabahPenyakitState.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus info wabah penyakit ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailInfoWabahPenyakit;
|
||||||
@@ -1,43 +1,101 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
|
||||||
function CreateInfoWabahPenyakit() {
|
function CreateInfoWabahPenyakit() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
// Reset state di valtio
|
||||||
|
infoWabahPenyakitState.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsiSingkat: "",
|
||||||
|
deskripsiLengkap: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset state lokal
|
||||||
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload gambar dulu
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simpan ID gambar ke form
|
||||||
|
infoWabahPenyakitState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
// Submit data berita
|
||||||
|
await infoWabahPenyakitState.create.create();
|
||||||
|
|
||||||
|
// Reset form setelah submit
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/info-wabah-penyakit")
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Create Info Wabah Penyakit</Title>
|
<Title order={3}>Create Info Wabah Penyakit</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
value={infoWabahPenyakitState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
infoWabahPenyakitState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
value={infoWabahPenyakitState.create.form.deskripsiSingkat}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
onChange={(val) => {
|
||||||
|
infoWabahPenyakitState.create.form.deskripsiSingkat = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
<CreateEditor
|
||||||
placeholder="masukkan kategori"
|
value={infoWabahPenyakitState.create.form.deskripsiLengkap}
|
||||||
/>
|
onChange={(val) => {
|
||||||
|
infoWabahPenyakitState.create.form.deskripsiLengkap = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* <FileInput
|
<FileInput
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
value={file}
|
value={file}
|
||||||
onChange={async (e) => {
|
onChange={async (e) => {
|
||||||
@@ -48,25 +106,18 @@ function CreateInfoWabahPenyakit() {
|
|||||||
);
|
);
|
||||||
setPreviewImage(base64);
|
setPreviewImage(base64);
|
||||||
}}
|
}}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
{/* {previewImage ? (
|
{previewImage ? (
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
) : (
|
) : (
|
||||||
<Center w={200} h={200} bg="gray">
|
<Center w={200} h={200} bg="gray">
|
||||||
<IconImageInPicture />
|
<IconImageInPicture />
|
||||||
</Center>
|
</Center>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
<Box>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
<Text fz="sm" fw="bold">Konten</Text>
|
Simpan
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button bg={colors['blue-button']}>
|
|
||||||
Simpan Potensi
|
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailInfoWabahPenyakit() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Info Wabah/Penyakit</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Info Wabah/Penyakit</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Info Wabah/Penyakit</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/info-wabah-penyakit/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus penanganan darurat ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailInfoWabahPenyakit;
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, SimpleGrid, Paper, Title, TextInput, Text, Button, Image } from '@mantine/core';
|
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
function EditInfoWabahPenyakit() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Edit Info Wabah/Penyakit</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Info Wabah/Penyakit</Text>}
|
|
||||||
placeholder='Masukkan nama Info Wabah/Penyakit'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Info Wabah/Penyakit</Text>}
|
|
||||||
placeholder='Masukkan deskripsi Info Wabah/Penyakit'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Preview Data Info Wabah/Penyakit</Title>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Nama Info Wabah/Penyakit</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Info Wabah/Penyakit</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Gambar</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditInfoWabahPenyakit;
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import infoWabahPenyakit from '../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function InfoWabahPenyakit() {
|
function InfoWabahPenyakit() {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +23,20 @@ function InfoWabahPenyakit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListInfoWabahPenyakit() {
|
function ListInfoWabahPenyakit() {
|
||||||
|
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
infoWabahPenyakitState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!infoWabahPenyakitState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -34,27 +50,32 @@ function ListInfoWabahPenyakit() {
|
|||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Kategori</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{infoWabahPenyakitState.findMany.data?.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
<Box w={100}>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"}>Test</Text>
|
<Box w={100}>
|
||||||
</Box></TableTd>
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
<TableTd>Test</TableTd>
|
</Box>
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Image w={100} src={"/"} alt="image" />
|
<TableTd>
|
||||||
</TableTd>
|
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/info-wabah-penyakit/detail')}>
|
<TableTd>
|
||||||
<IconDeviceImacCog size={25} />
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
</Button>
|
</TableTd>
|
||||||
</TableTd>
|
<TableTd>
|
||||||
</TableTr>
|
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
|
||||||
|
<IconDeviceImacCog size={25} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function EditKontakDarurat() {
|
||||||
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: kontakDaruratState.edit.form.name || '',
|
||||||
|
deskripsi: kontakDaruratState.edit.form.deskripsi || '',
|
||||||
|
imageId: kontakDaruratState.edit.form.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProgramKesehatan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await kontakDaruratState.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading program kesehatan:", error);
|
||||||
|
toast.error("Gagal memuat data program kesehatan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadProgramKesehatan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
kontakDaruratState.edit.form = {
|
||||||
|
...kontakDaruratState.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
kontakDaruratState.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await kontakDaruratState.edit.update();
|
||||||
|
toast.success("Kontak darurat berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/kontak-darurat");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating kontak darurat:", error);
|
||||||
|
toast.error("Gagal memuat data kontak darurat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Kontak Darurat</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditKontakDarurat;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
|
||||||
|
function DetailKontakDarurat() {
|
||||||
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
kontakDaruratState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
kontakDaruratState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/kontak-darurat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kontakDaruratState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={400} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Kontak Darurat</Text>
|
||||||
|
{kontakDaruratState.findUnique.data ? (
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{kontakDaruratState.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kontakDaruratState.findUnique.data.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={kontakDaruratState.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color="red" onClick={() => {
|
||||||
|
if (kontakDaruratState.findUnique.data) {
|
||||||
|
setSelectedId(kontakDaruratState.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={kontakDaruratState.delete.loading || !kontakDaruratState.findUnique.data}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${kontakDaruratState.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus kontak darurat ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailKontakDarurat;
|
||||||
@@ -1,77 +1,113 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
|
||||||
|
|
||||||
function CreateKontakDarurat() {
|
function CreateKontakDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
<Box>
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
<Box mb={10}>
|
const [file, setFile] = useState<File | null>(null);
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
const resetForm = () => {
|
||||||
</Button>
|
kontakDaruratState.create.form = {
|
||||||
</Box>
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
imageId: "",
|
||||||
<Stack gap="xs">
|
};
|
||||||
<Title order={3}>Create Kontak Darurat</Title>
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
<TextInput
|
};
|
||||||
|
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
const handleSubmit = async () => {
|
||||||
placeholder="masukkan judul"
|
if (!file) {
|
||||||
/>
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
<TextInput
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
file,
|
||||||
placeholder="masukkan deskripsi"
|
name: file.name,
|
||||||
/>
|
});
|
||||||
|
|
||||||
<TextInput
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
return toast.error("Gagal upload gambar");
|
||||||
placeholder="masukkan kategori"
|
}
|
||||||
/>
|
|
||||||
|
kontakDaruratState.create.form.imageId = uploaded.id;
|
||||||
{/* <FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
await kontakDaruratState.create.create();
|
||||||
value={file}
|
|
||||||
onChange={async (e) => {
|
resetForm();
|
||||||
if (!e) return;
|
router.push("/admin/kesehatan/kontak-darurat")
|
||||||
setFile(e);
|
}
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
return (
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
<Box>
|
||||||
);
|
<Box mb={10}>
|
||||||
setPreviewImage(base64);
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
}}
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
/> */}
|
|
||||||
|
|
||||||
{/* {previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Konten</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button bg={colors['blue-button']}>
|
|
||||||
Simpan Potensi
|
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Box>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
);
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Create Kontak Darurat</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={kontakDaruratState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={kontakDaruratState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKontakDarurat;
|
export default CreateKontakDarurat;
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailKontakDarurat() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Kontak Darurat</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Kontak Darurat</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Kontak Darurat</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/kontak-darurat/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus penanganan darurat ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailKontakDarurat;
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, SimpleGrid, Paper, Title, TextInput, Text, Button, Image } from '@mantine/core';
|
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
function EditKontakDarurat() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Edit Kontak Darurat</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kontak Darurat</Text>}
|
|
||||||
placeholder='Masukkan nama Kontak Darurat'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Kontak Darurat</Text>}
|
|
||||||
placeholder='Masukkan deskripsi Kontak Darurat'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Preview Data Kontak Darurat</Title>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Nama Kontak Darurat</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Kontak Darurat</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Gambar</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditKontakDarurat;
|
|
||||||
@@ -1,66 +1,88 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import kontakDarurat from '../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function KontakDarurat() {
|
function KontakDarurat() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='KontakDarurat'
|
title='Kontak Darurat'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
/>
|
/>
|
||||||
<ListKontakDarurat/>
|
<ListKontakDarurat />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListKontakDarurat() {
|
function ListKontakDarurat() {
|
||||||
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
kontakDaruratState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!kontakDaruratState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Kontak Darurat'
|
title='List Kontak Darurat'
|
||||||
href='/admin/kesehatan/kontak-darurat/create'
|
href='/admin/kesehatan/kontak-darurat/create'
|
||||||
/>
|
/>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Kategori</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{kontakDaruratState.findMany.data?.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
<Box w={100}>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"}>Test</Text>
|
<Box w={100}>
|
||||||
</Box></TableTd>
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
<TableTd>Test</TableTd>
|
</Box>
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Image w={100} src={"/"} alt="image" />
|
<TableTd>
|
||||||
</TableTd>
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/kontak-darurat/detail')}>
|
<TableTd>
|
||||||
<IconDeviceImacCog size={25} />
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
</Button>
|
</TableTd>
|
||||||
</TableTd>
|
<TableTd>
|
||||||
</TableTr>
|
<Button onClick={() => router.push(`/admin/kesehatan/kontak-darurat/${item.id}`)}>
|
||||||
</TableTbody>
|
<IconDeviceImacCog size={25} />
|
||||||
</Table>
|
</Button>
|
||||||
</Box>
|
</TableTd>
|
||||||
</Stack>
|
</TableTr>
|
||||||
</Paper>
|
))}
|
||||||
</Box>
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditPenangananDarurat() {
|
||||||
|
const penangananDaruratState = useProxy(penangananDarurat)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: penangananDaruratState.edit.form.name || '',
|
||||||
|
deskripsi: penangananDaruratState.edit.form.deskripsi || '',
|
||||||
|
imageId: penangananDaruratState.edit.form.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPenangananDarurat = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await penangananDaruratState.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading penanganan darurat:', error);
|
||||||
|
toast.error('Gagal memuat data penanganan darurat');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPenangananDarurat();
|
||||||
|
}, [params?.id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
penangananDaruratState.edit.form = {
|
||||||
|
...penangananDaruratState.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
penangananDaruratState.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await penangananDaruratState.edit.update();
|
||||||
|
toast.success("Penanganan darurat berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/penanganan-darurat");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating penanganan darurat:", error);
|
||||||
|
toast.error("Gagal memuat data penanganan darurat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p={"md"} w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Penanganan Darurat</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack >
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPenangananDarurat;
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { Skeleton } from '@mantine/core';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailPenangananDarurat() {
|
||||||
|
const penangananDaruratState = useProxy(penangananDarurat)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
penangananDaruratState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
penangananDaruratState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/penanganan-darurat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!penangananDaruratState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Penanganan Darurat</Text>
|
||||||
|
{penangananDaruratState.findUnique.data ? (
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Nama Penanganan Darurat</Text>
|
||||||
|
<Text fz={"lg"}>{penangananDaruratState.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: penangananDaruratState.findUnique.data.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={penangananDaruratState.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color="red" onClick={() => {
|
||||||
|
if (penangananDaruratState.findUnique.data) {
|
||||||
|
setSelectedId(penangananDaruratState.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={penangananDaruratState.delete.loading || !penangananDaruratState.findUnique.data}>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${penangananDaruratState.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus penanganan darurat ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPenangananDarurat;
|
||||||
@@ -1,14 +1,56 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import penangananDarurat from '../../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
|
||||||
|
|
||||||
function CreatePenangananDarurat() {
|
function CreatePenangananDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const penangananDaruratState = useProxy(penangananDarurat)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
penangananDaruratState.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
penangananDaruratState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
await penangananDaruratState.create.create();
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/penanganan-darurat")
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
@@ -20,54 +62,48 @@ function CreatePenangananDarurat() {
|
|||||||
<Title order={3}>Create Penanganan Darurat</Title>
|
<Title order={3}>Create Penanganan Darurat</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
value={penangananDaruratState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
penangananDaruratState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
|
||||||
|
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
|
||||||
placeholder="masukkan deskripsi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
|
|
||||||
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
|
||||||
placeholder="masukkan kategori"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* <FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={async (e) => {
|
|
||||||
if (!e) return;
|
|
||||||
setFile(e);
|
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
{/* {previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold">Konten</Text>
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
<KesehatanEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={penangananDaruratState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
penangananDaruratState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button bg={colors['blue-button']}>
|
<FileInput
|
||||||
Simpan Potensi
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
</Button>
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailPenangananDarurat() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Penanganan Darurat</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Penanganan Darurat</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Penanganan Darurat</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/penanganan-darurat/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus penanganan darurat ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailPenangananDarurat;
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, SimpleGrid, Paper, Title, TextInput, Text, Button, Image } from '@mantine/core';
|
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
function EditPenangananDarurat() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Edit Penanganan Darurat</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Penanganan Darurat</Text>}
|
|
||||||
placeholder='Masukkan nama Penanganan Darurat'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Penanganan Darurat</Text>}
|
|
||||||
placeholder='Masukkan deskripsi Penanganan Darurat'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Preview Data Penanganan Darurat</Title>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Nama Penanganan Darurat</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>No Telp Penanganan Darurat</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPenangananDarurat;
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import penangananDarurat from '../../_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
|
|
||||||
|
|
||||||
function PenangananDarurat() {
|
function PenangananDarurat() {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +24,21 @@ function PenangananDarurat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListPenangananDarurat() {
|
function ListPenangananDarurat() {
|
||||||
|
const penangananDaruratState = useProxy(penangananDarurat)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
penangananDaruratState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!penangananDaruratState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -34,27 +52,31 @@ function ListPenangananDarurat() {
|
|||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Judul</TableTh>
|
||||||
<TableTh>Kategori</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{penangananDaruratState.findMany.data?.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Box w={100}>
|
||||||
<Text truncate="end" fz={"sm"}>Test</Text>
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
</Box></TableTd>
|
</Box></TableTd>
|
||||||
<TableTd>Test</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Image w={100} src={"/"} alt="image" />
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/penanganan-darurat/detail')}>
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
|
||||||
<IconDeviceImacCog size={25} />
|
<IconDeviceImacCog size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
147
src/app/admin/(dashboard)/kesehatan/posyandu/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function EditPosyandu() {
|
||||||
|
const statePosyandu = useProxy(posyandustate)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: statePosyandu.edit.form.name || '',
|
||||||
|
nomor: statePosyandu.edit.form.nomor || '',
|
||||||
|
deskripsi: statePosyandu.edit.form.deskripsi || '',
|
||||||
|
imageId: statePosyandu.edit.form.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPosyandu = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await statePosyandu.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
nomor: data.nomor || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading posyandu:", error);
|
||||||
|
toast.error("Gagal memuat data posyandu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadPosyandu();
|
||||||
|
}, [params?.id])
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
statePosyandu.edit.form = {
|
||||||
|
...statePosyandu.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
nomor: formData.nomor,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
statePosyandu.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await statePosyandu.edit.update();
|
||||||
|
toast.success("Posyandu berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/posyandu");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating posyandu:", error);
|
||||||
|
toast.error("Gagal memuat data posyandu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={4}>Edit Posyandu</Title>
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
||||||
|
placeholder='Masukkan nama posyandu'
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
value={formData.nomor}
|
||||||
|
onChange={(e) => setFormData({ ...formData, nomor: e.target.value })}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
||||||
|
placeholder='Masukkan nomor posyandu'
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
statePosyandu.edit.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPosyandu;
|
||||||
101
src/app/admin/(dashboard)/kesehatan/posyandu/[id]/page.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import posyandustate from '../../../_state/kesehatan/posyandu/posyandu';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailPosyandu() {
|
||||||
|
const statePosyandu = useProxy(posyandustate)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter();
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePosyandu.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
statePosyandu.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/posyandu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statePosyandu.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Posyandu</Text>
|
||||||
|
{statePosyandu.findUnique.data ? (
|
||||||
|
<Paper key={statePosyandu.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Nama Posyandu</Text>
|
||||||
|
<Text fz={"lg"}>{statePosyandu.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Nomor Posyandu</Text>
|
||||||
|
<Text fz={"lg"}>{statePosyandu.findUnique.data.nomor}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi Posyandu</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: statePosyandu.findUnique.data.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={statePosyandu.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button onClick={() => {
|
||||||
|
if (statePosyandu.findUnique.data) {
|
||||||
|
setSelectedId(statePosyandu.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}} color="red">
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/posyandu/${statePosyandu.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus posyandu ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPosyandu;
|
||||||
@@ -1,13 +1,59 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React from 'react';
|
import { useState } from 'react';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import posyandustate from '../../../_state/kesehatan/posyandu/posyandu';
|
||||||
|
|
||||||
function CreatePosyandu() {
|
function CreatePosyandu() {
|
||||||
|
const statePosyandu = useProxy(posyandustate)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
statePosyandu.create.form = {
|
||||||
|
name: "",
|
||||||
|
nomor: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
setFile(null);
|
||||||
|
setPreviewImage(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload gambar dulu
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
statePosyandu.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
await statePosyandu.create.create();
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/posyandu")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
@@ -19,26 +65,52 @@ function CreatePosyandu() {
|
|||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Posyandu</Title>
|
<Title order={4}>Create Posyandu</Title>
|
||||||
<Box>
|
{previewImage ? (
|
||||||
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
<IconImageInPicture size={50} />
|
) : (
|
||||||
</Box>
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
||||||
placeholder='Masukkan nama posyandu'
|
placeholder='Masukkan nama posyandu'
|
||||||
|
value={statePosyandu.create.form.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePosyandu.create.form.name = e.target.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
||||||
placeholder='Masukkan nomor posyandu'
|
placeholder='Masukkan nomor posyandu'
|
||||||
|
value={statePosyandu.create.form.nomor}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePosyandu.create.form.nomor = e.target.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
||||||
<KesehatanEditor
|
<CreateEditor
|
||||||
showSubmit={false}
|
value={statePosyandu.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
statePosyandu.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailPosyandu() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Posyandu</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Posyandu</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Nomor Posyandu</Text>
|
|
||||||
<Text fz={"lg"}>089647038426</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Posyandu</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/posyandu/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus penanganan darurat ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailPosyandu;
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
|
||||||
|
|
||||||
function EditPosyandu() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Edit Posyandu</Title>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Masukkan Image</Text>
|
|
||||||
<IconImageInPicture size={50} />
|
|
||||||
</Box>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
|
||||||
placeholder='Masukkan nama posyandu'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nomor Posyandu</Text>}
|
|
||||||
placeholder='Masukkan nomor posyandu'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Posyandu</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPosyandu;
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import posyandustate from '../../_state/kesehatan/posyandu/posyandu';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function Posyandu() {
|
function Posyandu() {
|
||||||
return (
|
return (
|
||||||
@@ -14,13 +17,27 @@ function Posyandu() {
|
|||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
/>
|
/>
|
||||||
<ListPosyandu/>
|
<ListPosyandu />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListPosyandu() {
|
function ListPosyandu() {
|
||||||
|
const statePosyandu = useProxy(posyandustate)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePosyandu.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!statePosyandu.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -28,28 +45,34 @@ function ListPosyandu() {
|
|||||||
title='List Posyandu'
|
title='List Posyandu'
|
||||||
href='/admin/kesehatan/posyandu/create'
|
href='/admin/kesehatan/posyandu/create'
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<TableThead>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableTr>
|
<TableThead>
|
||||||
<TableTh>Nama Posyandu</TableTh>
|
<TableTr>
|
||||||
<TableTh>Nomor Posyandu</TableTh>
|
<TableTh>Nama Posyandu</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Nomor Posyandu</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{statePosyandu.findMany.data?.map((item) => (
|
||||||
<TableTd>Posyandu 1</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>0896232831883</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>Posyandu 1</TableTd>
|
<TableTd>{item.nomor}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/posyandu/detail')}>
|
<Text fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
<IconDeviceImac size={20} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd>
|
||||||
</TableTd>
|
<Button onClick={() => router.push(`/admin/kesehatan/posyandu/${item.id}`)}>
|
||||||
</TableTr>
|
<IconDeviceImac size={20} />
|
||||||
</TableTbody>
|
</Button>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
function EditProgramKesehatan() {
|
||||||
|
const programKesehatanState = useProxy(programKesehatan)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: programKesehatanState.edit.form.name || '',
|
||||||
|
deskripsiSingkat: programKesehatanState.edit.form.deskripsiSingkat || '',
|
||||||
|
deskripsi: programKesehatanState.edit.form.deskripsi || '',
|
||||||
|
imageId: programKesehatanState.edit.form.imageId || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProgramKesehatan = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await programKesehatanState.edit.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
deskripsiSingkat: data.deskripsiSingkat || '',
|
||||||
|
deskripsi: data.deskripsi || '',
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading program kesehatan:", error);
|
||||||
|
toast.error("Gagal memuat data program kesehatan");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadProgramKesehatan();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
programKesehatanState.edit.form = {
|
||||||
|
...programKesehatanState.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
deskripsiSingkat: formData.deskripsiSingkat,
|
||||||
|
deskripsi: formData.deskripsi,
|
||||||
|
imageId: formData.imageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
programKesehatanState.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await programKesehatanState.edit.update();
|
||||||
|
toast.success("Program kesehatan berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/program-kesehatan");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating program kesehatan:", error);
|
||||||
|
toast.error("Gagal memuat data program kesehatan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Program Kesehatan</Title>
|
||||||
|
<TextInput
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={formData.deskripsiSingkat}
|
||||||
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
|
placeholder="masukkan deskripsi"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
|
value={file}
|
||||||
|
onChange={async (e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setFile(e);
|
||||||
|
const base64 = await e.arrayBuffer().then((buf) =>
|
||||||
|
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
||||||
|
);
|
||||||
|
setPreviewImage(base64);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditProgramKesehatan;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailProgramKesehatan() {
|
||||||
|
const programKesehatanState = useProxy(programKesehatan)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
programKesehatanState.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
programKesehatanState.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/program-kesehatan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!programKesehatanState.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={400} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Program Kesehatan</Text>
|
||||||
|
{programKesehatanState.findUnique.data ? (
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
||||||
|
<Text fz={"lg"}>{programKesehatanState.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi Singkat</Text>
|
||||||
|
<Text fz={"lg"}>{programKesehatanState.findUnique.data.deskripsiSingkat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: programKesehatanState.findUnique.data.deskripsi }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={programKesehatanState.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color="red" onClick={() => {
|
||||||
|
if (programKesehatanState.findUnique.data) {
|
||||||
|
setSelectedId(programKesehatanState.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={programKesehatanState.delete.loading || !programKesehatanState.findUnique.data}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${programKesehatanState.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus program kesehatan ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailProgramKesehatan;
|
||||||
@@ -1,43 +1,101 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
|
||||||
function CreateProgramKesehatan() {
|
function CreateProgramKesehatan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const programKesehatanState = useProxy(programKesehatan)
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
// Reset state di valtio
|
||||||
|
programKesehatanState.create.form = {
|
||||||
|
name: "",
|
||||||
|
deskripsiSingkat: "",
|
||||||
|
deskripsi: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset state lokal
|
||||||
|
setPreviewImage(null);
|
||||||
|
setFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload gambar dulu
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simpan ID gambar ke form
|
||||||
|
programKesehatanState.create.form.imageId = uploaded.id;
|
||||||
|
|
||||||
|
// Submit data berita
|
||||||
|
await programKesehatanState.create.create();
|
||||||
|
|
||||||
|
// Reset form setelah submit
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/kesehatan/program-kesehatan")
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Create Program Kesehatan</Title>
|
<Title order={3}>Create Program Kesehatan</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
value={programKesehatanState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
programKesehatanState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
value={programKesehatanState.create.form.deskripsiSingkat}
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
onChange={(val) => {
|
||||||
|
programKesehatanState.create.form.deskripsiSingkat = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
<CreateEditor
|
||||||
placeholder="masukkan kategori"
|
value={programKesehatanState.create.form.deskripsi}
|
||||||
/>
|
onChange={(val) => {
|
||||||
|
programKesehatanState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* <FileInput
|
<FileInput
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
value={file}
|
value={file}
|
||||||
onChange={async (e) => {
|
onChange={async (e) => {
|
||||||
@@ -48,25 +106,18 @@ function CreateProgramKesehatan() {
|
|||||||
);
|
);
|
||||||
setPreviewImage(base64);
|
setPreviewImage(base64);
|
||||||
}}
|
}}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
{/* {previewImage ? (
|
{previewImage ? (
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
) : (
|
) : (
|
||||||
<Center w={200} h={200} bg="gray">
|
<Center w={200} h={200} bg="gray">
|
||||||
<IconImageInPicture />
|
<IconImageInPicture />
|
||||||
</Center>
|
</Center>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
<Box>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
<Text fz="sm" fw="bold">Konten</Text>
|
Simpan
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button bg={colors['blue-button']}>
|
|
||||||
Simpan Potensi
|
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailProgramKesehatan() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Potensi</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/program-kesehatan/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailProgramKesehatan;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Stack, SimpleGrid, Paper, Title, TextInput, Text, Button } from '@mantine/core';
|
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
|
|
||||||
function EditProgramKesehatan() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={3}>Edit Program Kesehatan</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Program Kesehatan</Text>}
|
|
||||||
placeholder='Masukkan nama Program Kesehatan'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>No Telp Program Kesehatan</Text>}
|
|
||||||
placeholder='Masukkan no telp Program Kesehatan'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
|
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Title order={4}>Preview Data Program Kesehatan</Title>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Nama Program Kesehatan</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>No Telp Program Kesehatan</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditProgramKesehatan;
|
|
||||||
@@ -1,66 +1,86 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import programKesehatan from '../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
function ProgramKesehatan() {
|
function ProgramKesehatan() {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='ProgramKesehatan'
|
title='Program Kesehatan'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
/>
|
/>
|
||||||
<ListProgramKesehatan/>
|
<ListProgramKesehatan />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListProgramKesehatan() {
|
function ListProgramKesehatan() {
|
||||||
|
const programKesehatanState = useProxy(programKesehatan)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
programKesehatanState.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!programKesehatanState.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Program Kesehatan'
|
title='List Program Kesehatan'
|
||||||
href='/admin/kesehatan/program-kesehatan/create'
|
href='/admin/kesehatan/program-kesehatan/create'
|
||||||
/>
|
/>
|
||||||
<Box style={{ overflowX: "auto" }}>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh w={250}>Judul</TableTh>
|
||||||
<TableTh>Kategori</TableTh>
|
<TableTh w={250}>Deskripsi Singkat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh w={250}>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh w={200}>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{programKesehatanState.findMany.data?.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
<Box w={100}>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"}>Test</Text>
|
<Box w={100}>
|
||||||
</Box></TableTd>
|
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
||||||
<TableTd>Test</TableTd>
|
</Box>
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Image w={100} src={"/"} alt="image" />
|
<TableTd>{item.deskripsiSingkat}</TableTd>
|
||||||
</TableTd>
|
<TableTd>
|
||||||
<TableTd>
|
<Image w={100} src={item.image?.link} alt="image" />
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/program-kesehatan/detail')}>
|
</TableTd>
|
||||||
<IconDeviceImacCog size={25} />
|
<TableTd>
|
||||||
</Button>
|
<Button onClick={() => router.push(`/admin/kesehatan/program-kesehatan/${item.id}`)}>
|
||||||
</TableTd>
|
<IconDeviceImacCog size={25} />
|
||||||
</TableTr>
|
</Button>
|
||||||
</TableTbody>
|
</TableTd>
|
||||||
</Table>
|
</TableTr>
|
||||||
</Box>
|
))}
|
||||||
</Stack>
|
</TableTbody>
|
||||||
</Paper>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
286
src/app/admin/(dashboard)/kesehatan/puskesmas/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
interface PuskesmasFormBase {
|
||||||
|
name: string;
|
||||||
|
alamat: string;
|
||||||
|
jam: {
|
||||||
|
workDays: string;
|
||||||
|
weekDays: string;
|
||||||
|
holiday: string;
|
||||||
|
};
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: string;
|
||||||
|
email: string;
|
||||||
|
facebook: string;
|
||||||
|
kontakUGD: string;
|
||||||
|
};
|
||||||
|
imageId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PuskesmasFormData extends PuskesmasFormBase {
|
||||||
|
image?: {
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditPuskesmas() {
|
||||||
|
const statePuskesmas = useProxy(puskesmasState);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState<PuskesmasFormData>({
|
||||||
|
name: statePuskesmas.edit.form.name || '',
|
||||||
|
alamat: statePuskesmas.edit.form.alamat || '',
|
||||||
|
jam: {
|
||||||
|
workDays: statePuskesmas.edit.form.jam?.workDays || '',
|
||||||
|
weekDays: statePuskesmas.edit.form.jam?.weekDays || '',
|
||||||
|
holiday: statePuskesmas.edit.form.jam?.holiday || '',
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: statePuskesmas.edit.form.kontak?.kontakPuskesmas || '',
|
||||||
|
email: statePuskesmas.edit.form.kontak?.email || '',
|
||||||
|
facebook: statePuskesmas.edit.form.kontak?.facebook || '',
|
||||||
|
kontakUGD: statePuskesmas.edit.form.kontak?.kontakUGD || '',
|
||||||
|
},
|
||||||
|
imageId: statePuskesmas.edit.form.imageId || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPuskesmas = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await statePuskesmas.edit.load(id);
|
||||||
|
const { form } = statePuskesmas.edit;
|
||||||
|
if (form) {
|
||||||
|
setFormData({
|
||||||
|
name: form.name,
|
||||||
|
alamat: form.alamat,
|
||||||
|
jam: {
|
||||||
|
workDays: form.jam.workDays,
|
||||||
|
weekDays: form.jam.weekDays,
|
||||||
|
holiday: form.jam.holiday,
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: form.kontak.kontakPuskesmas,
|
||||||
|
email: form.kontak.email,
|
||||||
|
facebook: form.kontak.facebook,
|
||||||
|
kontakUGD: form.kontak.kontakUGD,
|
||||||
|
},
|
||||||
|
imageId: form.imageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there's an existing image URL in the form data
|
||||||
|
const formWithImage = form as PuskesmasFormData;
|
||||||
|
if (formWithImage.image?.link) {
|
||||||
|
setPreviewImage(formWithImage.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading puskesmas:", error);
|
||||||
|
toast.error("Gagal memuat data puskesmas");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPuskesmas();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
statePuskesmas.edit.form = {
|
||||||
|
...statePuskesmas.edit.form,
|
||||||
|
name: formData.name,
|
||||||
|
alamat: formData.alamat,
|
||||||
|
jam: {
|
||||||
|
workDays: formData.jam.workDays,
|
||||||
|
weekDays: formData.jam.weekDays,
|
||||||
|
holiday: formData.jam.holiday,
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: formData.kontak.kontakPuskesmas,
|
||||||
|
email: formData.kontak.email,
|
||||||
|
facebook: formData.kontak.facebook,
|
||||||
|
kontakUGD: formData.kontak.kontakUGD,
|
||||||
|
},
|
||||||
|
imageId: formData.imageId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
toast.error("Gagal upload gambar");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statePuskesmas.edit.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = await statePuskesmas.edit.submit();
|
||||||
|
if (success) {
|
||||||
|
toast.success("Puskesmas berhasil diperbarui!");
|
||||||
|
router.push("/admin/kesehatan/puskesmas");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating puskesmas:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data puskesmas");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (selectedFile: File | null) => {
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
if (e.target?.result) {
|
||||||
|
setPreviewImage(e.target.result as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(selectedFile);
|
||||||
|
} else {
|
||||||
|
setFile(null);
|
||||||
|
setPreviewImage(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNestedChange = (section: 'jam' | 'kontak', field: string, value: string) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[section]: {
|
||||||
|
...prev[section],
|
||||||
|
[field]: value
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Puskesmas</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
||||||
|
placeholder="masukkan nama puskesmas"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
name="alamat"
|
||||||
|
value={formData.alamat}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Jam Buka</Text>}
|
||||||
|
placeholder="masukkan jam buka"
|
||||||
|
value={formData.jam.workDays}
|
||||||
|
onChange={(e) => handleNestedChange('jam', 'workDays', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Jam Tutup</Text>}
|
||||||
|
placeholder="masukkan jam tutup"
|
||||||
|
value={formData.jam.weekDays}
|
||||||
|
onChange={(e) => handleNestedChange('jam', 'weekDays', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Jam Libur</Text>}
|
||||||
|
placeholder="masukkan jam libur"
|
||||||
|
value={formData.jam.holiday}
|
||||||
|
onChange={(e) => handleNestedChange('jam', 'holiday', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Kontak Puskesmas</Text>}
|
||||||
|
placeholder="masukkan kontak puskesmas"
|
||||||
|
value={formData.kontak.kontakPuskesmas}
|
||||||
|
onChange={(e) => handleNestedChange('kontak', 'kontakPuskesmas', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Email</Text>}
|
||||||
|
placeholder="masukkan email"
|
||||||
|
value={formData.kontak.email}
|
||||||
|
onChange={(e) => handleNestedChange('kontak', 'email', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Facebook</Text>}
|
||||||
|
placeholder="masukkan facebook"
|
||||||
|
value={formData.kontak.facebook}
|
||||||
|
onChange={(e) => handleNestedChange('kontak', 'facebook', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
||||||
|
placeholder="masukkan kontak UGD"
|
||||||
|
value={formData.kontak.kontakUGD}
|
||||||
|
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
placeholder="Pilih gambar"
|
||||||
|
label="Gambar"
|
||||||
|
accept="image/*"
|
||||||
|
leftSection={<IconImageInPicture size={16} />}
|
||||||
|
value={file}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="Preview" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg="gray">
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
loading={statePuskesmas.edit.loading}
|
||||||
|
>
|
||||||
|
Simpan Perubahan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPuskesmas;
|
||||||
111
src/app/admin/(dashboard)/kesehatan/puskesmas/[id]/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Flex, Text, Image, Skeleton } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function DetailPuskesmas() {
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter();
|
||||||
|
const statePuskesmas = useProxy(puskesmasState)
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePuskesmas.findUnique.load(params?.id as string)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
statePuskesmas.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/kesehatan/puskesmas")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statePuskesmas.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Puskesmas</Text>
|
||||||
|
{statePuskesmas.findUnique.data ? (
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Nama Puskesmas</Text>
|
||||||
|
<Text fz={"lg"}>{statePuskesmas.findUnique.data.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Alamat</Text>
|
||||||
|
<Text fz={"lg"}>{statePuskesmas.findUnique.data.alamat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Jam Operasional</Text>
|
||||||
|
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.workDays}</Text>
|
||||||
|
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.weekDays}</Text>
|
||||||
|
<Text fz={"lg"}>{statePuskesmas.findUnique.data.jam.holiday}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Image src={statePuskesmas.findUnique.data.image?.link} alt="gambar" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Kontak</Text>
|
||||||
|
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.kontakPuskesmas}</Text>
|
||||||
|
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.email}</Text>
|
||||||
|
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.facebook}</Text>
|
||||||
|
<Text fz={"lg"} >{statePuskesmas.findUnique.data.kontak.kontakUGD}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Flex gap={"xs"}>
|
||||||
|
<Button color="red" onClick={() => {
|
||||||
|
if (statePuskesmas.findUnique.data) {
|
||||||
|
setSelectedId(statePuskesmas.findUnique.data.id)
|
||||||
|
setModalHapus(true)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${statePuskesmas.findUnique.data?.id}/edit`)} color="green">
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus potensi ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DetailPuskesmas;
|
||||||
@@ -1,14 +1,76 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { KesehatanEditor } from '../../_com/kesehatanEditor';
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import puskesmasState from '../../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
|
|
||||||
function CreatePuskesmas() {
|
function CreatePuskesmas() {
|
||||||
|
const statePuskesmas = useProxy(puskesmasState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
statePuskesmas.create.form = {
|
||||||
|
name: "",
|
||||||
|
alamat: "",
|
||||||
|
jam: {
|
||||||
|
workDays: "",
|
||||||
|
weekDays: "",
|
||||||
|
holiday: "",
|
||||||
|
},
|
||||||
|
kontak: {
|
||||||
|
kontakPuskesmas: "",
|
||||||
|
email: "",
|
||||||
|
facebook: "",
|
||||||
|
kontakUGD: "",
|
||||||
|
},
|
||||||
|
imageId: "",
|
||||||
|
image: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFile(null);
|
||||||
|
setPreviewImage(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!file) {
|
||||||
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload gambar dulu
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
|
||||||
|
statePuskesmas.create.form.imageId = uploaded.id;
|
||||||
|
// State is already being updated directly in the form inputs
|
||||||
|
|
||||||
|
await statePuskesmas.create.submit();
|
||||||
|
|
||||||
|
toast.success("Data berhasil disimpan");
|
||||||
|
resetForm();
|
||||||
|
// After successful submission, redirect to the list page
|
||||||
|
router.push('/admin/kesehatan/puskesmas');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box component="form" onSubmit={handleSubmit}>
|
||||||
<Box mb={10}>
|
<Box mb={10}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
@@ -18,26 +80,80 @@ function CreatePuskesmas() {
|
|||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Create Puskesmas</Title>
|
<Title order={3}>Create Puskesmas</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
placeholder="masukkan nama puskesmas"
|
||||||
placeholder="masukkan judul"
|
value={statePuskesmas.create.form.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.name = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Alamat</Text>}
|
||||||
|
placeholder="masukkan alamat"
|
||||||
|
value={statePuskesmas.create.form.alamat}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.alamat = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Jam Buka</Text>}
|
||||||
|
placeholder="masukkan jam buka"
|
||||||
|
value={statePuskesmas.create.form.jam.workDays}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.jam.workDays = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Jam Tutup</Text>}
|
||||||
|
placeholder="masukkan jam tutup"
|
||||||
|
value={statePuskesmas.create.form.jam.weekDays}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.jam.weekDays = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Holiday</Text>}
|
||||||
|
placeholder="masukkan holiday"
|
||||||
|
value={statePuskesmas.create.form.jam.holiday}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.jam.holiday = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Kontak Puskesmas</Text>}
|
||||||
|
placeholder="masukkan kontak puskesmas"
|
||||||
|
value={statePuskesmas.create.form.kontak.kontakPuskesmas}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.kontak.kontakPuskesmas = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Email</Text>}
|
||||||
|
placeholder="masukkan email"
|
||||||
|
value={statePuskesmas.create.form.kontak.email}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.kontak.email = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Facebook</Text>}
|
||||||
|
placeholder="masukkan facebook"
|
||||||
|
value={statePuskesmas.create.form.kontak.facebook}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.kontak.facebook = e.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
||||||
|
placeholder="masukkan kontak ugd"
|
||||||
|
value={statePuskesmas.create.form.kontak.kontakUGD}
|
||||||
|
onChange={(e) => {
|
||||||
|
statePuskesmas.create.form.kontak.kontakUGD = e.target.value;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<FileInput
|
||||||
|
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
|
|
||||||
placeholder="masukkan deskripsi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
|
|
||||||
label={<Text fz="sm" fw="bold">Kategori</Text>}
|
|
||||||
placeholder="masukkan kategori"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* <FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
||||||
value={file}
|
value={file}
|
||||||
onChange={async (e) => {
|
onChange={async (e) => {
|
||||||
@@ -48,25 +164,18 @@ function CreatePuskesmas() {
|
|||||||
);
|
);
|
||||||
setPreviewImage(base64);
|
setPreviewImage(base64);
|
||||||
}}
|
}}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
{/* {previewImage ? (
|
{previewImage ? (
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
) : (
|
) : (
|
||||||
<Center w={200} h={200} bg="gray">
|
<Center w={200} h={200} bg="gray">
|
||||||
<IconImageInPicture />
|
<IconImageInPicture />
|
||||||
</Center>
|
</Center>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
<Box>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
<Text fz="sm" fw="bold">Konten</Text>
|
Simpan Puskesmas
|
||||||
<KesehatanEditor
|
|
||||||
showSubmit={false}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button bg={colors['blue-button']}>
|
|
||||||
Simpan Potensi
|
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React from 'react';
|
|
||||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
|
|
||||||
function DetailPuskesmas() {
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Potensi</Text>
|
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
|
||||||
<Text fz={"lg"}>Test Judul</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
|
|
||||||
<Text fz={"lg"}>Test Kategori</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<Text fz={"lg"}>Test Deskripsi</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={"/"} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"lg"} fw={"bold"}>Konten</Text>
|
|
||||||
<Text fz={"lg"} >Test Konten</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button color="red">
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/puskesmas/edit')} color="green">
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Modal Hapus
|
|
||||||
<ModalKonfirmasiHapus
|
|
||||||
opened={modalHapus}
|
|
||||||
onClose={() => setModalHapus(false)}
|
|
||||||
onConfirm={handleHapus}
|
|
||||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DetailPuskesmas;
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import HeaderSearch from '../../_com/header';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
|
import puskesmasState from '../../_state/kesehatan/puskesmas/puskesmas';
|
||||||
|
|
||||||
function Puskesmas() {
|
function Puskesmas() {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +23,20 @@ function Puskesmas() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListPuskesmas() {
|
function ListPuskesmas() {
|
||||||
|
const statePuskesmas = useProxy(puskesmasState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
statePuskesmas.findMany.load()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!statePuskesmas.findMany.data) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Skeleton h={500}/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
@@ -33,28 +49,27 @@ function ListPuskesmas() {
|
|||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Judul</TableTh>
|
<TableTh>Nama Puskesmas</TableTh>
|
||||||
<TableTh>Kategori</TableTh>
|
<TableTh>Alamat</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Image</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
<TableTr>
|
{statePuskesmas.findMany.data?.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
<Box w={100}>
|
<TableTd>{item.name}</TableTd>
|
||||||
<Text truncate="end" fz={"sm"}>Test</Text>
|
<TableTd>{item.alamat}</TableTd>
|
||||||
</Box></TableTd>
|
<TableTd>
|
||||||
<TableTd>Test</TableTd>
|
<Image w={100} src={item.image.link} alt="image" />
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Image w={100} src={"/"} alt="image" />
|
<TableTd>
|
||||||
</TableTd>
|
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
|
||||||
<TableTd>
|
<IconDeviceImacCog size={25} />
|
||||||
<Button onClick={() => router.push('/admin/kesehatan/puskesmas/detail')}>
|
</Button>
|
||||||
<IconDeviceImacCog size={25} />
|
</TableTd>
|
||||||
</Button>
|
</TableTr>
|
||||||
</TableTd>
|
))}
|
||||||
</TableTr>
|
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function Page() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const allList = useProxy(stateProfilePPID)
|
const allList = useProxy(stateProfilePPID)
|
||||||
useShallowEffect(() => {
|
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) {
|
if (!allList.profile.data) {
|
||||||
@@ -45,7 +45,7 @@ function Page() {
|
|||||||
<Grid>
|
<Grid>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<GridCol span={{ base: 12, md: 12 }}>
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<GridCol span={{ base: 12, md: 12 }}>
|
||||||
@@ -62,7 +62,7 @@ function Page() {
|
|||||||
<Center>
|
<Center>
|
||||||
<Image
|
<Image
|
||||||
pt={{ base: 0, md: 90 }}
|
pt={{ base: 0, md: 90 }}
|
||||||
src={item.image?.link}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
w={{ base: 250, md: 350 }}
|
w={{ base: 250, md: 350 }}
|
||||||
alt='Foto Profil PPID'
|
alt='Foto Profil PPID'
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function VisiMisiPPIDEdit() {
|
|||||||
visiMisi.findById.data.misi = draftMisi;
|
visiMisi.findById.data.misi = draftMisi;
|
||||||
visiMisi.update.save(visiMisi.findById.data);
|
visiMisi.update.save(visiMisi.findById.data);
|
||||||
}
|
}
|
||||||
|
router.push('/admin/ppid/visi-misi-ppid')
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function VisiMisiPPIDList() {
|
|||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Paper p={"xl"} bg={colors['BG-trans']}>
|
||||||
<Box pb={30}>
|
<Box pb={30}>
|
||||||
<Center>
|
<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>
|
</Center>
|
||||||
<Text ta={"center"} fz={{ base: "h2", md: "2.5rem" }} fw={"bold"}>
|
<Text ta={"center"} fz={{ base: "h2", md: "2.5rem" }} fw={"bold"}>
|
||||||
MOTO PPID DESA DARMASABA
|
MOTO PPID DESA DARMASABA
|
||||||
|
|||||||
@@ -11,41 +11,26 @@ export const navBar = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Landing_Page_2",
|
id: "Landing_Page_2",
|
||||||
name: "Penghargaan",
|
|
||||||
path: "/admin/landing-page/penghargaan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Landing_Page_3",
|
|
||||||
name: "Layanan",
|
|
||||||
path: "/admin/landing-page/layanan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Landing_Page_4",
|
|
||||||
name: "Potensi",
|
|
||||||
path: "/admin/landing-page/potensi"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Landing_Page_5",
|
|
||||||
name: "Desa Anti Korupsi",
|
name: "Desa Anti Korupsi",
|
||||||
path: "/admin/landing-page/desa-anti-korupsi"
|
path: "/admin/landing-page/desa-anti-korupsi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Landing_Page_6",
|
id: "Landing_Page_3",
|
||||||
name: "Indeks Kepuasan Masyarakat",
|
name: "Indeks Kepuasan Masyarakat",
|
||||||
path: "/admin/landing-page/indeks-kepuasan-masyarakat"
|
path: "/admin/landing-page/indeks-kepuasan-masyarakat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Landing_Page_7",
|
id: "Landing_Page_4",
|
||||||
name: "SDGs Desa",
|
name: "SDGs Desa",
|
||||||
path: "/admin/landing-page/sdgs-desa"
|
path: "/admin/landing-page/sdgs-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Landing_Page_8",
|
id: "Landing_Page_5",
|
||||||
name: "APBDes",
|
name: "APBDes",
|
||||||
path: "/admin/landing-page/apbdes"
|
path: "/admin/landing-page/apbdes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Landing_Page_9",
|
id: "Landing_Page_6",
|
||||||
name: "Prestasi Desa",
|
name: "Prestasi Desa",
|
||||||
path: "/admin/landing-page/prestasi-desa"
|
path: "/admin/landing-page/prestasi-desa"
|
||||||
}
|
}
|
||||||
@@ -108,7 +93,7 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "Desa_1",
|
id: "Desa_1",
|
||||||
name: "Profile",
|
name: "Profile",
|
||||||
path: "/admin/desa/profile"
|
path: "/admin/desa/profile/profile-desa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Desa_2",
|
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 "@/lib/prisma";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
|
||||||
type FormCreate = Prisma.ProfilPerbekelGetPayload<{
|
type FormUpdate = Prisma.ProfilPerbekelGetPayload<{
|
||||||
select: {
|
select: {
|
||||||
id: true;
|
id: true;
|
||||||
biodata: true;
|
biodata: true;
|
||||||
pengalaman: true;
|
pengalaman: true;
|
||||||
pengalamanOrganisasi: true;
|
pengalamanOrganisasi: true;
|
||||||
programUnggulan: true;
|
programUnggulan: true;
|
||||||
}
|
imageId: true;
|
||||||
}>
|
};
|
||||||
|
}>;
|
||||||
export default async function profilePerbekelUpdate(context: Context) {
|
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({
|
const { biodata, pengalaman, pengalamanOrganisasi, programUnggulan, imageId } = body;
|
||||||
where: {
|
|
||||||
id: body.id
|
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: {
|
data: {
|
||||||
biodata: body.biodata,
|
biodata,
|
||||||
pengalaman: body.pengalaman,
|
pengalaman,
|
||||||
pengalamanOrganisasi: body.pengalamanOrganisasi,
|
pengalamanOrganisasi,
|
||||||
programUnggulan: body.programUnggulan,
|
programUnggulan,
|
||||||
|
imageId,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return new Response(
|
||||||
success: true,
|
JSON.stringify({
|
||||||
message: "Profile Perbekel Berhasil Diupdate",
|
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 SejarahDesa from "./sejarah";
|
||||||
import lambangDesaUpdate from "./lambangDesa/update";
|
import VisiMisiDesa from "./visi-misi";
|
||||||
import maskotDesaUpdate from "./maskotDesa/update";
|
import LambangDesa from "./lambang-desa";
|
||||||
import profilePerbekelUpdate from "../profilePerbekel/update";
|
import MaskotDesa from "./maskot-desa";
|
||||||
import sejarahDesaUpdate from "./sejarah/update";
|
import Elysia from "elysia";
|
||||||
import visimisiDesaUpdate from "./visimisiDesa/update";
|
import ProfilPerbekel from "../profilePerbekel";
|
||||||
import profileDesaFindById from "./find-by-id";
|
|
||||||
|
|
||||||
const ProfileDesa = new Elysia({
|
const ProfileDesa = new Elysia({
|
||||||
prefix: "/profile",
|
prefix: "/profile",
|
||||||
tags: ["Desa/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(),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
.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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||