Compare commits

...

70 Commits

Author SHA1 Message Date
7439eb7687 API & UI Menu Lingkungan, Submenu Penglolaan Sampah 2025-07-17 17:09:28 +08:00
49a1084099 API & UI Menu Inovasi, SubMenu Layanan Online Desa 2025-07-17 15:31:10 +08:00
cde6c91cd4 API & UI Menu Inovasi, Submenu Layanan Online Desa: Tab 1-4 2025-07-17 11:44:53 +08:00
55433128a9 API & UI Menu Inovasi & Submenu Layan Online Desa 2 tabs 2025-07-16 00:23:15 +08:00
e8ad74d118 Merge branch 'nico/push-stg' into nico/15-jul-25 2025-07-15 11:51:13 +08:00
99c1fd1004 UI & API Menu Inovasi, SubMenu : Kolaborasi Inovasi & Info Teknologi 2025-07-15 11:48:56 +08:00
03c0523194 UI & API Menu Inovasi, SubMenu Program Kreatif Desa 2025-07-14 17:35:38 +08:00
ae328f40a0 UI & API Menu Inovasi, SubMenu Program Kreatif Desa 2025-07-14 17:14:02 +08:00
6e109ffe00 FIX UI Admin Menu PPID 2025-07-14 14:33:08 +08:00
c4aea568e9 UI & API Smart DIgital Village 2025-07-14 10:24:59 +08:00
1c8104ee69 API & UI Admin Menu Ekonomi, Submenu PADesa 2025-07-11 18:50:20 +08:00
4baffe95f3 UI & API Menu Ekonomi, SubMenu PADesa : Tabs Pendapatan, Pembiayaan, dan Belanja 2025-07-11 17:51:07 +08:00
cb52701f47 API & UI Tabs SubMenu Pendapatan, Menu PADesa 2025-07-10 16:52:26 +08:00
2bc9b2f3c6 Menerapkan pagination di submenu pegawai & berita 2025-07-10 10:46:58 +08:00
7b2b306849 API & UI Menu Ekonomi, Submenu Jumlah Pengangguran 2025-07-10 00:21:33 +08:00
d328f64d86 UI & API Tabs Detail Data Pengangguran 2025-07-09 15:25:48 +08:00
119275b95c API & State Jumlah Pengangguran 2025-07-09 12:00:37 +08:00
124dfb8160 UI & API Menu Ekonomi, Submenu Demografi 2025-07-08 23:30:48 +08:00
46c79b8ded UI & API Submenu Sektor Unggulan, Menu Ekonomi 2025-07-08 17:16:13 +08:00
d105293149 UI & API Menu Ekonomi, Submenu Jumlah Penduduk Miskin 2025-07-08 16:30:33 +08:00
adcbe3aa3d UI & API Menu Ekonomi, Submenu Jumlah Penduduk usia yang menganggur 2025-07-08 15:20:26 +08:00
bffe648802 Fix Tabs Pegawai, Submenu Struktur & Organisasi, Menu Ekonomi 2025-07-08 12:05:10 +08:00
7e95d5fbb4 Add Fitur Search Di setiap menu 2025-07-08 00:30:55 +08:00
2725c2c064 UI & API Menu Struktur Organisasi Tabs Hubungan Organisasi 2025-07-07 22:24:46 +08:00
be189df37c UI & API Struktur organisasi sudah bisa aktif & tidak aktif 2025-07-07 21:45:04 +08:00
c0b941395d UI & API Struktur organisasi sudah bisa aktif & tidak aktif 2025-07-07 21:41:27 +08:00
a2e25a3e3a API & UI Menu Struktur bagian pegawai 2025-07-07 17:14:44 +08:00
d86824a943 API & UI Struktur Organisasi Posisi Organisasi 2025-07-07 11:33:05 +08:00
c823462a47 Merge pull request #39 from bipproduction/nico/4-jul-25
Nico/4 jul 25:
Fix UI & API Admin Menu Ekonomi Pasar Desa
2025-07-04 11:20:15 +08:00
4f97c01501 Perbaikan UI & API Menu Ekonomi Pasar Desa 2025-07-04 11:09:06 +08:00
0fd47e3e94 API & UI Pasar Desa Menu Ekonomi 2025-07-04 00:11:55 +08:00
b92a974dcd API & UI Admin Menu Keamanan Done 2025-07-03 16:09:39 +08:00
10361770b4 API & UI Program Kemiskinan Menu Ekonomi 2025-07-03 12:21:08 +08:00
aec2f5094a Push 1 Program Kemiskinan 2025-07-03 11:24:54 +08:00
72d39b020a API/UI Admin Ekonomi Lowongan kerja 2025-07-02 17:03:20 +08:00
51d67736ef API & UI Admin Keamanan Laporan Publik 2025-07-02 15:35:10 +08:00
406c6f3c9f UI & API Menu Keamanan, Kontak Darurat 2025-07-02 14:10:22 +08:00
1c5e4410c4 Save 2025-07-01 20:57:32 +08:00
4724b7473d Try Fix UI & API Menu Ekonomi Sub Menu Pasar Desa 2025-07-01 16:48:44 +08:00
32a75bcb01 Merge pull request #38 from bipproduction/nico/1-jul-25
UI & API Menu Keamanan baru 3 Menu : Keamanan Lingkungan, Polsek Terd
2025-07-01 11:19:03 +08:00
c5fc4f4cea UI & API Menu Keamanan baru 3 Menu : Keamanan Lingkungan, Polsek Terdekat, & Tips Keamanan 2025-07-01 11:16:53 +08:00
9f39eb41ab Merge pull request #36 from bipproduction/nico/30-jun-2025
Nico/30 jun 2025
2025-06-30 11:15:28 +08:00
81ea18cb07 Merge branch 'nico/push-stg' into nico/30-jun-2025 2025-06-30 11:15:06 +08:00
dd7ce6943d Keperluan Deploy 2025-06-30 11:04:20 +08:00
ee10f339e9 API Admin Menu Keamanan Done 2025-06-30 10:56:46 +08:00
02462b2c19 API 2 Menu Keamanan 2025-06-29 02:46:17 +08:00
41181d4cb3 Fix Dibagian Data Kesehatan Warga 2025-06-28 00:10:45 +08:00
6d5b8dcf64 UI & API Admin Menu Kesehatan Done 2025-06-27 23:52:00 +08:00
924be5b11b Sisa 1 Tabs aja yang data kesehatan warga 2025-06-26 17:24:57 +08:00
21085ce342 Merge branch 'staging' into nico/push-stg 2025-06-26 11:19:54 +08:00
88784f00f6 Merge pull request #34 from bipproduction/nico/25-jun-25
Nico/25 jun 25
2025-06-26 11:01:15 +08:00
456342851b Merge pull request #33 from bipproduction/nico/25-jun-25
Nico/25 jun 25
staging versi 0.1.3
2025-06-25 16:32:28 +08:00
fa922c7127 Merge pull request #31 from bipproduction/nico/push-stg
Nico/push stg
2025-06-18 16:38:45 +08:00
45acdba93f Merge pull request #30 from bipproduction/nico/18-jun-25
Nico/18 jun 25
2025-06-18 16:38:10 +08:00
cb8d561467 Merge pull request #29 from bipproduction/nico/push-stg
Nico/push stg 18 Juni 2025:
UI & API Menu PPID & Desa Clear
2025-06-18 15:35:49 +08:00
85a0cb6d56 Merge pull request #28 from bipproduction/nico/18-jun-25
Nico/18 jun 25:
UI & API Menu PPID & Desa Clear
2025-06-18 15:34:27 +08:00
4f2c565b2e Merge pull request #27 from bipproduction/nico/push-stg
Nico/push stg

Senin, 2 Juni 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu desa
Yang Lagi Dikerjakan:
* Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin di menu keamanan
2025-06-02 22:30:15 +08:00
c4adc9bb22 Merge pull request #26 from bipproduction/nico/1-jun-25
Senin, 2 Juni 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu desa
Yang Lagi Dikerjakan:
* Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin di menu keamanan
2025-06-02 22:28:58 +08:00
9d572f82c3 Merge pull request #25 from bipproduction/nico/push-stg
Jum'at, 30 May 2025 : 
Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu inovasi
- API Create, edit dan delete potensi
- Tampilan UI Landing Page sudah sesuai di mobile

Yang Lagi Dikerjakan:
- Progress Tampilan UI Admin Di Menu lingkungan
- Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
- API Create, edit dan delete pengumuman
- Tampilan UI Admin Di Menu Pendidikan
2025-05-30 21:16:15 +08:00
452692f314 Merge pull request #24 from bipproduction/nico/30-may-25
Jum'at, 30 May 2025 : 
Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu inovasi
- API Create, edit dan delete potensi
- Tampilan UI Landing Page sudah sesuai di mobile

Yang Lagi Dikerjakan:
- Progress Tampilan UI Admin Di Menu lingkungan
- Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
- API Create, edit dan delete pengumuman
- Tampilan UI Admin Di Menu Pendidikan
2025-05-30 21:15:27 +08:00
5010677bc8 Merge pull request #23 from bipproduction/nico/27-may-25-01
Selasa, 27 May 2025:
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu Inovasi
* Progress API ProfilePPID

Yang Akan Dikerjakan:
* API Menu Lain
* Tampilan UI Admin Di Menu Lingkungan
* Tampilan UI Admin Di Menu Pendidikan
2025-05-27 11:24:24 +08:00
7af3fbff2d Merge pull request #22 from bipproduction/nico/26-may-25
Senin, 26 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Akan Dikerjakan:
* API ProfilePPID
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 17:16:07 +08:00
34ca736dda Merge pull request #21 from bipproduction/nico/26-may-25
Nico/26 may 25
Senin, 26 May 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, dan delete berita

Yang Akan Dikerjakan:
* API Di Menu Desa : Edit Berita
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 14:30:07 +08:00
3b61f54509 Merge pull request #20 from bipproduction/nico/8-may-25
Nico/8 may 25
2025-05-09 10:36:45 +08:00
fbe0c19d22 Merge pull request #19 from bipproduction/nico/2-may-25
Fix Eror Admin Persentase & Grafik
2025-05-02 16:11:38 +08:00
f8914ab78f Merge pull request #18 from bipproduction/nico/30-apr-25
Admin Dashboard Bagian Data Kesehatan
2025-04-30 16:42:45 +08:00
8f3ee2f831 Push STG 2025-04-30 16:32:29 +08:00
ddf0ca62c4 Merge pull request #17 from bipproduction/nico/28-apr-25
Dashboard Admin
2025-04-30 16:16:48 +08:00
f8cdd3abdd Merge pull request #13 from bipproduction/nico/24-mar-25
tamabah versi
2025-03-24 15:35:55 +08:00
64bc739496 Merge pull request #12 from bipproduction/nico/24-mar-25
Nico/24 mar 25
2025-03-24 15:33:27 +08:00
583 changed files with 38138 additions and 6335 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.3",
"version": "0.1.5",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -15,7 +15,7 @@
"dependencies": {
"@cubejs-client/core": "^0.31.0",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/eden": "^1.2.0",
"@elysiajs/eden": "^1.3.2",
"@elysiajs/static": "^1.3.0",
"@elysiajs/stream": "^1.1.0",
"@elysiajs/swagger": "^1.2.0",
@@ -46,13 +46,14 @@
"bun": "^1.2.2",
"chart.js": "^4.4.8",
"dayjs": "^1.11.13",
"elysia": "^1.2.12",
"elysia": "^1.3.5",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^7.1.0",
"form-data": "^4.0.2",
"framer-motion": "^12.4.1",
"framer-motion": "^12.23.5",
"get-port": "^7.1.0",
"jotai": "^2.12.3",
"list": "^2.0.19",
"lodash": "^4.17.21",
"motion": "^12.4.1",
"nanoid": "^5.1.5",

View File

@@ -0,0 +1,51 @@
[
{
"month": "Jan",
"year": 2025,
"totalUnemployment": 160,
"educatedUnemployment": 95,
"uneducatedUnemployment": 65,
"percentageChange": null
},
{
"month": "Feb",
"year": 2025,
"totalUnemployment": 155,
"educatedUnemployment": 90,
"uneducatedUnemployment": 65,
"percentageChange": -3.1
},
{
"month": "Mar",
"year": 2025,
"totalUnemployment": 150,
"educatedUnemployment": 88,
"uneducatedUnemployment": 62,
"percentageChange": -3.2
},
{
"month": "Apr",
"year": 2025,
"totalUnemployment": 148,
"educatedUnemployment": 85,
"uneducatedUnemployment": 63,
"percentageChange": -1.3
},
{
"month": "Mei",
"year": 2025,
"totalUnemployment": 145,
"educatedUnemployment": 82,
"uneducatedUnemployment": 63,
"percentageChange": -2.0
},
{
"month": "Jun",
"year": 2025,
"totalUnemployment": 140,
"educatedUnemployment": 80,
"uneducatedUnemployment": 60,
"percentageChange": -3.4
}
]

View File

@@ -0,0 +1,10 @@
[
{
"id": "4b95bge6-012e-5ged-9552-4d8g65d44959",
"nama": "Makanan"
},
{
"id": "5c06chf7-123f-6hfe-0663-5e9h76e55060",
"nama": "Minuman"
}
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "650e8400-e29b-41d4-a716-446655440001",
"atasanId": "550e8400-e29b-41d4-a716-446655440001",
"bawahanId": "550e8400-e29b-41d4-a716-446655440002",
"tipe": "Langsung Melapor"
}
]

View File

@@ -0,0 +1,24 @@
[
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"namaLengkap": "Budi Santoso",
"gelarAkademik": "S.IP",
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
"email": "budi@desa.id",
"telepon": "081234567891",
"alamat": "Jl. Raya Desa No. 1",
"posisiId": "kepala_desa",
"isActive": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"namaLengkap": "Ani Lestari",
"gelarAkademik": "S.Pd",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "ani@desa.id",
"telepon": "081234567892",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "sekretaris_desa",
"isActive": true
}
]

View File

@@ -0,0 +1,27 @@
[
{
"id": "kepala_desa",
"nama": "Kepala Desa",
"deskripsi": "Kepala Desa",
"hierarki": 1
},
{
"id": "sekretaris_desa",
"nama": "Sekretaris Desa",
"deskripsi": "Sekretaris Desa",
"hierarki": 2
},
{
"id": "bendahara_desa",
"nama": "Bendahara Desa",
"deskripsi": "Bendahara Desa",
"hierarki": 3
},
{
"id": "staff_umum",
"nama": "Staff Umum",
"deskripsi": "Staff Umum",
"hierarki": 4
}
]

View File

@@ -0,0 +1,92 @@
/*
Warnings:
- The primary key for the `ArtikelKesehatan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `DoctorSign` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `FirstAid` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Introduction` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `MythVsFact` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Prevention` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Symptom` table will be changed. If it partially fails, the table could be left without primary key constraint.
- Added the required column `deskripsiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `dokumenJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `informasiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `layananJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `pendaftaranJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `syaratKetentuanJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "ArtikelKesehatan" DROP CONSTRAINT "ArtikelKesehatan_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "ArtikelKesehatan_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "ArtikelKesehatan_id_seq";
-- AlterTable
ALTER TABLE "DoctorSign" DROP CONSTRAINT "DoctorSign_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "DoctorSign_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "DoctorSign_id_seq";
-- AlterTable
ALTER TABLE "FirstAid" DROP CONSTRAINT "FirstAid_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "FirstAid_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "FirstAid_id_seq";
-- AlterTable
ALTER TABLE "Introduction" DROP CONSTRAINT "Introduction_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Introduction_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Introduction_id_seq";
-- AlterTable
ALTER TABLE "JadwalKegiatan" ADD COLUMN "deskripsiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "dokumenJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "informasiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "layananJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "pendaftaranJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "syaratKetentuanJadwalKegiatanId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "MythVsFact" DROP CONSTRAINT "MythVsFact_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "MythVsFact_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "MythVsFact_id_seq";
-- AlterTable
ALTER TABLE "Prevention" DROP CONSTRAINT "Prevention_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Prevention_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Prevention_id_seq";
-- AlterTable
ALTER TABLE "Symptom" DROP CONSTRAINT "Symptom_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Symptom_id_seq";
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_informasiJadwalKegiatanId_fkey" FOREIGN KEY ("informasiJadwalKegiatanId") REFERENCES "InformasiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_deskripsiJadwalKegiatanId_fkey" FOREIGN KEY ("deskripsiJadwalKegiatanId") REFERENCES "DeskripsiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_layananJadwalKegiatanId_fkey" FOREIGN KEY ("layananJadwalKegiatanId") REFERENCES "LayananJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_syaratKetentuanJadwalKegiatanId_fkey" FOREIGN KEY ("syaratKetentuanJadwalKegiatanId") REFERENCES "SyaratKetentuanJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_dokumenJadwalKegiatanId_fkey" FOREIGN KEY ("dokumenJadwalKegiatanId") REFERENCES "DokumenJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_pendaftaranJadwalKegiatanId_fkey" FOREIGN KEY ("pendaftaranJadwalKegiatanId") REFERENCES "PendaftaranJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,68 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `DataKematian_Kelahiran` table. All the data in the column will be lost.
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `GrafikKepuasan` table. All the data in the column will be lost.
- A unique constraint covering the columns `[id]` on the table `DataKematian_Kelahiran` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[id]` on the table `GrafikKepuasan` will be added. If there are existing duplicate values, this will fail.
- Added the required column `doctorSignId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `firstAidId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `introductionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `mythVsFactId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `preventionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `symptomId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "DataKematian_Kelahiran_uuid_key";
-- DropIndex
DROP INDEX "GrafikKepuasan_uuid_key";
-- AlterTable
ALTER TABLE "ArtikelKesehatan" ADD COLUMN "doctorSignId" TEXT NOT NULL,
ADD COLUMN "firstAidId" TEXT NOT NULL,
ADD COLUMN "introductionId" TEXT NOT NULL,
ADD COLUMN "mythVsFactId" TEXT NOT NULL,
ADD COLUMN "preventionId" TEXT NOT NULL,
ADD COLUMN "symptomId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "DataKematian_Kelahiran_id_seq";
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "GrafikKepuasan_id_seq";
-- CreateIndex
CREATE UNIQUE INDEX "DataKematian_Kelahiran_id_key" ON "DataKematian_Kelahiran"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GrafikKepuasan_id_key" ON "GrafikKepuasan"("id");
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_introductionId_fkey" FOREIGN KEY ("introductionId") REFERENCES "Introduction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_symptomId_fkey" FOREIGN KEY ("symptomId") REFERENCES "Symptom"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_preventionId_fkey" FOREIGN KEY ("preventionId") REFERENCES "Prevention"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_firstAidId_fkey" FOREIGN KEY ("firstAidId") REFERENCES "FirstAid"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_mythVsFactId_fkey" FOREIGN KEY ("mythVsFactId") REFERENCES "MythVsFact"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_doctorSignId_fkey" FOREIGN KEY ("doctorSignId") REFERENCES "DoctorSign"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,201 @@
-- CreateEnum
CREATE TYPE "StatusLaporan" AS ENUM ('SELESAI', 'PROSES', 'GAGAL');
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
-- DropIndex
DROP INDEX "DataKematian_Kelahiran_id_key";
-- AlterTable
ALTER TABLE "GrafikKepuasan" ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
-- DropIndex
DROP INDEX "GrafikKepuasan_id_key";
-- CreateTable
CREATE TABLE "KeamananLingkungan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" 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 "KeamananLingkungan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PolsekTerdekat" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jarakKeDesa" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"jamOperasional" TEXT NOT NULL,
"embedMapUrl" TEXT NOT NULL,
"namaTempatMaps" TEXT NOT NULL,
"alamatMaps" TEXT NOT NULL,
"linkPetunjukArah" TEXT NOT NULL,
"layananPolsekId" 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 "PolsekTerdekat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LayananPolsek" (
"id" TEXT NOT NULL,
"nama" 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 "LayananPolsek_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakDaruratKeamanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"kontak" TEXT NOT NULL,
"icon" 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 "KontakDaruratKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PencegahanKriminalitas" (
"id" TEXT NOT NULL,
"programKeamananId" TEXT NOT NULL,
"tipsKeamananId" TEXT NOT NULL,
"videoKeamananId" 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 "PencegahanKriminalitas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKeamanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProgramKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TipsKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"konten" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TipsKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VideoKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT,
"videoUrl" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "VideoKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LaporanPublik" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"tanggalWaktu" TIMESTAMP(3) NOT NULL,
"status" "StatusLaporan" NOT NULL,
"kronologi" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LaporanPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PenangananLaporanPublik" (
"id" TEXT NOT NULL,
"laporanId" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
CONSTRAINT "PenangananLaporanPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pelapor" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
CONSTRAINT "Pelapor_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MenuTipsKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" 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 "MenuTipsKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ProgramKeamanan_slug_key" ON "ProgramKeamanan"("slug");
-- AddForeignKey
ALTER TABLE "KeamananLingkungan" ADD CONSTRAINT "KeamananLingkungan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PolsekTerdekat" ADD CONSTRAINT "PolsekTerdekat_layananPolsekId_fkey" FOREIGN KEY ("layananPolsekId") REFERENCES "LayananPolsek"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_programKeamananId_fkey" FOREIGN KEY ("programKeamananId") REFERENCES "ProgramKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_tipsKeamananId_fkey" FOREIGN KEY ("tipsKeamananId") REFERENCES "TipsKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_videoKeamananId_fkey" FOREIGN KEY ("videoKeamananId") REFERENCES "VideoKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PenangananLaporanPublik" ADD CONSTRAINT "PenangananLaporanPublik_laporanId_fkey" FOREIGN KEY ("laporanId") REFERENCES "LaporanPublik"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Pelapor" ADD CONSTRAINT "Pelapor_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MenuTipsKeamanan" ADD CONSTRAINT "MenuTipsKeamanan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "LayananPolsek" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;

View File

@@ -0,0 +1,75 @@
/*
Warnings:
- You are about to drop the column `deletedAt` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `deskripsi` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `imageId` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `name` on the `KontakDarurat` table. All the data in the column will be lost.
- Added the required column `nama` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "KontakDarurat" DROP CONSTRAINT "KontakDarurat_imageId_fkey";
-- AlterTable
ALTER TABLE "KontakDarurat" DROP COLUMN "deletedAt",
DROP COLUMN "deskripsi",
DROP COLUMN "imageId",
DROP COLUMN "isActive",
DROP COLUMN "name",
ADD COLUMN "icon" TEXT,
ADD COLUMN "nama" TEXT NOT NULL,
ADD COLUMN "urutan" INTEGER;
-- CreateTable
CREATE TABLE "KontakItem" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"icon" TEXT,
"kategoriId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "KontakItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PasarDesa" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"harga" INTEGER NOT NULL,
"satuan" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"rating" DOUBLE PRECISION 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,
"kategoriId" TEXT NOT NULL,
CONSTRAINT "PasarDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KategoriMakanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KategoriMakanan_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KontakDarurat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriMakanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,38 @@
/*
Warnings:
- You are about to drop the column `icon` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `nama` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `urutan` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `deletedAt` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `kontak` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- Added the required column `deskripsi` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
- Added the required column `imageId` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
- Added the required column `name` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "KontakItem" DROP CONSTRAINT "KontakItem_kategoriId_fkey";
-- AlterTable
ALTER TABLE "KontakDarurat" DROP COLUMN "icon",
DROP COLUMN "nama",
DROP COLUMN "urutan",
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deskripsi" TEXT NOT NULL,
ADD COLUMN "imageId" TEXT NOT NULL,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "name" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "KontakDaruratKeamanan" DROP COLUMN "deletedAt",
DROP COLUMN "isActive",
DROP COLUMN "kontak",
ADD COLUMN "urutan" INTEGER;
-- AddForeignKey
ALTER TABLE "KontakDarurat" ADD CONSTRAINT "KontakDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KontakDaruratKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,85 @@
/*
Warnings:
- The values [SELESAI,PROSES,GAGAL] on the enum `StatusLaporan` will be removed. If these variants are still used in the database, this will fail.
- You are about to drop the column `icon` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `urutan` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `icon` on the `KontakItem` table. All the data in the column will be lost.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "StatusLaporan_new" AS ENUM ('Selesai', 'Proses', 'Gagal');
ALTER TABLE "LaporanPublik" ALTER COLUMN "status" TYPE "StatusLaporan_new" USING ("status"::text::"StatusLaporan_new");
ALTER TYPE "StatusLaporan" RENAME TO "StatusLaporan_old";
ALTER TYPE "StatusLaporan_new" RENAME TO "StatusLaporan";
DROP TYPE "StatusLaporan_old";
COMMIT;
-- AlterTable
ALTER TABLE "KontakDaruratKeamanan" DROP COLUMN "icon",
DROP COLUMN "urutan",
ADD COLUMN "imageId" TEXT;
-- AlterTable
ALTER TABLE "KontakItem" DROP COLUMN "icon",
ADD COLUMN "imageId" TEXT;
-- CreateTable
CREATE TABLE "LowonganPekerjaan" (
"id" TEXT NOT NULL,
"posisi" TEXT NOT NULL,
"namaPerusahaan" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"tipePekerjaan" TEXT NOT NULL,
"gaji" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kualifikasi" TEXT NOT NULL,
"tanggalPosting" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LowonganPekerjaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKemiskinan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"ikonUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"statistikId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProgramKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StatistikKemiskinan" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"jumlah" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StatistikKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ProgramKemiskinan_statistikId_key" ON "ProgramKemiskinan"("statistikId");
-- CreateIndex
CREATE UNIQUE INDEX "StatistikKemiskinan_tahun_key" ON "StatistikKemiskinan"("tahun");
-- AddForeignKey
ALTER TABLE "KontakDaruratKeamanan" ADD CONSTRAINT "KontakDaruratKeamanan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramKemiskinan" ADD CONSTRAINT "ProgramKemiskinan_statistikId_fkey" FOREIGN KEY ("statistikId") REFERENCES "StatistikKemiskinan"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,59 @@
/*
Warnings:
- You are about to drop the column `alamat` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `deletedAt` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `kategoriId` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `satuan` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the `KategoriMakanan` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `alamatUsaha` to the `PasarDesa` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "PasarDesa" DROP CONSTRAINT "PasarDesa_imageId_fkey";
-- DropForeignKey
ALTER TABLE "PasarDesa" DROP CONSTRAINT "PasarDesa_kategoriId_fkey";
-- AlterTable
ALTER TABLE "PasarDesa" DROP COLUMN "alamat",
DROP COLUMN "deletedAt",
DROP COLUMN "isActive",
DROP COLUMN "kategoriId",
DROP COLUMN "satuan",
ADD COLUMN "alamatUsaha" TEXT NOT NULL,
ALTER COLUMN "imageId" DROP NOT NULL;
-- DropTable
DROP TABLE "KategoriMakanan";
-- CreateTable
CREATE TABLE "KategoriProduk" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "KategoriProduk_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ProdukToKategori" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ProdukToKategori_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ProdukToKategori_B_index" ON "_ProdukToKategori"("B");
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ProdukToKategori" ADD CONSTRAINT "_ProdukToKategori_A_fkey" FOREIGN KEY ("A") REFERENCES "KategoriProduk"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ProdukToKategori" ADD CONSTRAINT "_ProdukToKategori_B_fkey" FOREIGN KEY ("B") REFERENCES "PasarDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,41 @@
/*
Warnings:
- You are about to drop the `_ProdukToKategori` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "_ProdukToKategori" DROP CONSTRAINT "_ProdukToKategori_A_fkey";
-- DropForeignKey
ALTER TABLE "_ProdukToKategori" DROP CONSTRAINT "_ProdukToKategori_B_fkey";
-- AlterTable
ALTER TABLE "KategoriProduk" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "PasarDesa" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- DropTable
DROP TABLE "_ProdukToKategori";
-- CreateTable
CREATE TABLE "KategoriToPasar" (
"id" TEXT NOT NULL,
"kategoriId" TEXT NOT NULL,
"pasarDesaId" 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 "KategoriToPasar_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "KategoriToPasar" ADD CONSTRAINT "KategoriToPasar_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriProduk"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KategoriToPasar" ADD CONSTRAINT "KategoriToPasar_pasarDesaId_fkey" FOREIGN KEY ("pasarDesaId") REFERENCES "PasarDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `kategoriProdukId` to the `PasarDesa` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "PasarDesa" ADD COLUMN "kategoriProdukId" TEXT NOT NULL;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_kategoriProdukId_fkey" FOREIGN KEY ("kategoriProdukId") REFERENCES "KategoriProduk"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,78 @@
-- CreateTable
CREATE TABLE "posisi_organisasi" (
"id" VARCHAR(50) NOT NULL,
"nama" VARCHAR(100) NOT NULL,
"deskripsi" TEXT,
"hierarki" INTEGER NOT NULL,
CONSTRAINT "posisi_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "pegawai" (
"id" UUID NOT NULL,
"namaLengkap" VARCHAR(255) NOT NULL,
"gelarAkademik" VARCHAR(100),
"imageId" TEXT,
"tanggalMasuk" DATE,
"email" VARCHAR(255),
"telepon" VARCHAR(20),
"alamat" TEXT,
"posisiId" VARCHAR(50) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "pegawai_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "hubungan_organisasi" (
"id" UUID NOT NULL,
"atasanId" UUID NOT NULL,
"bawahanId" UUID NOT NULL,
"tipe" VARCHAR(50),
CONSTRAINT "hubungan_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "struktur_organisasi" (
"id" TEXT NOT NULL,
"posisiOrganisasiId" VARCHAR(50) NOT NULL,
"pegawaiId" UUID NOT NULL,
"hubunganOrganisasiId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "struktur_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "pegawai_email_key" ON "pegawai"("email");
-- CreateIndex
CREATE UNIQUE INDEX "hubungan_organisasi_atasanId_bawahanId_key" ON "hubungan_organisasi"("atasanId", "bawahanId");
-- AddForeignKey
ALTER TABLE "pegawai" ADD CONSTRAINT "pegawai_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "pegawai" ADD CONSTRAINT "pegawai_posisiId_fkey" FOREIGN KEY ("posisiId") REFERENCES "posisi_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "hubungan_organisasi" ADD CONSTRAINT "hubungan_organisasi_atasanId_fkey" FOREIGN KEY ("atasanId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "hubungan_organisasi" ADD CONSTRAINT "hubungan_organisasi_bawahanId_fkey" FOREIGN KEY ("bawahanId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_posisiOrganisasiId_fkey" FOREIGN KEY ("posisiOrganisasiId") REFERENCES "posisi_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_pegawaiId_fkey" FOREIGN KEY ("pegawaiId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_hubunganOrganisasiId_fkey" FOREIGN KEY ("hubunganOrganisasiId") REFERENCES "hubungan_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,77 @@
-- CreateTable
CREATE TABLE "GrafikMenganggurBerdasarkanUsia" (
"id" TEXT NOT NULL,
"usia18_25" TEXT NOT NULL,
"usia26_35" TEXT NOT NULL,
"usia36_45" TEXT NOT NULL,
"usia46_keatas" 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 "GrafikMenganggurBerdasarkanUsia_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikMenganggurBerdasarkanPendidikan" (
"id" TEXT NOT NULL,
"SD" TEXT NOT NULL,
"SMP" TEXT NOT NULL,
"SMA" TEXT NOT NULL,
"D3" TEXT NOT NULL,
"S1" 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 "GrafikMenganggurBerdasarkanPendidikan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikJumlahPendudukMiskin" (
"id" UUID NOT NULL,
"year" INTEGER NOT NULL,
"totalPoorPopulation" INTEGER 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 "GrafikJumlahPendudukMiskin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SektorUnggulanDesa" (
"id" UUID NOT NULL,
"name" VARCHAR(100) NOT NULL,
"description" TEXT,
"value" DOUBLE PRECISION,
"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 "SektorUnggulanDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DataDemografiPekerjaan" (
"id" TEXT NOT NULL,
"pekerjaan" TEXT NOT NULL,
"lakiLaki" INTEGER NOT NULL,
"perempuan" INTEGER 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 "DataDemografiPekerjaan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "GrafikJumlahPendudukMiskin_year_key" ON "GrafikJumlahPendudukMiskin"("year");
-- CreateIndex
CREATE UNIQUE INDEX "SektorUnggulanDesa_name_key" ON "SektorUnggulanDesa"("name");

View File

@@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "DetailDataPengangguran" (
"id" UUID NOT NULL,
"month" VARCHAR(20) NOT NULL,
"year" INTEGER NOT NULL,
"totalUnemployment" INTEGER NOT NULL,
"educatedUnemployment" INTEGER NOT NULL,
"uneducatedUnemployment" INTEGER NOT NULL,
"percentageChange" DOUBLE PRECISION,
"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 "DetailDataPengangguran_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "DetailDataPengangguran_month_year_key" ON "DetailDataPengangguran"("month", "year");

View File

@@ -0,0 +1,133 @@
-- CreateTable
CREATE TABLE "ApbDesa" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"pendapatanId" TEXT NOT NULL,
"belanjaId" TEXT NOT NULL,
"pembiayaanId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ApbDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pendapatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"value" INTEGER NOT NULL,
CONSTRAINT "Pendapatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Belanja" (
"id" TEXT NOT NULL,
"penyelenggaraan" INTEGER NOT NULL,
"pelaksanaanPembangunan" INTEGER NOT NULL,
"pembinaanMasyarakat" INTEGER NOT NULL,
"pemberdayaanMasyarakat" INTEGER NOT NULL,
"penanggulanganBencana" INTEGER NOT NULL,
"total" INTEGER NOT NULL,
CONSTRAINT "Belanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pembiayaan" (
"id" TEXT NOT NULL,
"silpa" INTEGER NOT NULL,
CONSTRAINT "Pembiayaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KlasifikasiBelanja" (
"id" TEXT NOT NULL,
"jenis" TEXT NOT NULL,
"persen" DOUBLE PRECISION NOT NULL,
"total" INTEGER NOT NULL,
"apbDesaId" TEXT NOT NULL,
CONSTRAINT "KlasifikasiBelanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RincianBelanja" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"klasifikasiBelanjaId" TEXT NOT NULL,
CONSTRAINT "RincianBelanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KegiatanSubak" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"apbDesaId" TEXT NOT NULL,
CONSTRAINT "KegiatanSubak_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ApbDesaToKegiatanSubak" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaToKegiatanSubak_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_BelanjaToKlasifikasiBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_BelanjaToKlasifikasiBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_KlasifikasiBelanjaToRincianBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ApbDesaToKegiatanSubak_B_index" ON "_ApbDesaToKegiatanSubak"("B");
-- CreateIndex
CREATE INDEX "_BelanjaToKlasifikasiBelanja_B_index" ON "_BelanjaToKlasifikasiBelanja"("B");
-- CreateIndex
CREATE INDEX "_KlasifikasiBelanjaToRincianBelanja_B_index" ON "_KlasifikasiBelanjaToRincianBelanja"("B");
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pendapatanId_fkey" FOREIGN KEY ("pendapatanId") REFERENCES "Pendapatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_belanjaId_fkey" FOREIGN KEY ("belanjaId") REFERENCES "Belanja"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pembiayaanId_fkey" FOREIGN KEY ("pembiayaanId") REFERENCES "Pembiayaan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey" FOREIGN KEY ("B") REFERENCES "KegiatanSubak"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "RincianBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,34 @@
/*
Warnings:
- You are about to drop the column `pelaksanaanPembangunan` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `pemberdayaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `pembinaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `penanggulanganBencana` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `penyelenggaraan` on the `Belanja` table. All the data in the column will be lost.
- Added the required column `name` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `value` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `total` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Belanja" DROP COLUMN "pelaksanaanPembangunan",
DROP COLUMN "pemberdayaanMasyarakat",
DROP COLUMN "pembinaanMasyarakat",
DROP COLUMN "penanggulanganBencana",
DROP COLUMN "penyelenggaraan",
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "name" TEXT NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
ADD COLUMN "value" INTEGER NOT NULL;
-- AlterTable
ALTER TABLE "Pendapatan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "total" INTEGER NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `total` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `total` on the `Pendapatan` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Belanja" DROP COLUMN "total";
-- AlterTable
ALTER TABLE "Pendapatan" DROP COLUMN "total";

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `silpa` on the `Pembiayaan` table. All the data in the column will be lost.
- Added the required column `name` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
- Added the required column `value` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Pembiayaan" DROP COLUMN "silpa",
ADD COLUMN "name" TEXT NOT NULL,
ADD COLUMN "value" INTEGER NOT NULL;

View File

@@ -0,0 +1,245 @@
/*
Warnings:
- You are about to drop the column `belanjaId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the column `pembiayaanId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the column `pendapatanId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the `KegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `KlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `RincianBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_ApbDesaToKegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_BelanjaToKlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_KlasifikasiBelanjaToRincianBelanja` table. If the table is not empty, all the data it contains will be lost.
- Changed the type of `tanggal` on the `DaftarInformasiPublik` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
- Added the required column `updatedAt` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_belanjaId_fkey";
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pembiayaanId_fkey";
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pendapatanId_fkey";
-- DropForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey";
-- DropForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey";
-- DropForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey";
-- DropForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey";
-- DropForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey";
-- DropForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey";
-- AlterTable
ALTER TABLE "ApbDesa" DROP COLUMN "belanjaId",
DROP COLUMN "pembiayaanId",
DROP COLUMN "pendapatanId",
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "tanggal",
ADD COLUMN "tanggal" DATE NOT NULL;
-- AlterTable
ALTER TABLE "Pembiayaan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
-- DropTable
DROP TABLE "KegiatanSubak";
-- DropTable
DROP TABLE "KlasifikasiBelanja";
-- DropTable
DROP TABLE "RincianBelanja";
-- DropTable
DROP TABLE "_ApbDesaToKegiatanSubak";
-- DropTable
DROP TABLE "_BelanjaToKlasifikasiBelanja";
-- DropTable
DROP TABLE "_KlasifikasiBelanjaToRincianBelanja";
-- CreateTable
CREATE TABLE "DesaDigital" (
"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 "DesaDigital_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKreatif" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"icon" 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 "ProgramKreatif_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KolaborasiInovasi" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"slug" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kolaborator" 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 "KolaborasiInovasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InfoTekno" (
"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 "InfoTekno_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AjukanIdeInovatif" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"namaIde" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"masalah" TEXT NOT NULL,
"benefit" 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 "AjukanIdeInovatif_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdministrasiOnline" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"jenisLayananId" 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 "AdministrasiOnline_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JenisLayanan" (
"id" TEXT NOT NULL,
"nama" 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 "JenisLayanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ApbDesaPembiayaan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaPembiayaan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_ApbDesaBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_ApbDesaPendapatan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaPendapatan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ApbDesaPembiayaan_B_index" ON "_ApbDesaPembiayaan"("B");
-- CreateIndex
CREATE INDEX "_ApbDesaBelanja_B_index" ON "_ApbDesaBelanja"("B");
-- CreateIndex
CREATE INDEX "_ApbDesaPendapatan_B_index" ON "_ApbDesaPendapatan"("B");
-- AddForeignKey
ALTER TABLE "DesaDigital" ADD CONSTRAINT "DesaDigital_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KolaborasiInovasi" ADD CONSTRAINT "KolaborasiInovasi_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InfoTekno" ADD CONSTRAINT "InfoTekno_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AdministrasiOnline" ADD CONSTRAINT "AdministrasiOnline_jenisLayananId_fkey" FOREIGN KEY ("jenisLayananId") REFERENCES "JenisLayanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pembiayaan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pendapatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -73,9 +73,19 @@ model FileStorage {
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
Pelapor Pelapor[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
}
//========================================= MENU PPID ========================================= //
@@ -136,7 +146,7 @@ model DaftarInformasiPublik {
id String @id @default(cuid())
jenisInformasi String
deskripsi String
tanggal String
tanggal DateTime @db.Date
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
@@ -471,24 +481,24 @@ model Penghargaan {
// ========================================= FASILITAS KESEHATAN ========================================= //
model FasilitasKesehatan {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
informasiUmumId String
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
layananUnggulanId String
dokterdantenagamedis DokterdanTenagaMedis @relation(fields: [dokterdanTenagaMedisId], references: [id])
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
informasiUmumId String
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
layananUnggulanId String
dokterdantenagamedis DokterdanTenagaMedis @relation(fields: [dokterdanTenagaMedisId], references: [id])
dokterdanTenagaMedisId String
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
fasilitasPendukungId String
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
prosedurPendaftaranId String
tarifdanlayanan TarifDanLayanan @relation(fields: [tarifDanLayananId], references: [id])
tarifDanLayananId String
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
fasilitasPendukungId String
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
prosedurPendaftaranId String
tarifdanlayanan TarifDanLayanan @relation(fields: [tarifDanLayananId], references: [id])
tarifDanLayananId String
}
model InformasiUmum {
@@ -558,33 +568,47 @@ model TarifDanLayanan {
// ========================================= JADWAL KEGIATAN ========================================= //
model JadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
content String
informasijadwalkegiatan InformasiJadwalKegiatan @relation(fields: [informasiJadwalKegiatanId], references: [id])
informasiJadwalKegiatanId String
deskripsijadwalkegiatan DeskripsiJadwalKegiatan @relation(fields: [deskripsiJadwalKegiatanId], references: [id])
deskripsiJadwalKegiatanId String
layananjadwalkegiatan LayananJadwalKegiatan @relation(fields: [layananJadwalKegiatanId], references: [id])
layananJadwalKegiatanId String
syaratketentuanjadwalkegiatan SyaratKetentuanJadwalKegiatan @relation(fields: [syaratKetentuanJadwalKegiatanId], references: [id])
syaratKetentuanJadwalKegiatanId String
dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id])
dokumenJadwalKegiatanId String
pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan @relation(fields: [pendaftaranJadwalKegiatanId], references: [id])
pendaftaranJadwalKegiatanId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model InformasiJadwalKegiatan {
id String @id @default(cuid())
name String
tanggal String
waktu String
lokasi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
name String
tanggal String
waktu String
lokasi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model DeskripsiJadwalKegiatan {
id String @id @default(cuid())
deskripsi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
deskripsi String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model LayananJadwalKegiatan {
@@ -594,6 +618,8 @@ model LayananJadwalKegiatan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model SyaratKetentuanJadwalKegiatan {
@@ -603,35 +629,38 @@ model SyaratKetentuanJadwalKegiatan {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model DokumenJadwalKegiatan {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
model PendaftaranJadwalKegiatan {
id String @id @default(cuid())
name String
tanggal String
namaOrangtua String
nomor String
alamat String
catatan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
name String
tanggal String
namaOrangtua String
nomor String
alamat String
catatan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
JadwalKegiatan JadwalKegiatan[]
}
// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= //
model DataKematian_Kelahiran {
id Int @id @default(autoincrement())
uuid String @default(cuid()) @unique
id String @id @default(cuid())
tahun String
kematianKasar String
kematianBayi String
@@ -644,8 +673,7 @@ model DataKematian_Kelahiran {
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id Int @id @default(autoincrement())
uuid String @default(cuid()) @unique
id String @id @default(cuid())
label String
jumlah String
createdAt DateTime @default(now())
@@ -656,56 +684,74 @@ model GrafikKepuasan {
// ========================================= ARTIKEL KESEHATAN ========================================= //
model ArtikelKesehatan {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
title String
content String
introduction Introduction @relation(fields: [introductionId], references: [id])
introductionId String
symptom Symptom @relation(fields: [symptomId], references: [id])
symptomId String
prevention Prevention @relation(fields: [preventionId], references: [id])
preventionId String
firstaid FirstAid @relation(fields: [firstAidId], references: [id])
firstAidId String
mythvsfact MythVsFact @relation(fields: [mythVsFactId], references: [id])
mythVsFactId String
doctorsign DoctorSign @relation(fields: [doctorSignId], references: [id])
doctorSignId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Introduction {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model Symptom {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model Prevention {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model FirstAid {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model MythVsFact {
id Int @id @default(autoincrement())
id String @id @default(cuid())
title String
mitos String
fakta String
@@ -713,15 +759,19 @@ model MythVsFact {
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
model DoctorSign {
id Int @id @default(autoincrement())
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ArtikelKesehatan ArtikelKesehatan[]
}
// ========================================= POSYANDU ========================================= //
@@ -819,12 +869,465 @@ model KontakDarurat {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= INFO WABAH PENYAKIT ========================================= //
model InfoWabahPenyakit {
id String @id @default(cuid())
name String
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)
}
// ========================================= MENU KEAMANAN ========================================= //
// ========================================= KEAMANAN LINGKUNGAN ========================================= //
model KeamananLingkungan {
id String @id @default(cuid())
name String @db.Text
deskripsi String @db.Text
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= POLSEK TERDEKAT ========================================= //
model PolsekTerdekat {
id String @id @default(uuid())
nama String
jarakKeDesa String
alamat String
nomorTelepon String
jamOperasional String
embedMapUrl String
namaTempatMaps String
alamatMaps String
linkPetunjukArah String
layananPolsek LayananPolsek @relation(fields: [layananPolsekId], references: [id])
layananPolsekId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model LayananPolsek {
id String @id @default(uuid())
nama String // contoh: "Pelayanan SKCK", "Laporan Kriminal"
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
PolsekTerdekat PolsekTerdekat[]
}
// ========================================= KONTAK DARURAT ========================================= //
model KontakDaruratKeamanan {
id String @id @default(uuid())
nama String // contoh: "Layanan Darurat", "Fasilitas Kesehatan"
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
kontakItems KontakItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model KontakItem {
id String @id @default(uuid())
nama String // contoh: "Polisi", "Ambulans", "Puskesmas Darmasaba"
nomorTelepon String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
kategori KontakDaruratKeamanan @relation(fields: [kategoriId], references: [id])
kategoriId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
model PencegahanKriminalitas {
id String @id @default(cuid())
programKeamanan ProgramKeamanan @relation(fields: [programKeamananId], references: [id])
programKeamananId String
tipsKeamanan TipsKeamanan @relation(fields: [tipsKeamananId], references: [id])
tipsKeamananId String
videoKeamanan VideoKeamanan @relation(fields: [videoKeamananId], references: [id])
videoKeamananId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ProgramKeamanan {
id String @id @default(cuid())
nama String // contoh: "Ronda Malam"
deskripsi String? // jika mau tambahkan info detail
slug String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
}
model TipsKeamanan {
id String @id @default(cuid())
judul String
konten String
slug String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
}
model VideoKeamanan {
id String @id @default(cuid())
judul String
deskripsi String?
videoUrl String // link youtube atau embed url
slug String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
}
// ========================================= LAPORAN PUBLIK ========================================= //
model LaporanPublik {
id String @id @default(cuid())
judul String
lokasi String
tanggalWaktu DateTime
status StatusLaporan
penanganan PenangananLaporanPublik[]
kronologi String? // Optional, bisa diisi detail kronologi
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model PenangananLaporanPublik {
id String @id @default(cuid())
laporanId String
deskripsi String
laporan LaporanPublik @relation(fields: [laporanId], references: [id], onDelete: Cascade)
}
enum StatusLaporan {
Selesai
Proses
Gagal
}
model Pelapor {
id String @id @default(cuid())
nama String
alamat String
nomorTelepon String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
}
// ========================================= TIPS KEAMANAN ========================================= //
model MenuTipsKeamanan {
id String @id @default(cuid())
judul 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)
}
// ========================================= MENU EKONOMI ========================================= //
// ========================================= PASAR DESA ========================================= //
model PasarDesa {
id String @id @default(uuid())
nama String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
harga Int
rating Float
alamatUsaha String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
kategoriProduk KategoriProduk @relation(fields: [kategoriProdukId], references: [id])
kategoriProdukId String
KategoriToPasar KategoriToPasar[]
}
model KategoriProduk {
id String @id @default(uuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
KategoriToPasar KategoriToPasar[]
PasarDesa PasarDesa[]
}
model KategoriToPasar {
id String @id @default(uuid())
kategori KategoriProduk @relation(fields: [kategoriId], references: [id])
kategoriId String
pasarDesa PasarDesa @relation(fields: [pasarDesaId], references: [id])
pasarDesaId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= LOWONGAN KERJA LOKAL ========================================= //
model LowonganPekerjaan {
id String @id @default(uuid())
posisi String
namaPerusahaan String
lokasi String
tipePekerjaan String
gaji String
deskripsi String
kualifikasi String
tanggalPosting DateTime @default(now())
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
}
// ========================================= STRUKTUR ORGANISASI ========================================= //
model PosisiOrganisasi {
id String @id @default(uuid()) @db.VarChar(50)
nama String @db.VarChar(100)
deskripsi String? @db.Text
hierarki Int
pegawai Pegawai[]
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
@@map("posisi_organisasi")
}
model Pegawai {
id String @id @default(uuid()) @db.Uuid
namaLengkap String @db.VarChar(255)
gelarAkademik String? @db.VarChar(100)
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
tanggalMasuk DateTime? @db.Date
email String? @unique @db.VarChar(255)
telepon String? @db.VarChar(20)
alamat String? @db.Text
posisiId String @db.VarChar(50)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posisi PosisiOrganisasi @relation(fields: [posisiId], references: [id])
sebagaiAtasan HubunganOrganisasi[] @relation("AtasanToBawahan")
sebagaiBawahan HubunganOrganisasi[] @relation("BawahanToAtasan")
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
@@map("pegawai")
}
model HubunganOrganisasi {
id String @id @default(uuid()) @db.Uuid
atasanId String @db.Uuid
bawahanId String @db.Uuid
tipe String? @db.VarChar(50)
atasan Pegawai @relation("AtasanToBawahan", fields: [atasanId], references: [id])
bawahan Pegawai @relation("BawahanToAtasan", fields: [bawahanId], references: [id])
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
@@unique([atasanId, bawahanId])
@@map("hubungan_organisasi")
}
model StrukturOrganisasi {
id String @id @default(uuid())
posisiOrganisasiId String @db.VarChar(50)
pegawaiId String @db.Uuid
hubunganOrganisasiId String @db.Uuid
posisiOrganisasi PosisiOrganisasi @relation(fields: [posisiOrganisasiId], references: [id])
pegawai Pegawai @relation(fields: [pegawaiId], references: [id])
hubunganOrganisasi HubunganOrganisasi @relation(fields: [hubunganOrganisasiId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
@@map("struktur_organisasi")
}
// ========================================= PROGRAM KEMISKINAN ========================================= //
model ProgramKemiskinan {
id String @id @default(uuid())
nama String
deskripsi String
ikonUrl String?
isActive Boolean @default(true)
statistikId String? @unique
statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model StatistikKemiskinan {
id String @id @default(uuid())
tahun Int @unique
jumlah Int
programKemiskinan ProgramKemiskinan?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ========================================= JUMLAH PENDUDUK USIA KERJA YANG MENGANGGUR ========================================= //
model GrafikMenganggurBerdasarkanUsia {
id String @id @default(cuid())
usia18_25 String
usia26_35 String
usia36_45 String
usia46_keatas String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model GrafikMenganggurBerdasarkanPendidikan {
id String @id @default(cuid())
SD String
SMP String
SMA String
D3 String
S1 String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= JUMLAH PENDUDUK MISKIN ========================================= //
model GrafikJumlahPendudukMiskin {
id String @id @default(uuid()) @db.Uuid // Menggunakan UUID sebagai primary key
year Int @unique // Tahun data (e.g., 2024, 2025)
totalPoorPopulation Int // Jumlah penduduk miskin (e.g., 4800000)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= SEKTOR UNGGULAN DESA ========================================= //
model SektorUnggulanDesa {
id String @id @default(uuid()) @db.Uuid // Menggunakan UUID sebagai primary key
name String @unique @db.VarChar(100) // Nama sektor (e.g., "Sektor Pertanian", "Sektor Peternakan")
description String? @db.Text // Deskripsi lengkap tentang sektor
value Float? // Nilai kuantitatif sektor (misalnya, kontribusi PDB, jumlah produksi, dll.)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= DEMOGRAFI PEKERJAAN ========================================= //
model DataDemografiPekerjaan {
id String @id @default(cuid())
pekerjaan String
lakiLaki Int
perempuan Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= JUMLAH PENGANGGURAN ========================================= //
model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20)
year Int
totalUnemployment Int
educatedUnemployment Int
uneducatedUnemployment Int
percentageChange Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
@@unique([month, year])
}
// ========================================= PADESA PENDAPATAN ASLI DESA ========================================= //
model ApbDesa {
id String @id @default(uuid())
tahun Int
pembiayaan Pembiayaan[] @relation("ApbDesaPembiayaan")
belanja Belanja[] @relation("ApbDesaBelanja")
pendapatan Pendapatan[] @relation("ApbDesaPendapatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Pendapatan {
id String @id @default(uuid())
name String
value Int
ApbDesa ApbDesa[] @relation("ApbDesaPendapatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Belanja {
id String @id @default(uuid())
name String
value Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ApbDesa ApbDesa[] @relation("ApbDesaBelanja")
}
model Pembiayaan {
id String @id @default(uuid())
name String
value Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan")
}
// ========================================= INOVASI ========================================= //
// ========================================= DESA DIGITAL / SMART VILLAGE ========================================= //
model DesaDigital {
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
@@ -832,3 +1335,125 @@ model InfoWabahPenyakit {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PROGRAM KREATIF ========================================= //
model ProgramKreatif {
id String @id @default(cuid())
name String
slug String @db.Text //deskripsi singkat
deskripsi String @db.Text //deskripsi panjang
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= KOLABORASI INOVASI ========================================= //
model KolaborasiInovasi {
id String @id @default(cuid())
name String
tahun Int
slug String @db.Text //deskripsi singkat
deskripsi String @db.Text //deskripsi panjang
kolaborator 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 TEKHNOLOGI TEPAT GUNA ========================================= //
model InfoTekno {
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= AJUKAN IDE INOVATIF ========================================= //
model AjukanIdeInovatif {
id String @id @default(cuid())
name String
alamat String
namaIde String
deskripsi String @db.Text
masalah String @db.Text
benefit String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= LAYANAN ONLINE DESA ========================================= //
model AdministrasiOnline {
id String @id @default(cuid())
name String
alamat String
nomorTelepon String
jenisLayanan JenisLayanan @relation(fields: [jenisLayananId], references: [id])
jenisLayananId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisLayanan {
id String @id @default(uuid())
nama String
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
AdministrasiOnline AdministrasiOnline[]
}
model PengaduanMasyarakat {
id String @id @default(cuid())
name String
email String
nomorTelepon String
nik String
judulPengaduan String
lokasiKejadian String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
deskripsiPengaduan String @db.Text
jenisPengaduan JenisPengaduan @relation(fields: [jenisPengaduanId], references: [id])
jenisPengaduanId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisPengaduan {
id String @id @default(uuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PengaduanMasyarakat PengaduanMasyarakat[]
}
// ========================================= PENGELOLAAN SAMPAH ========================================= //
model PengelolaanSampah {
id String @id @default(cuid())
name String
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -17,6 +17,11 @@ 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";
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
import detailDataPengangguran from './data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json';
(async () => {
for (const l of layanan) {
@@ -340,6 +345,116 @@ import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
});
}
console.log("dasar hukum PPID success ...");
for (const k of kategoriProduk) {
await prisma.kategoriProduk.upsert({
where: {
id: k.id,
},
update: {
nama: k.nama,
},
create: {
id: k.id,
nama: k.nama,
},
});
}
console.log("kategori produk success ...");
for (const p of posisiOrganisasi) {
await prisma.posisiOrganisasi.upsert({
where: {
id: p.id,
},
update: {
nama: p.nama,
deskripsi: p.deskripsi,
hierarki: p.hierarki,
},
create: {
id: p.id,
nama: p.nama,
deskripsi: p.deskripsi,
hierarki: p.hierarki,
},
});
}
console.log("posisi organisasi success ...");
for (const p of pegawai) {
await prisma.pegawai.upsert({
where: {
id: p.id,
},
update: {
namaLengkap: p.namaLengkap,
gelarAkademik: p.gelarAkademik,
tanggalMasuk: new Date(p.tanggalMasuk),
email: p.email,
telepon: p.telepon,
alamat: p.alamat,
posisiId: p.posisiId,
isActive: p.isActive,
},
create: {
id: p.id,
namaLengkap: p.namaLengkap,
gelarAkademik: p.gelarAkademik,
tanggalMasuk: new Date(p.tanggalMasuk),
email: p.email,
telepon: p.telepon,
alamat: p.alamat,
posisiId: p.posisiId,
isActive: p.isActive,
},
});
}
console.log("pegawai success ...");
for (const p of hubunganOrganisasi) {
await prisma.hubunganOrganisasi.upsert({
where: {
atasanId_bawahanId: {
atasanId: p.atasanId,
bawahanId: p.bawahanId,
},
},
update: {
tipe: p.tipe,
},
create: {
atasanId: p.atasanId,
bawahanId: p.bawahanId,
tipe: p.tipe,
},
});
}
console.log("hubungan organisasi success ...");
for (const d of detailDataPengangguran) {
await prisma.detailDataPengangguran.upsert({
where: {
month_year: { month: d.month, year: d.year },
},
update: {
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
create: {
month: d.month,
year: d.year,
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
});
}
console.log("📊 detailDataPengangguran success ...");
})()
.then(() => prisma.$disconnect())
.catch((e) => {

BIN
public/pa-desa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -1,22 +1,37 @@
import React from 'react';
import { Grid, GridCol, Paper, TextInput, Title } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react'; // Sesuaikan jika kamu pakai icon lain
import { IconSearch } from '@tabler/icons-react';
import colors from '@/con/colors';
type HeaderSearchProps = {
title: string;
placeholder?: string;
searchIcon?: React.ReactNode;
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }: { title: string, placeholder?: string, searchIcon?: React.ReactNode }) => {
const HeaderSearch = ({
title = "",
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />,
value,
onChange,
}: HeaderSearchProps) => {
return (
<Grid>
<Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>{title}</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<Paper radius="lg" bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
value={value}
onChange={onChange}
/>
</Paper>
</GridCol>
@@ -24,4 +39,4 @@ const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <Ico
);
};
export default HeaderSearch;
export default HeaderSearch;

View File

@@ -5,31 +5,50 @@ import { IconCircleDashedPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
const JudulListTab = ({ title = "", href = "#", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }) => {
type JudulListTabProps = {
title: string;
href: string;
placeholder: string;
searchIcon: React.ReactNode;
value?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const JudulListTab = ({
title = "",
href = "#",
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />,
value,
onChange
}: JudulListTabProps) => {
const router = useRouter();
const handleNavigate = () => {
router.push(href);
};
return (
<Grid mb={10}>
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{base: "md", md: "xl"}} fw={"bold"}>{title}</Text>
<Text fz={{ base: "md", md: "xl" }} fw={"bold"}>{title}</Text>
</GridCol>
<GridCol span={{ base: 9, md: 3}} ta="right">
<GridCol span={{ base: 9, md: 3 }} ta="right">
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
value={value}
onChange={onChange}
/>
</Paper>
</GridCol>
<GridCol span={{ base: 3, md: 1}} ta="right">
<GridCol span={{ base: 3, md: 1 }} ta="right">
<Button onClick={handleNavigate} bg={colors['blue-button']}>
<IconCircleDashedPlus size={25} />
</Button>

View File

@@ -80,13 +80,33 @@ const berita = proxy({
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.berita["find-many"].get();
if (res.status === 200) {
berita.findMany.data = (res.data?.data ) ?? [];
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
berita.findMany.loading = true;
berita.findMany.page = page;
try {
const res = await ApiFetch.api.desa.berita["find-many"].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
berita.findMany.data = res.data.data ?? [];
berita.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
} finally {
berita.findMany.loading = false;
}
},
},
findUnique: {
data: null as
| Prisma.BeritaGetPayload<{
@@ -243,6 +263,59 @@ const berita = proxy({
berita.edit.form = { ...defaultForm };
},
},
findFirst: {
data: null as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}> | null,
loading: false,
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.desa.berita["find-first"].get();
if (res.status === 200 && res.data?.success) {
// Add type assertion to ensure type safety
berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}> | null;
}
} catch (err) {
console.error("Gagal fetch berita terbaru:", err);
} finally {
this.loading = false;
}
},
},
findRecent: {
data: [] as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}>[],
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.desa.berita["find-recent"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data ?? [];
}
} catch (error) {
console.error("Gagal fetch berita recent:", error);
} finally {
this.loading = false;
}
},
}
});

View File

@@ -68,11 +68,11 @@ const pengumuman = proxy({
},
findMany: {
data: null as
| Prisma.PengumumanGetPayload<{
include: {
| Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
}
}>[]
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
@@ -82,30 +82,28 @@ const pengumuman = proxy({
}
},
},
// findUnique: {
// data: null as
// | Prisma.PengumumanGetPayload<{
// include: {
// CategoryPengumuman: true;
// }
// }>
// | null,
// async load(id: string) {
// try {
// const res = await fetch(`/api/desa/pengumuman/${id}`);
// if (res.ok) {
// const data = await res.json();
// pengumuman.findUnique.data = data.data ?? null;
// } else {
// console.error('Failed to fetch pengumuman:', res.statusText);
// pengumuman.findUnique.data = null;
// }
// } catch (error) {
// console.error('Error fetching pengumuman:', error);
// pengumuman.findUnique.data = null;
// }
// },
// },
findUnique: {
data: null as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/pengumuman/${id}`);
if (res.ok) {
const data = await res.json();
pengumuman.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch pengumuman:", res.statusText);
pengumuman.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching pengumuman:", error);
pengumuman.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
@@ -237,6 +235,55 @@ const pengumuman = proxy({
}
},
},
findFirst: {
data: null as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null,
loading: false,
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.desa.pengumuman["find-first"].get();
if (res.status === 200 && res.data?.success) {
// Add type assertion to ensure type safety
pengumuman.findFirst.data = res.data
.data as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null;
}
} catch (err) {
console.error("Gagal fetch pengumuman terbaru:", err);
} finally {
this.loading = false;
}
},
},
findRecent: {
data: [] as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}>[],
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.desa.pengumuman["find-recent"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data ?? [];
}
} catch (error) {
console.error("Gagal fetch pengumuman recent:", error);
} finally {
this.loading = false;
}
},
},
});
const stateDesaPengumuman = proxy({

View File

@@ -0,0 +1,887 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateApbDesa = z.object({
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"),
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"),
});
const ApbDesaDefaultForm = {
tahun: 0,
pendapatanIds: [] as string[],
belanjaIds: [] as string[],
pembiayaanIds: [] as string[],
};
const ApbDesa = proxy({
create: {
form: { ...ApbDesaDefaultForm },
loading: false,
async submit() {
const cek = templateApbDesa.safeParse(this.form);
if (!cek.success) {
const err = cek.error.issues.map((v) => v.message).join("\n");
return toast.error(err);
}
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan APB Desa");
ApbDesa.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan APB Desa");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan APB Desa");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...ApbDesaDefaultForm };
},
},
findMany: {
data: null as
| Prisma.ApbDesaGetPayload<{
include: {
pendapatan: true;
belanja: true;
pembiayaan: true;
};
}>[]
| null,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil APB Desa");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil APB Desa");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...ApbDesaDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error("Gagal mengambil APB Desa");
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || "Gagal memuat APB Desa");
}
const data = result.data;
this.id = id;
this.form = {
tahun: data.tahun || 0,
pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [],
belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
};
return data;
} catch (error) {
console.error("Error loading APB Desa:", error);
toast.error("Gagal memuat data APB Desa");
return null;
}
},
async update() {
try {
this.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
}
);
if (!response.ok) {
throw new Error("Gagal memperbarui APB Desa");
}
const data = await response.json();
toast.success("APB Desa berhasil diperbarui");
return data;
} catch (error) {
console.error("Error updating APB Desa:", error);
toast.error("Gagal memperbarui APB Desa");
throw error;
} finally {
this.loading = false;
}
},
reset() {
this.id = "";
this.form = { ...ApbDesaDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
try {
this.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/del/${id}`,
{
method: "DELETE",
}
);
if (!response.ok) {
throw new Error("Gagal menghapus APB Desa");
}
toast.success("APB Desa berhasil dihapus");
return true;
} catch (error) {
console.error("Error deleting APB Desa:", error);
toast.error("Gagal menghapus APB Desa");
return false;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ApbDesaGetPayload<{
include: { pendapatan: true; belanja: true; pembiayaan: true };
}> | null,
async load(id: string) {
try {
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`
);
if (!response.ok) {
throw new Error("Gagal mengambil detail APB Desa");
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || "Gagal mengambil data");
}
this.data = result.data; // ✅ fix utama di sini
return result.data;
} catch (error) {
console.error("Error loading APB Desa detail:", error);
toast.error("Gagal memuat detail APB Desa");
return null;
}
},
},
});
const templatePendapatan = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const PendapatanDefaultForm = {
name: "",
value: 0,
};
const pendapatan = proxy({
create: {
form: { ...PendapatanDefaultForm },
loading: false,
async submit() {
const cek = templatePendapatan.safeParse(this.form);
if (!cek.success) {
const err = cek.error.issues.map((v) => v.message).join("\n");
return toast.error(err);
}
try {
this.loading = true;
const res =
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Pendapatan Asli");
pendapatan.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Pendapatan Asli");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Pendapatan Asli");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...PendapatanDefaultForm };
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
pendapatan.findMany.loading = true; // Use the full path to access the property
pendapatan.findMany.page = page;
try {
const res =
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pendapatan.findMany.data = res.data.data || [];
pendapatan.findMany.total = res.data.total || 0;
pendapatan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pendapatan:", res.data?.message);
pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pendapatan:", error);
pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1;
} finally {
pendapatan.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...PendapatanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pendapatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePendapatan.safeParse(pendapatan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pendapatan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update pendapatan");
await pendapatan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pendapatan");
}
} catch (error) {
console.error("Error updating pendapatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pendapatan"
);
return false;
} finally {
pendapatan.update.loading = false;
}
},
reset() {
pendapatan.update.id = "";
pendapatan.update.form = { ...PendapatanDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pendapatan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pendapatan Asli berhasil dihapus");
await pendapatan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Pendapatan Asli");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Pendapatan Asli");
} finally {
pendapatan.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PendapatanGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`
);
if (res.ok) {
const json = await res.json();
pendapatan.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
pendapatan.findUnique.data = null;
}
},
},
});
const templateBelanja = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const BelanjaDefaultForm = {
name: "",
value: 0,
};
const belanja = proxy({
create: {
form: { ...BelanjaDefaultForm },
loading: false,
async submit() {
const cek = templateBelanja.safeParse(this.form);
if (!cek.success) {
const err = cek.error.issues.map((v) => v.message).join("\n");
return toast.error(err);
}
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Belanja");
belanja.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Belanja");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Belanja");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...BelanjaDefaultForm };
},
},
findMany: {
data: [] as Array<{
id: string;
name: string;
value: number;
}>,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil Belanja");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil Belanja");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...BelanjaDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading belanja:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateBelanja.safeParse(belanja.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
belanja.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update belanja");
await belanja.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate belanja");
}
} catch (error) {
console.error("Error updating belanja:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate belanja"
);
return false;
} finally {
belanja.update.loading = false;
}
},
reset() {
belanja.update.id = "";
belanja.update.form = { ...BelanjaDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
belanja.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/belanja/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Belanja berhasil dihapus");
await belanja.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Belanja");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Belanja");
} finally {
belanja.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.BelanjaGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`);
if (res.ok) {
const json = await res.json();
belanja.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
belanja.findUnique.data = null;
}
},
},
});
const templatePembiayaan = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const PembiayaanDefaultForm = {
name: "",
value: 0,
};
const pembiayaan = proxy({
create: {
form: { ...PembiayaanDefaultForm },
loading: false,
async submit() {
const cek = templatePembiayaan.safeParse(this.form);
if (!cek.success) {
const err = cek.error.issues.map((v) => v.message).join("\n");
return toast.error(err);
}
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Pembiayaan");
pembiayaan.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Pembiayaan");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Pembiayaan");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...PembiayaanDefaultForm };
},
},
findMany: {
data: [] as Array<{
id: string;
name: string;
value: number;
}>,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil Pembiayaan");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil Pembiayaan");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...PembiayaanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pembiayaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePembiayaan.safeParse(pembiayaan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pembiayaan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update pembiayaan");
await pembiayaan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pembiayaan");
}
} catch (error) {
console.error("Error updating pembiayaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pembiayaan"
);
return false;
} finally {
pembiayaan.update.loading = false;
}
},
reset() {
pembiayaan.update.id = "";
pembiayaan.update.form = { ...PembiayaanDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pembiayaan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/pembiayaan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pembiayaan berhasil dihapus");
await pembiayaan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Pembiayaan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Pembiayaan");
} finally {
pembiayaan.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PembiayaanGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`
);
if (res.ok) {
const json = await res.json();
pembiayaan.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
pembiayaan.findUnique.data = null;
}
},
},
});
const PendapatanAsliDesa = proxy({
ApbDesa,
belanja,
pembiayaan,
pendapatan,
});
export default PendapatanAsliDesa;

View File

@@ -0,0 +1,197 @@
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 templateDemografiPekerjaan = z.object({
pekerjaan: z.string().min(1, "Pekerjaan harus diisi"),
lakiLaki: z.number().min(1, "Laki - Laki harus diisi"),
perempuan: z.number().min(1, "Perempuan harus diisi"),
});
type DemografiPekerjaan = Prisma.DataDemografiPekerjaanGetPayload<{
select: {
pekerjaan: true;
lakiLaki: true;
perempuan: true;
};
}>;
const defaultForm: DemografiPekerjaan = {
pekerjaan: "",
lakiLaki: 0,
perempuan: 0,
};
const demografiPekerjaan = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateDemografiPekerjaan.safeParse(
demografiPekerjaan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
demografiPekerjaan.create.loading = true;
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
"create"
].post(demografiPekerjaan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
demografiPekerjaan.create.form = { ...defaultForm };
demografiPekerjaan.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
demografiPekerjaan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DataDemografiPekerjaanGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
"find-many"
].get();
if (res.status === 200) {
demografiPekerjaan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DataDemografiPekerjaanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/demografipekerjaan/${id}`);
if (res.ok) {
const data = await res.json();
demografiPekerjaan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch demografiPekerjaan:", res.statusText);
demografiPekerjaan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching demografiPekerjaan:", error);
demografiPekerjaan.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
pekerjaan: this.form.pekerjaan,
lakiLaki: this.form.lakiLaki,
perempuan: this.form.perempuan,
};
const cek = templateDemografiPekerjaan.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/ekonomi/demografipekerjaan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await demografiPekerjaan.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data demografi pekerjaan");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
demografiPekerjaan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/demografipekerjaan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Demografi pekerjaan berhasil dihapus"
);
await demografiPekerjaan.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus demografi pekerjaan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus persentase kelahiran");
} finally {
demografiPekerjaan.delete.loading = false;
}
},
},
});
export default demografiPekerjaan

View File

@@ -0,0 +1,180 @@
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 templateJumlahPendudukMiskin = z.object({
year: z.number().min(1, "Data tahun harus diisi"),
totalPoorPopulation: z.number().min(1, "Data total penduduk miskin harus diisi"),
});
type JumlahPendudukMiskin = Prisma.GrafikJumlahPendudukMiskinGetPayload<{
select: {
id: true;
year: true;
totalPoorPopulation: true;
};
}>;
const defaultForm: Omit<JumlahPendudukMiskin, 'id'> & { id?: string } = {
year: 0,
totalPoorPopulation: 0,
};
const jumlahPendudukMiskin = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateJumlahPendudukMiskin.safeParse(
jumlahPendudukMiskin.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jumlahPendudukMiskin.create.loading = true;
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
"create"
].post(jumlahPendudukMiskin.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
jumlahPendudukMiskin.create.form = {
year: 0,
totalPoorPopulation: 0,
};
jumlahPendudukMiskin.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
jumlahPendudukMiskin.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikJumlahPendudukMiskinGetPayload<{
select: { id: true; year: true; totalPoorPopulation: true; };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
"find-many"
].get();
if (res.status === 200) {
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
select: { id: true; year: true; totalPoorPopulation: true; };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpendudukmiskin/${id}`
);
if (res.ok) {
const data = await res.json();
jumlahPendudukMiskin.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jumlahPendudukMiskin.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik jumlah penduduk miskin:", error);
jumlahPendudukMiskin.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultForm},
loading: false,
async byId() {
// Method implementation if needed
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateJumlahPendudukMiskin.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => (v.path as string[]).join(".")).join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(
`/api/ekonomi/jumlahpendudukmiskin/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await jumlahPendudukMiskin.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data grafik jumlah penduduk miskin:", error);
toast.error("Gagal update data grafik jumlah penduduk miskin");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jumlahPendudukMiskin.delete.loading = true;
const response = await fetch(`/api/ekonomi/jumlahpendudukmiskin/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik jumlah penduduk miskin berhasil dihapus");
await jumlahPendudukMiskin.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik jumlah penduduk miskin");
}
} catch (error) {
console.error("Gagal delete grafik jumlah penduduk miskin:", error);
toast.error("Terjadi kesalahan saat menghapus grafik jumlah penduduk miskin");
} finally {
jumlahPendudukMiskin.delete.loading = false;
}
},
}
})
export default jumlahPendudukMiskin

View File

@@ -0,0 +1,243 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateJumlahPengngguran = z.object({
month: z.string().min(1, "Bulan harus diisi"),
year: z.number().min(1, "Tahun harus diisi"),
totalUnemployment: z.number().min(1, "Total pengangguran harus diisi"),
educatedUnemployment: z
.number()
.min(1, "Pengangguran pendidikan harus diisi"),
uneducatedUnemployment: z
.number()
.min(1, "Pengangguran tidak pendidikan harus diisi"),
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"),
});
type JumlahPengangguran = {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange: number;
};
const jumlahPengangguranForm: JumlahPengangguran = {
month: "",
year: 0,
totalUnemployment: 0,
educatedUnemployment: 0,
uneducatedUnemployment: 0,
percentageChange: 0,
};
const jumlahPengangguran = proxy({
findByMonthYear: {
data: null as any,
loading: false,
load: async ({ month, year }: { month: string; year: number }) => {
jumlahPengangguran.findByMonthYear.loading = true;
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/month/${month}/year/${year}`
);
const json = await res.json();
jumlahPengangguran.findByMonthYear.data = json.data;
return json.data;
} catch (err) {
console.error("Gagal ambil data bulan/tahun:", err);
} finally {
jumlahPengangguran.findByMonthYear.loading = false;
}
},
},
create: {
form: jumlahPengangguranForm,
loading: false,
async create() {
const cek = templateJumlahPengngguran.safeParse(
jumlahPengangguran.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
jumlahPengangguran.create.loading = true;
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"create"
].post(jumlahPengangguran.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
jumlahPengangguran.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
jumlahPengangguran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"find-many"
].get();
if (res.status === 200) {
jumlahPengangguran.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/${id}`
);
if (res.ok) {
const data = await res.json();
jumlahPengangguran.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch jumlahPengangguran:", res.statusText);
jumlahPengangguran.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching jumlahPengangguran:", error);
jumlahPengangguran.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...jumlahPengangguranForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
month: this.form.month,
year: this.form.year,
totalUnemployment: this.form.totalUnemployment,
educatedUnemployment: this.form.educatedUnemployment,
uneducatedUnemployment: this.form.uneducatedUnemployment,
percentageChange: this.form.percentageChange,
};
const cek = templateJumlahPengngguran.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/${id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
}
);
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await jumlahPengangguran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data jumlah pengangguran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jumlahPengangguran.delete.loading = true;
const response = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Jumlah pengangguran berhasil dihapus"
);
await jumlahPengangguran.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus jumlah pengangguran");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jumlah pengangguran");
} finally {
jumlahPengangguran.delete.loading = false;
}
},
},
});
const jumlahPengangguranState = proxy({
jumlahPengangguran,
});
export default jumlahPengangguranState;

View File

@@ -0,0 +1,225 @@
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({
posisi: z.string(),
namaPerusahaan: z.string(),
lokasi: z.string(),
tipePekerjaan: z.string(),
gaji: z.string(),
deskripsi: z.string(),
kualifikasi: z.string(),
});
const defaultForm = {
posisi: "",
namaPerusahaan: "",
lokasi: "",
tipePekerjaan: "",
gaji: "",
deskripsi: "",
kualifikasi: "",
};
const lowonganKerjaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(lowonganKerjaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
lowonganKerjaState.create.loading = true;
const res = await ApiFetch.api.ekonomi.lowongankerja["create"].post(
lowonganKerjaState.create.form
);
if (res.status === 200) {
lowonganKerjaState.create.loading = false;
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
lowonganKerjaState.create.loading = false;
}
},
resetForm() {
lowonganKerjaState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
if (res.status === 200) {
lowonganKerjaState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/lowongankerja/${id}`);
if (res.ok) {
const data = await res.json();
lowonganKerjaState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
lowonganKerjaState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
lowonganKerjaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
lowonganKerjaState.delete.loading = true;
const response = await fetch(`/api/ekonomi/lowongankerja/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Lowongan kerja berhasil dihapus");
await lowonganKerjaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus lowongan kerja");
}
} catch (error) {
console.error("Error deleting data:", error);
toast.error("Terjadi kesalahan saat menghapus lowongan kerja");
} finally {
lowonganKerjaState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/lowongankerja/${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 = {
posisi: data.posisi,
namaPerusahaan: data.namaPerusahaan,
lokasi: data.lokasi,
tipePekerjaan: data.tipePekerjaan,
gaji: data.gaji,
deskripsi: data.deskripsi,
kualifikasi: data.kualifikasi,
};
return data;
} else {
throw new Error(
result?.message || "Gagal memuat data lowongan kerja"
);
}
} catch (error) {
console.error("Error fetching data:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(lowonganKerjaState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
lowonganKerjaState.update.loading = true;
const response = await fetch(`/api/ekonomi/lowongankerja/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
posisi: this.form.posisi,
namaPerusahaan: this.form.namaPerusahaan,
lokasi: this.form.lokasi,
tipePekerjaan: this.form.tipePekerjaan,
gaji: this.form.gaji,
deskripsi: this.form.deskripsi,
kualifikasi: this.form.kualifikasi,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update lowongan kerja");
await lowonganKerjaState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update lowongan kerja");
}
} catch (error) {
console.error("Error updating data:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate lowongan kerja");
return false;
} finally {
lowonganKerjaState.update.loading = false;
}
},
reset() {
lowonganKerjaState.update.id = "";
lowonganKerjaState.update.form = { ...defaultForm };
},
},
});
export default lowonganKerjaState;

View File

@@ -0,0 +1,468 @@
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 templatePasarDesaForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
harga: z.number().min(1, "Harga minimal 1"),
alamatUsaha: z.string().min(1, "Alamat minimal 1 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
rating: z.number().min(1, "Rating minimal 1"),
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
});
const defaultPasarDesaForm = {
nama: "",
harga: 0,
alamatUsaha: "",
imageId: "",
rating: 0,
kategoriId: [] as string[],
};
const pasarDesa = proxy({
create: {
form: { ...defaultPasarDesaForm },
loading: false,
async create() {
const cek = templatePasarDesaForm.safeParse(pasarDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pasarDesa.create.loading = true;
const res = await ApiFetch.api.ekonomi.pasardesa["create"].post(
pasarDesa.create.form
);
if (res.status === 200) {
pasarDesa.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
pasarDesa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
}>
> | null,
async load() {
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
if (res.status === 200) {
pasarDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/pasardesa/${id}`);
if (res.ok) {
const data = await res.json();
pasarDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pasarDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pasarDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pasarDesa.delete.loading = true;
const response = await fetch(`/api/ekonomi/pasardesa/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pasar desa berhasil dihapus");
await pasarDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pasar desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pasar desa");
} finally {
pasarDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultPasarDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pasardesa/${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 = {
nama: data.nama,
harga: data.harga,
alamatUsaha: data.alamatUsaha,
imageId: data.imageId,
rating: data.rating,
kategoriId: data.kategoriId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pasar desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePasarDesaForm.safeParse(pasarDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pasarDesa.edit.loading = true;
const response = await fetch(`/api/ekonomi/pasardesa/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
harga: this.form.harga,
alamatUsaha: this.form.alamatUsaha,
imageId: this.form.imageId,
rating: this.form.rating,
kategoriId: this.form.kategoriId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update pasar desa");
await pasarDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pasar desa");
}
} catch (error) {
console.error("Error updating pasar desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pasar desa"
);
return false;
} finally {
pasarDesa.edit.loading = false;
}
},
reset() {
pasarDesa.edit.id = "";
pasarDesa.edit.form = { ...defaultPasarDesaForm };
},
},
});
// ========================================= KATEGORI PRODUK ========================================= //
const kategoriProdukForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const kategoriProdukDefaultForm = {
nama: "",
};
const kategoriProduk = proxy({
create: {
form: { ...kategoriProdukDefaultForm },
loading: false,
async create() {
const cek = kategoriProdukForm.safeParse(kategoriProduk.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriProduk.create.loading = true;
const res = await ApiFetch.api.ekonomi.kategoriproduk["create"].post(
kategoriProduk.create.form
);
if (res.status === 200) {
kategoriProduk.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
kategoriProduk.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
kategoriProduk.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriProdukGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/kategoriproduk/${id}`);
if (res.ok) {
const data = await res.json();
kategoriProduk.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriProduk.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriProduk.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriProduk.delete.loading = true;
const response = await fetch(`/api/ekonomi/kategoriproduk/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori produk berhasil dihapus");
await kategoriProduk.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori produk");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori produk");
} finally {
kategoriProduk.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...kategoriProdukDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/kategoriproduk/${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 = {
nama: data.nama,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori produk:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriProdukForm.safeParse(kategoriProduk.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriProduk.edit.loading = true;
const response = await fetch(
`/api/ekonomi/kategoriproduk/${kategoriProduk.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: kategoriProduk.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate kategori produk (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui kategori produk"
);
await kategoriProduk.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate kategori produk"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating kategori produk:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori produk"
);
return false;
} finally {
kategoriProduk.edit.loading = false;
}
},
reset() {
kategoriProduk.edit.id = "";
kategoriProduk.edit.form = { ...kategoriProdukDefaultForm };
},
},
});
const pasarDesaState = proxy({
pasarDesa,
kategoriProduk,
});
export default pasarDesaState;

View File

@@ -0,0 +1,249 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
ikonUrl: z.string().optional(),
statistik: z.object({
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
})
});
const defaultForm = {
nama: "",
deskripsi: "",
ikonUrl: "",
statistik: {
tahun: "",
jumlah: ""
}
};
const programKemiskinanState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKemiskinanState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKemiskinanState.create.loading = true;
const res = await ApiFetch.api.ekonomi.programkemiskinan["create"].post(
programKemiskinanState.create.form
);
if (res.status === 200) {
programKemiskinanState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log(error);
return toast.error("failed create");
} finally {
programKemiskinanState.create.loading = false;
}
},
},
findMany: {
data: [] as Prisma.ProgramKemiskinanGetPayload<{
include: {
statistik: true;
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get();
if (res.status === 200) {
programKemiskinanState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.ProgramKemiskinanGetPayload<{
include: {
statistik: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/programkemiskinan/${id}`);
if (res.ok) {
const data = await res.json();
programKemiskinanState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
programKemiskinanState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
programKemiskinanState.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/programkemiskinan/${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 = {
nama: data.nama,
deskripsi: data.deskripsi,
ikonUrl: data.ikonUrl || "",
statistik: {
tahun: data.statistik.tahun,
jumlah: data.statistik.jumlah,
},
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading program kemiskinan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKemiskinanState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
programKemiskinanState.update.loading = true;
const response = await fetch(
`/api/ekonomi/programkemiskinan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
deskripsi: this.form.deskripsi,
ikonUrl: this.form.ikonUrl,
statistik: {
tahun: this.form.statistik.tahun,
jumlah: this.form.statistik.jumlah,
},
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update program kemiskinan");
await programKemiskinanState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update program kemiskinan");
}
} catch (error) {
console.error("Error updating program kemiskinan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update program kemiskinan"
);
return false;
} finally {
programKemiskinanState.update.loading = false;
}
},
reset() {
programKemiskinanState.update.id = "";
programKemiskinanState.update.form = { ...defaultForm };
},
},
delete: {
loading: false,
async delete(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKemiskinanState.delete.loading = true;
const response = await fetch(
`/api/ekonomi/programkemiskinan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Program kemiskinan berhasil dihapus"
);
await programKemiskinanState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kemiskinan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kemiskinan");
} finally {
programKemiskinanState.delete.loading = false;
}
},
},
});
export default programKemiskinanState;

View File

@@ -0,0 +1,202 @@
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 templateGrafikSektorUnggulan = z.object({
name: z.string().min(2, "Nama harus diisi"),
description: z.string().min(2, "Deskripsi harus diisi"),
value: z.number().min(1, "Nilai harus diisi"),
});
interface SektorUnggulanForm {
id?: string;
name: string;
description: string;
value: number;
}
const defaultForm: SektorUnggulanForm = {
name: "",
description: "",
value: 0,
};
const grafikSektorUnggulan = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikSektorUnggulan.safeParse(
grafikSektorUnggulan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikSektorUnggulan.create.loading = true;
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
"create"
].post(grafikSektorUnggulan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikSektorUnggulan.create.form = {
name: "",
description: "",
value: 0,
};
grafikSektorUnggulan.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikSektorUnggulan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SektorUnggulanDesaGetPayload<{
select: {
id: true;
name: true;
description: true;
value: true;
createdAt: true;
updatedAt: true;
};
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
"find-many"
].get();
if (res.status === 200) {
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.SektorUnggulanDesaGetPayload<{
select: {
id: true;
name: true;
description: true;
value: true;
createdAt: true;
updatedAt: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/sektourunggulandesa/${id}`);
if (res.ok) {
const data = await res.json();
grafikSektorUnggulan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikSektorUnggulan.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik sektor unggulan desa:", error);
grafikSektorUnggulan.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async byId() {
// Method implementation if needed
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikSektorUnggulan.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/ekonomi/sektourunggulandesa/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await grafikSektorUnggulan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik sektor unggulan desa");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikSektorUnggulan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/sektourunggulandesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik sektor unggulan desa berhasil dihapus"
);
await grafikSektorUnggulan.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus grafik sektor unggulan desa"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error(
"Terjadi kesalahan saat menghapus grafik sektor unggulan desa"
);
} finally {
grafikSektorUnggulan.delete.loading = false;
}
},
},
});
export default grafikSektorUnggulan;

View File

@@ -0,0 +1,785 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { proxy } from "valtio";
import { z } from "zod";
import { toast } from "react-toastify";
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
const templatePosisiOrganisasi = z.object({
nama: z.string().min(1, "Nama harus diisi"),
deskripsi: z.string().optional(),
hierarki: z.number().int().positive("Hierarki harus angka positif"),
});
const posisiOrganisasiDefaultForm = {
nama: "",
deskripsi: "",
hierarki: 0,
};
const posisiOrganisasi = proxy({
create: {
form: { ...posisiOrganisasiDefaultForm },
loading: false,
async submit() {
const cek = templatePosisiOrganisasi.safeParse(this.form);
if (!cek.success) {
const err = cek.error.issues.map((v) => v.message).join("\n");
return toast.error(err);
}
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"posisi-organisasi"
]["create"].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan posisi organisasi");
posisiOrganisasi.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan posisi");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Terjadi kesalahan saat menambahkan posisi");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...posisiOrganisasiDefaultForm };
},
},
edit: {
id: "",
form: { ...posisiOrganisasiDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${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 = {
nama: data.nama,
deskripsi: data.deskripsi,
hierarki: data.hierarki,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading posisi organisasi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePosisiOrganisasi.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
this.loading = true;
const response = await fetch(
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
deskripsi: this.form.deskripsi,
hierarki: this.form.hierarki,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update posisi organisasi");
await posisiOrganisasi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate posisi organisasi"
);
}
} catch (error) {
console.error("Error updating posisi organisasi:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate posisi organisasi"
);
return false;
} finally {
this.loading = false;
}
},
reset() {
this.id = "";
this.form = { ...posisiOrganisasiDefaultForm };
},
},
findMany: {
data: [] as Array<{
id: string;
nama: string;
deskripsi: string | null;
hierarki: number;
}>,
async load() {
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"posisi-organisasi"
]["find-many"].get();
if (res.status === 200) {
// The API now returns the id field, so we can use it directly
this.data = res.data?.data ?? [];
}
} catch (error) {
console.error("Find many error:", error);
this.data = [];
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
posisiOrganisasi.delete.loading = true;
const response = await fetch(
`/api/ekonomi/struktur-organisasi/posisi-organisasi/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Posisi organisasi berhasil dihapus");
await posisiOrganisasi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus posisi organisasi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus posisi organisasi");
} finally {
posisiOrganisasi.delete.loading = false;
}
},
},
});
const templatePegawai = z.object({
namaLengkap: z.string().min(1, "Nama wajib diisi"),
gelarAkademik: z.string().optional(),
imageId: z.string().nullable().optional(),
tanggalMasuk: z.string().optional(), // ISO format
email: z.string().email("Email tidak valid").optional(),
telepon: z.string().optional(),
alamat: z.string().optional(),
posisiId: z.string().min(1, "Posisi wajib diisi"),
isActive: z.boolean().default(true),
});
const pegawaiDefaultForm = {
namaLengkap: "",
gelarAkademik: "",
imageId: "",
tanggalMasuk: "",
email: "",
telepon: "",
alamat: "",
posisiId: "",
isActive: true,
};
const pegawai = proxy({
create: {
form: { ...pegawaiDefaultForm },
loading: false,
async submit() {
const cek = templatePegawai.safeParse(pegawai.create.form);
if (!cek.success) {
const err = cek.error.issues.map((i) => i.message).join("\n");
toast.error(err);
return;
}
try {
pegawai.create.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"pegawai"
]["create"].post(pegawai.create.form);
if (res.status === 200) {
toast.success("Pegawai berhasil ditambahkan");
await pegawai.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah pegawai");
}
} catch (error) {
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan pegawai");
} finally {
pegawai.create.loading = false;
}
},
},
// In struktur-organisasi.ts
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pegawai.findMany.loading = true; // Use the full path to access the property
pegawai.findMany.page = page;
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"pegawai"
]["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pegawai.findMany.data = res.data.data || [];
pegawai.findMany.total = res.data.total || 0;
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
},
},
findUnique: {
data: null as
| (Prisma.PegawaiGetPayload<{
include: { posisi: true; image: true };
}> & { isActive: boolean })
| null,
async load(id: string) {
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`);
if (res.ok) {
const json = await res.json();
pegawai.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? json.data.aktif ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
pegawai.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pegawai.delete.loading = true;
const res = await fetch(
`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`,
{
method: "DELETE",
}
);
const json = await res.json();
if (res.ok) {
toast.success(json.message ?? "Berhasil hapus pegawai");
await pegawai.findMany.load();
} else {
toast.error(json.message ?? "Gagal hapus pegawai");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus");
} finally {
pegawai.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...pegawaiDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/ekonomi/struktur-organisasi/pegawai/${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 = {
namaLengkap: data.namaLengkap,
gelarAkademik: data.gelarAkademik,
imageId: data.imageId,
tanggalMasuk: data.tanggalMasuk,
email: data.email,
telepon: data.telepon,
alamat: data.alamat,
posisiId: data.posisiId,
isActive: data.isActive,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading berita:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const cek = templatePegawai.safeParse(pegawai.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
pegawai.edit.loading = true;
// Format tanggalMasuk to ISO string if it exists
const formattedTanggalMasuk = this.form.tanggalMasuk
? new Date(this.form.tanggalMasuk).toISOString()
: undefined;
const response = await fetch(
`/api/ekonomi/struktur-organisasi/pegawai/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: this.id,
namaLengkap: this.form.namaLengkap,
gelarAkademik: this.form.gelarAkademik,
imageId: this.form.imageId || null,
tanggalMasuk: formattedTanggalMasuk,
email: this.form.email,
telepon: this.form.telepon,
alamat: this.form.alamat,
posisiId: this.form.posisiId,
isActive: this.form.isActive,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update pegawai");
await pegawai.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update pegawai");
}
} catch (error) {
console.error("Error updating pegawai:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update pegawai"
);
return false;
} finally {
pegawai.edit.loading = false;
}
},
reset() {
pegawai.edit.id = "";
pegawai.edit.form = { ...pegawaiDefaultForm };
},
},
});
// Schema Zod untuk form validasi
const templateHubunganOrganisasiForm = z.object({
atasanId: z.string().min(1, "Atasan wajib dipilih"),
bawahanId: z.string().min(1, "Bawahan wajib dipilih"),
tipe: z.string().optional(),
});
// Default form state
const defaultHubunganOrganisasiForm = {
atasanId: "",
bawahanId: "",
tipe: "",
};
// ====================== STATE ===========================
const hubunganOrganisasi = proxy({
create: {
form: { ...defaultHubunganOrganisasiForm },
loading: false,
async create() {
const cek = templateHubunganOrganisasiForm.safeParse(
hubunganOrganisasi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n")}]`;
return toast.error(err);
}
try {
hubunganOrganisasi.create.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"hubungan-organisasi"
]["create"].post(hubunganOrganisasi.create.form);
if (res.status === 200 && res.data?.success) {
hubunganOrganisasi.findMany.load();
return toast.success("Berhasil menambahkan hubungan organisasi");
} else {
return toast.error(res.data?.message || "Gagal menambahkan data");
}
} catch (error) {
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan");
} finally {
hubunganOrganisasi.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
atasanId: string;
bawahanId: string;
tipe?: string | null;
atasan: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string | null;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
};
bawahan: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string | null;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
};
}> | null,
async load() {
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"hubungan-organisasi"
]["find-many"].get();
if (res.status === 200) {
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map(
(item: any) => ({
...item,
atasan: item.atasan
? {
...item.atasan,
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true,
}
: null,
bawahan: item.bawahan
? {
...item.bawahan,
isActive:
item.bawahan.isActive ?? item.bawahan.aktif ?? true,
}
: null,
})
);
} else {
hubunganOrganisasi.findMany.data = [];
}
} catch (error) {
console.error("Fetch list error:", error);
toast.error("Gagal memuat data hubungan organisasi");
hubunganOrganisasi.findMany.data = [];
}
},
},
findUnique: {
data: null as {
id: string;
atasanId: string;
bawahanId: string;
tipe?: string | null;
atasan?: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
aktif: boolean;
createdAt: Date;
updatedAt: Date;
};
bawahan?: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
aktif: boolean;
createdAt: Date;
updatedAt: Date;
};
} | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
);
const result = await res.json();
if (res.ok && result?.success) {
hubunganOrganisasi.findUnique.data = result.data;
} else {
hubunganOrganisasi.findUnique.data = null;
toast.error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Find unique error:", error);
hubunganOrganisasi.findUnique.data = null;
}
},
},
edit: {
id: "",
form: { ...defaultHubunganOrganisasiForm },
loading: false,
async load(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
);
const result = await res.json();
if (res.ok && result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
atasanId: data.atasanId,
bawahanId: data.bawahanId,
tipe: data.tipe || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateHubunganOrganisasiForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n")}]`;
return toast.error(err);
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
}
);
const result = await res.json();
if (res.ok && result.success) {
await hubunganOrganisasi.findMany.load();
toast.success("Berhasil mengupdate hubungan organisasi");
return true;
} else {
throw new Error(result?.message || "Gagal mengupdate");
}
} catch (error) {
console.error("Update error:", error);
toast.error(error instanceof Error ? error.message : "Gagal update");
return false;
} finally {
this.loading = false;
}
},
reset() {
hubunganOrganisasi.edit.id = "";
hubunganOrganisasi.edit.form = { ...defaultHubunganOrganisasiForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
hubunganOrganisasi.delete.loading = true;
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result?.success) {
toast.success("Hubungan organisasi berhasil dihapus");
hubunganOrganisasi.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus hubungan organisasi");
}
} catch (error) {
console.error("Delete error:", error);
toast.error("Terjadi kesalahan saat menghapus");
} finally {
hubunganOrganisasi.delete.loading = false;
}
},
},
});
const strukturorganisasiState = proxy({
posisiOrganisasi,
pegawai,
hubunganOrganisasi,
});
export default strukturorganisasiState;

View File

@@ -0,0 +1,376 @@
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 templateGrafikUsiaKerjaYangMenganggur = z.object({
usia18_25: z.string().min(1, "Data usia 18-25 harus diisi"),
usia26_35: z.string().min(1, "Data usia 26-35 harus diisi"),
usia36_45: z.string().min(1, "Data usia 36-45 harus diisi"),
usia46_keatas: z.string().min(1, "Data usia 46 keatas harus diisi"),
});
type GrafikUsiaKerjaYangMenganggur = Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
select: {
id: true;
usia18_25: true;
usia26_35: true;
usia36_45: true;
usia46_keatas: true;
};
}>;
const defaultForm: Omit<GrafikUsiaKerjaYangMenganggur, 'id'> & { id?: string } = {
usia18_25: "",
usia26_35: "",
usia36_45: "",
usia46_keatas: "",
};
const grafikBerdasarkanUsiaKerjaNganggur = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikUsiaKerjaYangMenganggur.safeParse(
grafikBerdasarkanUsiaKerjaNganggur.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanUsiaKerjaNganggur.create.loading = true;
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
"create"
].post(grafikBerdasarkanUsiaKerjaNganggur.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanUsiaKerjaNganggur.create.form = {
usia18_25: "",
usia26_35: "",
usia36_45: "",
usia46_keatas: "",
};
grafikBerdasarkanUsiaKerjaNganggur.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanUsiaKerjaNganggur.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/grafikusiakerjayangmenganggur/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanUsiaKerjaNganggur.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanUsiaKerjaNganggur.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan usia kerja yang menganggur:", error);
grafikBerdasarkanUsiaKerjaNganggur.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultForm},
loading: false,
async byId() {
// Method implementation if needed
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikUsiaKerjaYangMenganggur.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(
`/api/ekonomi/grafikusiakerjayangmenganggur/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await grafikBerdasarkanUsiaKerjaNganggur.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan usia kerja yang menganggur");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanUsiaKerjaNganggur.delete.loading = true;
const response = await fetch(`/api/ekonomi/grafikusiakerjayangmenganggur/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan usia kerja yang menganggur berhasil dihapus");
await grafikBerdasarkanUsiaKerjaNganggur.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan usia kerja yang menganggur");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan usia kerja yang menganggur");
} finally {
grafikBerdasarkanUsiaKerjaNganggur.delete.loading = false;
}
},
}
});
const templateGrafikBerpendidikanYangMenganggur = z.object({
SD: z.string().min(1, "Data SD harus diisi"),
SMP: z.string().min(1, "Data SMP harus diisi"),
SMA: z.string().min(1, "Data SMA harus diisi"),
D3: z.string().min(1, "Data D3 harus diisi"),
S1: z.string().min(1, "Data S1 harus diisi"),
});
type GrafikBerpendidikanYangMenganggur = Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
select: {
id: true;
SD: true;
SMP: true;
SMA: true;
D3: true;
S1: true;
};
}>;
const defaultFormBerpendidikan: Omit<GrafikBerpendidikanYangMenganggur, 'id'> & { id?: string } = {
SD: "",
SMP: "",
SMA: "",
D3: "",
S1: "",
};
const grafikBerdasarkanPendidikan = proxy({
create: {
form: defaultFormBerpendidikan,
loading: false,
async create() {
const cek = templateGrafikBerpendidikanYangMenganggur.safeParse(
grafikBerdasarkanPendidikan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanPendidikan.create.loading = true;
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
"create"
].post(grafikBerdasarkanPendidikan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanPendidikan.create.form = {
SD: "",
SMP: "",
SMA: "",
D3: "",
S1: "",
};
grafikBerdasarkanPendidikan.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanPendidikan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/grafikmenganggurberdasarkanpendidikan/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanPendidikan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanPendidikan.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan usia kerja yang menganggur:", error);
grafikBerdasarkanPendidikan.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultFormBerpendidikan},
loading: false,
async byId() {
// Method implementation if needed
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikBerpendidikanYangMenganggur.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(
`/api/ekonomi/grafikmenganggurberdasarkanpendidikan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await grafikBerdasarkanPendidikan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan pendidikan yang menganggur");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanPendidikan.delete.loading = true;
const response = await fetch(`/api/ekonomi/grafikmenganggurberdasarkanpendidikan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan pendidikan yang menganggur berhasil dihapus");
await grafikBerdasarkanPendidikan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan pendidikan yang menganggur");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan pendidikan yang menganggur");
} finally {
grafikBerdasarkanPendidikan.delete.loading = false;
}
},
}
});
const grafikNganggur = proxy({
grafikBerdasarkanUsiaKerjaNganggur,
grafikBerdasarkanPendidikan
})
export default grafikNganggur;

View File

@@ -0,0 +1,123 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
alamat: z.string().min(1).max(5000),
namaIde: z.string().min(1).max(5000),
masalah: z.string().min(1).max(5000),
benefit: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
alamat: "",
namaIde: "",
masalah: "",
benefit: "",
};
const ajukanIdeInovatifState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(ajukanIdeInovatifState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
ajukanIdeInovatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.ajukanideinovatif["create"].post(
ajukanIdeInovatifState.create.form
);
if (res.status === 200) {
ajukanIdeInovatifState.findMany.load();
return toast.success("Ajukan Ide Inovatif berhasil di kirim");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
ajukanIdeInovatifState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get();
if (res.status === 200) {
ajukanIdeInovatifState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/ajukanideinovatif/${id}`);
if (res.ok) {
const data = await res.json();
ajukanIdeInovatifState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
ajukanIdeInovatifState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading ajukan ide inovatif:", error);
ajukanIdeInovatifState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
ajukanIdeInovatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus");
await ajukanIdeInovatifState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus ajukan ide inovatif");
} finally {
ajukanIdeInovatifState.delete.loading = false;
}
},
},
});
export default ajukanIdeInovatifState;

View File

@@ -0,0 +1,216 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const desaDigitalState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(desaDigitalState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
desaDigitalState.create.loading = true;
const res = await ApiFetch.api.inovasi.desadigital["create"].post(
desaDigitalState.create.form
);
if (res.status === 200) {
desaDigitalState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
desaDigitalState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DesaDigitalGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.desadigital["find-many"].get();
if (res.status === 200) {
desaDigitalState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DesaDigitalGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/desadigital/${id}`);
if (res.ok) {
const data = await res.json();
desaDigitalState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
desaDigitalState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading desa digital:", error);
desaDigitalState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
desaDigitalState.delete.loading = true;
const response = await fetch(`/api/inovasi/desadigital/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Desa Digital berhasil dihapus");
await desaDigitalState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus desa digital");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus desa digital");
} finally {
desaDigitalState.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/inovasi/desadigital/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading desa digital:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(desaDigitalState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
desaDigitalState.edit.loading = true;
const response = await fetch(
`/api/inovasi/desadigital/${desaDigitalState.edit.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("Berhasil update desa digital");
await desaDigitalState.findMany.load();
return true;
} else {
throw new Error(result?.message || "Gagal update desa digital");
}
} catch (error) {
console.error("Error updating desa digital:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update desa digital"
);
return false;
} finally {
desaDigitalState.edit.loading = false;
}
},
reset() {
desaDigitalState.edit.id = "";
desaDigitalState.edit.form = { ...defaultForm };
},
},
});
export default desaDigitalState;

View File

@@ -0,0 +1,216 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const infoTeknoState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(infoTeknoState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoTeknoState.create.loading = true;
const res = await ApiFetch.api.inovasi.infotekno["create"].post(
infoTeknoState.create.form
);
if (res.status === 200) {
infoTeknoState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
infoTeknoState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.InfoTeknoGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.infotekno["find-many"].get();
if (res.status === 200) {
infoTeknoState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.InfoTeknoGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/infotekno/${id}`);
if (res.ok) {
const data = await res.json();
infoTeknoState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
infoTeknoState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading desa digital:", error);
infoTeknoState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
infoTeknoState.delete.loading = true;
const response = await fetch(`/api/inovasi/infotekno/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Info Tekno berhasil dihapus");
await infoTeknoState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus info tekno");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus info tekno");
} finally {
infoTeknoState.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/inovasi/infotekno/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading info tekno:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(infoTeknoState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
infoTeknoState.edit.loading = true;
const response = await fetch(
`/api/inovasi/infotekno/${infoTeknoState.edit.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("Berhasil update info tekno");
await infoTeknoState.findMany.load();
return true;
} else {
throw new Error(result?.message || "Gagal update info tekno");
}
} catch (error) {
console.error("Error updating info tekno:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update info tekno"
);
return false;
} finally {
infoTeknoState.edit.loading = false;
}
},
reset() {
infoTeknoState.edit.id = "";
infoTeknoState.edit.form = { ...defaultForm };
},
},
});
export default infoTeknoState;

View File

@@ -0,0 +1,234 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
kolaborator: z.string().min(1, "Kolaborator minimal 1 karakter"),
imageId: z.string().min(1, "Image ID minimal 1 karakter"),
})
const defaultForm = {
name: "",
tahun: 0,
slug: "",
deskripsi: "",
kolaborator: "",
imageId: "",
}
const kolaborasiInovasiState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(kolaborasiInovasiState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kolaborasiInovasiState.create.loading = true;
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["create"].post(
kolaborasiInovasiState.create.form
);
if (res.status === 200) {
kolaborasiInovasiState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
kolaborasiInovasiState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
kolaborasiInovasiState.findMany.loading = true; // Use the full path to access the property
kolaborasiInovasiState.findMany.page = page;
try {
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
kolaborasiInovasiState.findMany.data = res.data.data || [];
kolaborasiInovasiState.findMany.total = res.data.total || 0;
kolaborasiInovasiState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load grafik berdasarkan jenis kelamin:",
res.data?.message
);
kolaborasiInovasiState.findMany.data = [];
kolaborasiInovasiState.findMany.total = 0;
kolaborasiInovasiState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
kolaborasiInovasiState.findMany.data = [];
kolaborasiInovasiState.findMany.total = 0;
kolaborasiInovasiState.findMany.totalPages = 1;
} finally {
kolaborasiInovasiState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/kolaborasiinovasi/${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,
tahun: data.tahun,
slug: data.slug,
deskripsi: data.deskripsi,
kolaborator: data.kolaborator,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading kolaborasi inovasi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/inovasi/kolaborasiinovasi/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await kolaborasiInovasiState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data kolaborasi inovasi");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.KolaborasiInovasiGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/kolaborasiinovasi/${id}`);
if (res.ok) {
const data = await res.json();
kolaborasiInovasiState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kolaborasiInovasiState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading kolaborasi inovasi:", error);
kolaborasiInovasiState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kolaborasiInovasiState.delete.loading = true;
const response = await fetch(`/api/inovasi/kolaborasiinovasi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kolaborasi inovasi berhasil dihapus");
await kolaborasiInovasiState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kolaborasi inovasi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kolaborasi inovasi");
} finally {
kolaborasiInovasiState.delete.loading = false;
}
},
},
});
export default kolaborasiInovasiState;

View File

@@ -0,0 +1,803 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= ADMINISTRASI ONLINE ========================================= //
const templateAdministrasiOnlineForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
jenisLayananId: z.string().min(1, "Jenis layanan minimal 1 karakter"),
});
const defaultAdministrasiOnlineForm = {
name: "",
alamat: "",
nomorTelepon: "",
jenisLayananId: "",
};
const administrasiOnline = proxy({
create: {
form: { ...defaultAdministrasiOnlineForm },
loading: false,
async create() {
const cek = templateAdministrasiOnlineForm.safeParse(
administrasiOnline.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
administrasiOnline.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"create"
].post(administrasiOnline.create.form);
if (res.status === 200) {
administrasiOnline.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
administrasiOnline.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
administrasiOnline.findMany.loading = true;
administrasiOnline.findMany.page = page;
try {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"find-many"
].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
administrasiOnline.findMany.data = res.data.data ?? [];
administrasiOnline.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch administrasi online paginated:", err);
} finally {
administrasiOnline.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/${id}`
);
if (res.ok) {
const data = await res.json();
administrasiOnline.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch administrasi online:", res.statusText);
administrasiOnline.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching administrasi online:", error);
administrasiOnline.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
administrasiOnline.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Administrasi online berhasil dihapus"
);
await administrasiOnline.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus administrasi online");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus administrasi online");
} finally {
administrasiOnline.delete.loading = false;
}
},
},
});
// ========================================= JENIS LAYANAN ========================================= //
const templateJenisLayananForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
});
const defaultJenisLayananForm = {
nama: "",
deskripsi: "",
};
const jenisLayanan = proxy({
create: {
form: { ...defaultJenisLayananForm },
loading: false,
async create() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenisLayanan.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"create"
].post(jenisLayanan.create.form);
if (res.status === 200) {
jenisLayanan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
jenisLayanan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
deskripsi: string;
}> | null,
async load() {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"find-many"
].get();
if (res.status === 200) {
jenisLayanan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenisLayananGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${id}`
);
if (res.ok) {
const data = await res.json();
jenisLayanan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenisLayanan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenisLayanan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenisLayanan.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Jenis layanan berhasil dihapus");
await jenisLayanan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenis layanan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenis layanan");
} finally {
jenisLayanan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultJenisLayananForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${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 = {
nama: data.nama,
deskripsi: data.deskripsi,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenis layanan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenisLayanan.edit.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${jenisLayanan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenisLayanan.edit.form.nama,
deskripsi: jenisLayanan.edit.form.deskripsi,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate jenis layanan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenis layanan"
);
await jenisLayanan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate jenis layanan");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating jenis layanan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenis layanan"
);
return false;
} finally {
jenisLayanan.edit.loading = false;
}
},
reset() {
jenisLayanan.edit.id = "";
jenisLayanan.edit.form = { ...defaultJenisLayananForm };
},
},
});
// ========================================= PENGADUAN MASYARAKAT ========================================= //
const templatePengaduanMasyarakatForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
email: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
nik: z.string().min(1, "NIK minimal 1 karakter"),
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
deskripsiPengaduan: z.string().min(1, "Deskripsi pengaduan minimal 1 karakter"),
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
imageId: z.string().min(1, "Image minimal 1 karakter"),
});
const defaultPengaduanMasyarakatForm = {
name: "",
email: "",
nomorTelepon: "",
nik: "",
judulPengaduan: "",
lokasiKejadian: "",
deskripsiPengaduan: "",
jenisPengaduanId: "",
imageId: "",
};
const pengaduanMasyarakat = proxy({
create: {
form: { ...defaultPengaduanMasyarakatForm },
loading: false,
async create() {
const cek = templatePengaduanMasyarakatForm.safeParse(
pengaduanMasyarakat.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengaduanMasyarakat.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
"create"
].post(pengaduanMasyarakat.create.form);
if (res.status === 200) {
pengaduanMasyarakat.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
pengaduanMasyarakat.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PengaduanMasyarakatGetPayload<{
include: {
jenisPengaduan: true;
image: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
pengaduanMasyarakat.findMany.loading = true;
pengaduanMasyarakat.findMany.page = page;
try {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
"find-many"
].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
pengaduanMasyarakat.findMany.data = res.data.data ?? [];
pengaduanMasyarakat.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch pengaduan masyarakat paginated:", err);
} finally {
pengaduanMasyarakat.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PengaduanMasyarakatGetPayload<{
include: {
jenisPengaduan: true;
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/${id}`
);
if (res.ok) {
const data = await res.json();
pengaduanMasyarakat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch pengaduan masyarakat:", res.statusText);
pengaduanMasyarakat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching pengaduan masyarakat:", error);
pengaduanMasyarakat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengaduanMasyarakat.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Pengaduan masyarakat berhasil dihapus"
);
await pengaduanMasyarakat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengaduan masyarakat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengaduan masyarakat");
} finally {
pengaduanMasyarakat.delete.loading = false;
}
},
},
});
// ========================================= JENIS PENGADUAN ========================================= //
const templateJenisPengaduanForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const defaultJenisPengaduanForm = {
nama: "",
};
const jenisPengaduan = proxy({
create: {
form: { ...defaultJenisPengaduanForm },
loading: false,
async create() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenisPengaduan.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
"create"
].post(jenisPengaduan.create.form);
if (res.status === 200) {
jenisPengaduan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
jenisPengaduan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
"find-many"
].get();
if (res.status === 200) {
jenisPengaduan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenisPengaduanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${id}`
);
if (res.ok) {
const data = await res.json();
jenisPengaduan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenisPengaduan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenisPengaduan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenisPengaduan.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Jenis pengduan berhasil dihapus");
await jenisPengaduan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenis pengaduan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenis pengaduan");
} finally {
jenisPengaduan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultJenisPengaduanForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${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 = {
nama: data.nama
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenis pengaduan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenisPengaduan.edit.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${jenisPengaduan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenisPengaduan.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate jenis pengaduan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenis pengaduan"
);
await jenisPengaduan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate jenis pengaduan");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating jenis pengaduan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenis pengaduan"
);
return false;
} finally {
jenisPengaduan.edit.loading = false;
}
},
reset() {
jenisPengaduan.edit.id = "";
jenisPengaduan.edit.form = { ...defaultJenisPengaduanForm };
},
},
});
const layananonlineDesa = proxy({
administrasiOnline,
jenisLayanan,
pengaduanMasyarakat,
jenisPengaduan,
});
export default layananonlineDesa;

View File

@@ -0,0 +1,227 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
deskripsi: "",
slug: "",
icon: "",
};
const programKreatifState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKreatifState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKreatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.programkreatif["create"].post(
programKreatifState.create.form
);
if (res.status === 200) {
programKreatifState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
programKreatifState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
programKreatifState.findMany.loading = true; // Use the full path to access the property
programKreatifState.findMany.page = page;
try {
const res = await ApiFetch.api.inovasi.programkreatif["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
programKreatifState.findMany.data = res.data.data || [];
programKreatifState.findMany.total = res.data.total || 0;
programKreatifState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load grafik berdasarkan jenis kelamin:",
res.data?.message
);
programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1;
} finally {
programKreatifState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/programkreatif/${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,
slug: data.slug,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading program kreatif:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/inovasi/programkreatif/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await programKreatifState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data program kreatif");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ProgramKreatifGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/programkreatif/${id}`);
if (res.ok) {
const data = await res.json();
programKreatifState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
programKreatifState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading program kreatif:", error);
programKreatifState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKreatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/programkreatif/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program kreatif berhasil dihapus");
await programKreatifState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kreatif");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kreatif");
} finally {
programKreatifState.delete.loading = false;
}
},
},
});
export default programKreatifState;

View File

@@ -0,0 +1,232 @@
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, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const keamananLingkunganState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(keamananLingkunganState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
keamananLingkunganState.create.loading = true;
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"create"
].post(keamananLingkunganState.create.form);
if (res.status === 200) {
keamananLingkunganState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
keamananLingkunganState.create.loading = false;
}
},
resetForm() {
keamananLingkunganState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"find-many"
].get();
if (res.status === 200) {
keamananLingkunganState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/keamananlingkungan/${id}`);
if (res.ok) {
const data = await res.json();
keamananLingkunganState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
keamananLingkunganState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
keamananLingkunganState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
keamananLingkunganState.delete.loading = true;
const response = await fetch(
`/api/keamanan/keamananlingkungan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Keamanan ingkungan berhasil dihapus"
);
await keamananLingkunganState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus keamanan ingkungan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus keamanan ingkungan");
} finally {
keamananLingkunganState.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/keamanan/keamananlingkungan/${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 loading keamanan lingkungan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(keamananLingkunganState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
keamananLingkunganState.edit.loading = true;
const response = await fetch(
`/api/keamanan/keamananlingkungan/${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("Berhasil update keamanan lingkungan");
await keamananLingkunganState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update keamanan lingkungan");
}
} catch (error) {
console.error("Error updating keamanan lingkungan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update keamanan lingkungan"
);
return false;
} finally {
keamananLingkunganState.edit.loading = false;
}
},
reset() {
keamananLingkunganState.edit.id = "";
keamananLingkunganState.edit.form = { ...defaultForm };
},
},
});
export default keamananLingkunganState;

View File

@@ -0,0 +1,259 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
imageId: z.string().nonempty(),
kontakItems: z.array(
z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
imageId: z.string().nonempty(),
})
),
});
const defaultForm = {
nama: "",
imageId: "",
kontakItems: [
{
nama: "",
nomorTelepon: "",
imageId: "",
},
],
};
const kontakDaruratKeamananState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(
kontakDaruratKeamananState.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratKeamananState.create.loading = true;
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
"create"
].post(kontakDaruratKeamananState.create.form);
if (res.status === 200) {
kontakDaruratKeamananState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDaruratKeamananState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.KontakDaruratKeamananGetPayload<{
include: {
kontakItems: true;
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
"find-many"
].get();
if (res.status === 200) {
kontakDaruratKeamananState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KontakDaruratKeamananGetPayload<{
include: {
kontakItems: {
include: {
image: true;
};
};
image: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/kontakdaruratkeamanan/${id}`);
if (res.ok) {
const data = await res.json();
kontakDaruratKeamananState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDaruratKeamananState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDaruratKeamananState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kontakDaruratKeamananState.delete.loading = true;
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/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 kontakDaruratKeamananState.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 {
kontakDaruratKeamananState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/${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 = {
nama: data.nama,
imageId: data.imageId,
kontakItems: [
{
nama: data.kontakItems.nama,
nomorTelepon: data.kontakItems.nomorTelepon,
imageId: data.kontakItems.imageId,
},
],
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kontak darurat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(
kontakDaruratKeamananState.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratKeamananState.update.loading = true;
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
imageId: this.form.imageId,
kontakItems: [
{
nama: this.form.kontakItems[0].nama,
nomorTelepon: this.form.kontakItems[0].nomorTelepon,
imageId: this.form.kontakItems[0].imageId,
},
],
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update kontak darurat");
await kontakDaruratKeamananState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate kontak darurat");
}
} catch (error) {
console.error("Error updating kontak darurat:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kontak darurat"
);
return false;
} finally {
kontakDaruratKeamananState.update.loading = false;
}
},
reset() {
kontakDaruratKeamananState.update.id = "";
kontakDaruratKeamananState.update.form = { ...defaultForm };
},
},
});
export default kontakDaruratKeamananState;

View File

@@ -0,0 +1,273 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
export type Status = "Selesai" | "Proses" | "Gagal";
const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
tanggalWaktu: z.string().min(3, "Tanggal Waktu minimal 3 karakter"),
status: z.enum(["Selesai", "Proses", "Gagal"]),
penanganan: z.string(),
kronologi: z.string().optional(),
});
interface FormData {
judul: string;
lokasi: string;
tanggalWaktu: string;
status: Status;
penanganan: string;
kronologi: string;
}
const defaultForm: FormData = {
judul: "",
lokasi: "",
tanggalWaktu: new Date().toISOString(),
status: "Proses",
penanganan: "",
kronologi: "",
};
const laporanPublikState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(laporanPublikState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
laporanPublikState.create.loading = true;
// Ensure we have a valid date
if (!laporanPublikState.create.form.tanggalWaktu) {
return toast.error("Tanggal laporan harus diisi");
}
// Format the data before sending
const formData = {
...laporanPublikState.create.form,
// Ensure the date is in the correct format for the API
tanggalWaktu: new Date(laporanPublikState.create.form.tanggalWaktu).toISOString()
};
console.log("Sending form data:", formData); // Debug log
const res = await ApiFetch.api.keamanan.laporanpublik["create"].post(
formData
);
if (res.error) {
console.error("API Error:", res.error);
throw new Error("Failed to create laporan publik");
}
if (res.status === 200) {
laporanPublikState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.error("Error creating laporan publik:", error);
toast.error(error instanceof Error ? error.message : "Gagal membuat laporan publik");
throw error; // Re-throw to be handled by the caller
} finally {
laporanPublikState.create.loading = false;
}
},
resetForm() {
laporanPublikState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get();
if (res.status === 200) {
laporanPublikState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/laporanpublik/${id}`);
if (res.ok) {
const data = await res.json();
laporanPublikState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
laporanPublikState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
laporanPublikState.findUnique.data = null;
}
},
resetForm() {
laporanPublikState.findUnique.data = null;
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
laporanPublikState.delete.loading = true;
const response = await fetch(`/api/keamanan/laporanpublik/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Laporan publik berhasil dihapus"
);
await laporanPublikState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus laporan publik");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus laporan publik");
} finally {
laporanPublikState.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/keamanan/laporanpublik/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
judul: data.judul,
lokasi: data.lokasi,
tanggalWaktu: data.tanggalWaktu,
status: data.status,
penanganan: data.penanganan,
kronologi: data.kronologi,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading keamanan lingkungan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(laporanPublikState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
laporanPublikState.edit.loading = true;
const response = await fetch(
`/api/keamanan/laporanpublik/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
lokasi: this.form.lokasi,
tanggalWaktu: this.form.tanggalWaktu,
status: this.form.status,
penanganan: this.form.penanganan,
kronologi: this.form.kronologi,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update laporan publik");
await laporanPublikState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update laporan publik");
}
} catch (error) {
console.error("Error updating laporan publik:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update laporan publik"
);
return false;
} finally {
laporanPublikState.edit.loading = false;
}
},
reset() {
laporanPublikState.edit.id = "";
laporanPublikState.edit.form = { ...defaultForm };
},
}
});
export default laporanPublikState;

View File

@@ -0,0 +1,314 @@
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({
pencegahanKriminalitas: z.object({
programKeamanan: z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
tipsKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
konten: z.string().min(1, "Konten minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
videoKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
videoUrl: z.string().min(1, "Video URL minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
}),
});
const defaultForm = {
pencegahanKriminalitas: {
programKeamanan: {
nama: "",
deskripsi: "",
slug: "",
},
tipsKeamanan: {
judul: "",
konten: "",
slug: "",
},
videoKeamanan: {
judul: "",
deskripsi: "",
videoUrl: "",
slug: "",
},
},
};
const pencegahanKriminalitasState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(
pencegahanKriminalitasState.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pencegahanKriminalitasState.create.loading = true;
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"create"
].post(pencegahanKriminalitasState.create.form.pencegahanKriminalitas);
if (res.status === 200) {
pencegahanKriminalitasState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
pencegahanKriminalitasState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"find-many"
].get();
if (res.status === 200) {
pencegahanKriminalitasState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/pencegahankriminalitas/${id}`);
if (res.ok) {
const data = await res.json();
pencegahanKriminalitasState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pencegahanKriminalitasState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pencegahanKriminalitasState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pencegahanKriminalitasState.delete.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Pencegahan kriminalitas berhasil dihapus"
);
await pencegahanKriminalitasState.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus pencegahan kriminalitas"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pencegahan kriminalitas");
} finally {
pencegahanKriminalitasState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
pencegahanKriminalitasState.update.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/${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;
pencegahanKriminalitasState.update.id = data.id;
pencegahanKriminalitasState.update.form = {
pencegahanKriminalitas: {
programKeamanan: {
nama: data.programKeamanan.nama,
deskripsi: data.programKeamanan.deskripsi,
slug: data.programKeamanan.slug,
},
tipsKeamanan: {
judul: data.tipsKeamanan.judul,
konten: data.tipsKeamanan.konten,
slug: data.tipsKeamanan.slug,
},
videoKeamanan: {
judul: data.videoKeamanan.judul,
deskripsi: data.videoKeamanan.deskripsi,
videoUrl: data.videoKeamanan.videoUrl,
slug: data.videoKeamanan.slug,
},
},
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pencegahan kriminalitas"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(
pencegahanKriminalitasState.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pencegahanKriminalitasState.update.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/${pencegahanKriminalitasState.update.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
pencegahanKriminalitas: {
programKeamanan: {
nama: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.nama,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.deskripsi,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.slug,
},
tipsKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.judul,
konten:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.konten,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.slug,
},
videoKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.judul,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.deskripsi,
videoUrl:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.videoUrl,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.slug,
},
},
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update pencegahan kriminalitas");
await pencegahanKriminalitasState.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate pencegahan kriminalitas"
);
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate pencegahan kriminalitas"
);
return false;
} finally {
pencegahanKriminalitasState.update.loading = false;
}
},
reset() {
pencegahanKriminalitasState.update.id = "";
pencegahanKriminalitasState.update.form = { ...defaultForm };
},
},
});
export default pencegahanKriminalitasState;

View File

@@ -0,0 +1,242 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
jarakKeDesa: z.string().min(1, "Jarak minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
jamOperasional: z.string().min(1, "Jam Operasional minimal 1 karakter"),
embedMapUrl: z.string().min(1, "Embed Map Url minimal 1 karakter"),
namaTempatMaps: z.string().min(1, "Nama Tempat Maps minimal 1 karakter"),
alamatMaps: z.string().min(1, "Alamat Maps minimal 1 karakter"),
linkPetunjukArah: z.string().min(1, "Link Petunjuk Arah minimal 1 karakter"),
layananPolsekId: z.string().min(1, "Layanan Polsek Id minimal 1 karakter"),
});
const defaultForm = {
nama: "",
jarakKeDesa: "",
alamat: "",
nomorTelepon: "",
jamOperasional: "",
embedMapUrl: "",
namaTempatMaps: "",
alamatMaps: "",
linkPetunjukArah: "",
layananPolsekId: "",
};
const polsekTerdekatState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(polsekTerdekatState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
polsekTerdekatState.create.loading = true;
const res = await ApiFetch.api.keamanan.polsekterdekat["create"].post(
polsekTerdekatState.create.form
);
if (res.status === 200) {
polsekTerdekatState.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
polsekTerdekatState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
if (res.status === 200) {
polsekTerdekatState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/polsekterdekat/${id}`);
if (res.ok) {
const data = await res.json();
polsekTerdekatState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
polsekTerdekatState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
polsekTerdekatState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
polsekTerdekatState.delete.loading = true;
const response = await fetch(`/api/keamanan/polsekterdekat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Polsek terdekat berhasil dihapus");
await polsekTerdekatState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus polsek terdekat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus polsek terdekat");
} finally {
polsekTerdekatState.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/keamanan/polsekterdekat/${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 = {
nama: data.nama,
jarakKeDesa: data.jarakKeDesa,
alamat: data.alamat,
nomorTelepon: data.nomorTelepon,
jamOperasional: data.jamOperasional,
embedMapUrl: data.embedMapUrl,
namaTempatMaps: data.namaTempatMaps,
alamatMaps: data.alamatMaps,
linkPetunjukArah: data.linkPetunjukArah,
layananPolsekId: data.layananPolsekId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading polsek terdekat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(polsekTerdekatState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
polsekTerdekatState.edit.loading = true;
const response = await fetch(
`/api/keamanan/polsekterdekat/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
jarakKeDesa: this.form.jarakKeDesa,
alamat: this.form.alamat,
nomorTelepon: this.form.nomorTelepon,
jamOperasional: this.form.jamOperasional,
embedMapUrl: this.form.embedMapUrl,
namaTempatMaps: this.form.namaTempatMaps,
alamatMaps: this.form.alamatMaps,
linkPetunjukArah: this.form.linkPetunjukArah,
layananPolsekId: this.form.layananPolsekId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update polsek terdekat");
await polsekTerdekatState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate polsek terdekat");
}
} catch (error) {
console.error("Error updating polsek terdekat:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate polsek terdekat"
);
return false;
} finally {
polsekTerdekatState.edit.loading = false;
}
},
reset() {
polsekTerdekatState.edit.id = "";
polsekTerdekatState.edit.form = { ...defaultForm };
},
},
});
export default polsekTerdekatState;

View File

@@ -0,0 +1,212 @@
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({
judul: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
judul: "",
deskripsi: "",
imageId: "",
};
const tipsKeamananState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(tipsKeamananState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tipsKeamananState.create.loading = true;
const res = await ApiFetch.api.keamanan.menutipskeamanan["create"].post(
tipsKeamananState.create.form
);
if (res.status === 200) {
tipsKeamananState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
tipsKeamananState.create.loading = false;
}
},
resetForm() {
tipsKeamananState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.menutipskeamanan[
"find-many"
].get();
if (res.status === 200) {
tipsKeamananState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/menutipskeamanan/${id}`);
if (res.ok) {
const data = await res.json();
tipsKeamananState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
tipsKeamananState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
tipsKeamananState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
tipsKeamananState.delete.loading = true;
const response = await fetch(
`/api/keamanan/menutipskeamanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Tips keamanan berhasil dihapus");
await tipsKeamananState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus tips keamanan");
}
} catch (error) {
toast.error("Terjadi kesalahan saat menghapus tips keamanan");
console.error("Gagal delete:", error);
} finally {
tipsKeamananState.delete.loading = false;
}
},
},
update: {
id: "",
loading: false,
form: { ...defaultForm },
async load(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
tipsKeamananState.update.loading = true;
const response = await fetch(`/api/keamanan/menutipskeamanan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
judul: data.judul,
deskripsi: data.deskripsi,
imageId: data.imageId || "",
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching data:", error);
toast.error("Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(tipsKeamananState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tipsKeamananState.update.loading = true;
const response = await fetch(
`/api/keamanan/menutipskeamanan/${tipsKeamananState.update.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update tips keamanan");
await tipsKeamananState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update tips keamanan");
}
} catch (error) {
console.error("Error updating data:", error);
toast.error("Gagal update data");
return false;
} finally {
tipsKeamananState.update.loading = false;
}
},
reset() {
tipsKeamananState.update.id = "";
tipsKeamananState.update.form = { ...defaultForm };
},
},
});
export default tipsKeamananState;

View File

@@ -1,339 +1,306 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Introduction */
const templateIntroduction = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
const templateForm = z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
introduction: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
symptom: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
prevention: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
firstAid: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
mythVsFact: z.object({
title: z.string().min(1, "Judul harus diisi"),
mitos: z.string().min(1, "Mitos harus diisi"),
fakta: z.string().min(1, "Fakta harus diisi"),
}),
doctorSign: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
});
type Introduction = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
const defaultForm = {
title: "",
content: "",
introduction: {
content: "",
},
symptom: {
title: "",
content: "",
},
prevention: {
title: "",
content: "",
},
firstAid: {
title: "",
content: "",
},
mythVsFact: {
title: "",
mitos: "",
fakta: "",
},
doctorSign: {
content: "",
},
};
const introduction = proxy({
const artikelKesehatanState = proxy({
create: {
form: {} as Introduction,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateIntroduction.safeParse(introduction.create.form);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
introduction.create.loading = true;
const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"artikel-kesehatan"
].create.post(payload);
if (res.status === 200) {
introduction.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan artikel kesehatan");
this.resetForm();
await artikelKesehatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
introduction.create.loading = false;
this.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IntroductionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.introduction["find-many"].get();
if (res.status === 200) {
introduction.findMany.data = res.data?.data ?? [];
}
}
}
});
/* ======================================================================= */
/* symptom */
const templateSymptom = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Symptom = Prisma.SymptomGetPayload<{
select: {
title: true;
content: true;
};
}>;
const symptom = proxy({
create: {
form: {} as Symptom,
loading: false,
async create() {
const cek = templateSymptom.safeParse(symptom.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
symptom.create.loading = true;
const res = await ApiFetch.api.kesehatan.symptom["create"].post(symptom.create.form);
if (res.status === 200) {
symptom.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
symptom.create.loading = false;
}
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[]
| Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.kesehatan.symptom["find-many"].get();
if (res.status === 200) {
symptom.findMany.data = res.data?.data ?? [];
try {
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)["artikel-kesehatan"][
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data artikel kesehatan");
}
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}> | null,
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
artikelKesehatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data artikel kesehatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data artikel kesehatan");
return;
}
const result = await res.json();
const data = result.data;
artikelKesehatanState.edit.id = data.id;
artikelKesehatanState.edit.form = {
title: data.title,
content: data.content,
introduction: {
content: data.introduction.content,
},
symptom: {
title: data.symptom.title,
content: data.symptom.content,
},
prevention: {
title: data.prevention.title,
content: data.prevention.content,
},
firstAid: {
title: data.firstaid.title,
content: data.firstaid.content,
},
mythVsFact: {
title: data.mythvsfact.title,
mitos: data.mythvsfact.mitos,
fakta: data.mythvsfact.fakta,
},
doctorSign: {
content: data.doctorsign.content,
},
};
},
async submit() {
const cek = templateForm.safeParse(artikelKesehatanState.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
artikelKesehatanState.edit.loading = true;
const payload = {
title: artikelKesehatanState.edit.form.title,
content: artikelKesehatanState.edit.form.content,
introduction: {
content: artikelKesehatanState.edit.form.introduction.content,
},
symptom: {
title: artikelKesehatanState.edit.form.symptom.title,
content: artikelKesehatanState.edit.form.symptom.content,
},
prevention: {
title: artikelKesehatanState.edit.form.prevention.title,
content: artikelKesehatanState.edit.form.prevention.content,
},
firstAid: {
title: artikelKesehatanState.edit.form.firstAid.title,
content: artikelKesehatanState.edit.form.firstAid.content,
},
mythVsFact: {
title: artikelKesehatanState.edit.form.mythVsFact.title,
mitos: artikelKesehatanState.edit.form.mythVsFact.mitos,
fakta: artikelKesehatanState.edit.form.mythVsFact.fakta,
},
doctorSign: {
content: artikelKesehatanState.edit.form.doctorSign.content,
},
};
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/${artikelKesehatanState.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 artikel kesehatan");
await artikelKesehatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
artikelKesehatanState.edit.loading = false;
}
},
resetForm() {
artikelKesehatanState.edit.id = "";
artikelKesehatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
try {
artikelKesehatanState.delete.loading = true;
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result.success) {
toast.success("Artikel kesehatan berhasil dihapus");
await artikelKesehatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
artikelKesehatanState.delete.loading = false;
}
},
},
});
/* ======================================================================= */
/* Prevention */
const templatePrevention = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Prevention = Prisma.PreventionGetPayload<{
select: {
title: true;
content: true;
};
}>;
const prevention = proxy({
create: {
form: {} as Prevention,
loading: false,
async create() {
const cek = templatePrevention.safeParse(prevention.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prevention.create.loading = true;
const res = await ApiFetch.api.kesehatan.prevention["create"].post(prevention.create.form);
if (res.status === 200) {
prevention.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prevention.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PreventionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.prevention["find-many"].get();
if (res.status === 200) {
prevention.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* First Aid */
const templateFirstAid = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FirstAid = Prisma.FirstAidGetPayload<{
select: {
title: true;
content: true;
};
}>;
const firstAid = proxy({
create: {
form: {} as FirstAid,
loading: false,
async create() {
const cek = templateFirstAid.safeParse(firstAid.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
firstAid.create.loading = true;
const res = await ApiFetch.api.kesehatan.firstaid["create"].post(firstAid.create.form);
if (res.status === 200) {
firstAid.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
firstAid.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FirstAidGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.firstaid["find-many"].get();
if (res.status === 200) {
firstAid.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Myth vs Fact */
const templateMythFact = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
mitos: z.string().min(3, "Mitos minimal 3 karakter"),
fakta: z.string().min(3, "Fakta minimal 3 karakter"),
})
type MythFact = Prisma.MythVsFactGetPayload<{
select: {
title: true;
mitos: true;
fakta: true;
};
}>;
const mythFact = proxy({
create: {
form: {} as MythFact,
loading: false,
async create() {
const cek = templateMythFact.safeParse(mythFact.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
mythFact.create.loading = true;
const res = await ApiFetch.api.kesehatan.mythvsfact["create"].post(mythFact.create.form);
if (res.status === 200) {
mythFact.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
mythFact.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.MythVsFactGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.mythvsfact["find-many"].get();
if (res.status === 200) {
mythFact.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Doctor Sign */
const templateDoctorSign = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type DoctorSign = Prisma.DoctorSignGetPayload<{
select: {
content: true
}
}>
const doctorSign = proxy({
create: {
form: {} as DoctorSign,
loading: false,
async create() {
const cek = templateDoctorSign.safeParse(doctorSign.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
doctorSign.create.loading = true;
const res = await ApiFetch.api.kesehatan.doctor_sign["create"].post(doctorSign.create.form);
if (res.status === 200) {
doctorSign.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
doctorSign.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DoctorSignGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.doctor_sign["find-many"].get();
if (res.status === 200) {
doctorSign.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
const stateArtikelKesehatan = proxy({
introduction,
symptom,
prevention,
firstAid,
mythFact,
doctorSign
})
export default stateArtikelKesehatan
export default artikelKesehatanState;

View File

@@ -39,15 +39,15 @@ const grafikkepuasan = proxy({
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(grafikkepuasan.create.form);
if (res.status === 200) {
const uuid = res.data?.data?.uuid;
if (uuid) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.findMany.load();
return uuid;
return id;
}
}
toast.error("failed create");
@@ -77,9 +77,9 @@ const grafikkepuasan = proxy({
data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true }
}> | null,
async load(uuid: string) {
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${uuid}`);
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
if (res.ok) {
const data = await res.json();
grafikkepuasan.findUnique.data = data.data ?? null;
@@ -94,14 +94,14 @@ const grafikkepuasan = proxy({
},
},
update: {
uuid: "",
id: "",
form: {...defaultForm},
loading: false,
async byId() {
},
async submit() {
const uuid = this.uuid;
if (!uuid) {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
@@ -114,7 +114,7 @@ const grafikkepuasan = proxy({
}
try {
this.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/${uuid}`, {
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
@@ -143,14 +143,14 @@ const grafikkepuasan = proxy({
},
delete: {
loading: false,
async byId(uuid: string) {
if (!uuid) {
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${uuid}`, {
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,362 +6,307 @@ import { proxy } from "valtio";
import { z } from "zod";
/* Informasi Kegiatan */
const templateInformasiKegiatan = z.object({
name: z.string().min(3, "Name minimal 3 karakter"),
tanggal: z.string().min(3, "Tanggal minimal 3 karakter"),
waktu: z.string().min(3, "Waktu minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
const templateForm = z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
informasiJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
waktu: z.string().min(1, "Waktu minimal 1 karakter"),
lokasi: z.string().min(1, "Lokasi minimal 1 karakter"),
}),
deskripsiJadwalKegiatan: z.object({
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
}),
layananJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
syaratKetentuanJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
dokumenJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
pendaftaranJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
namaOrangtua: z.string().min(1, "Nama Orangtua minimal 1 karakter"),
nomor: z.string().min(1, "Nomor minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
catatan: z.string().min(1, "Catatan minimal 1 karakter"),
}),
});
type InformasiKegiatan = Prisma.InformasiJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
waktu: true;
lokasi: true;
};
}>;
const defaultForm = {
content: "",
informasiJadwalKegiatan: {
name: "",
tanggal: "",
waktu: "",
lokasi: "",
},
deskripsiJadwalKegiatan: {
deskripsi: "",
},
layananJadwalKegiatan: {
content: "",
},
syaratKetentuanJadwalKegiatan: {
content: "",
},
dokumenJadwalKegiatan: {
content: "",
},
pendaftaranJadwalKegiatan: {
name: "",
tanggal: "",
namaOrangtua: "",
nomor: "",
alamat: "",
catatan: "",
},
};
const informasiKegiatan = proxy({
const jadwalkegiatanState = proxy({
create: {
form: {} as InformasiKegiatan,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateInformasiKegiatan.safeParse(
informasiKegiatan.create.form
);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
informasiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"create"
].post(informasiKegiatan.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
].create.post(payload);
if (res.status === 200) {
informasiKegiatan.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan jadwal kegiatan");
this.resetForm();
await jadwalkegiatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
informasiKegiatan.create.loading = false;
this.loading = false;
}
},
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.InformasiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"find-many"
].get();
if (res.status === 200) {
informasiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Deskripsi Kegiatan */
const templateDeskripsiKegiatan = z.object({
deskripsi: z.string().min(3, "Content minimal 3 karakter"),
});
type DeskripsiKegiatan = Prisma.DeskripsiJadwalKegiatanGetPayload<{
select: { deskripsi: true };
}>;
const deskripsiKegiatan = proxy({
create: {
form: {} as DeskripsiKegiatan,
loading: false,
async create() {
const cek = templateDeskripsiKegiatan.safeParse(
deskripsiKegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
async load() {
try {
deskripsiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"create"
].post(deskripsiKegiatan.create.form);
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
]["find-many"].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.load();
return toast.success("success create");
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data jadwal kegiatan");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
deskripsiKegiatan.create.loading = false;
this.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DeskripsiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"find-many"
].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Layanan Tersedia */
const templateLayananTersedia = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type LayananTersedia = Prisma.LayananJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const layanantersedia = proxy({
create: {
form: {} as LayananTersedia,
findUnique: {
data: null as Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
syaratketentuanjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
};
}> | null,
loading: false,
async create() {
const cek = templateLayananTersedia.safeParse(
layanantersedia.create.form
);
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (res.ok) {
const data = await res.json();
jadwalkegiatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data jadwal kegiatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data jadwal kegiatan");
return;
}
const result = await res.json();
const data = result.data;
jadwalkegiatanState.edit.id = data.id;
jadwalkegiatanState.edit.form = {
content: data.content,
informasiJadwalKegiatan: {
name: data.informasijadwalkegiatan.name,
tanggal: data.informasijadwalkegiatan.tanggal,
waktu: data.informasijadwalkegiatan.waktu,
lokasi: data.informasijadwalkegiatan.lokasi,
},
layananJadwalKegiatan: {
content: data.layananjadwalkegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: data.deskripsijadwalkegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: data.syaratketentuanjadwalkegiatan.content,
},
dokumenJadwalKegiatan: {
content: data.dokumenjadwalkegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: data.pendaftaranjadwalkegiatan.name,
tanggal: data.pendaftaranjadwalkegiatan.tanggal,
namaOrangtua: data.pendaftaranjadwalkegiatan.namaOrangtua,
nomor: data.pendaftaranjadwalkegiatan.nomor,
alamat: data.pendaftaranjadwalkegiatan.alamat,
catatan: data.pendaftaranjadwalkegiatan.catatan,
},
};
},
async submit() {
const cek = templateForm.safeParse(jadwalkegiatanState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
layanantersedia.create.loading = true;
const res = await ApiFetch.api.kesehatan.layanantersedia["create"].post(
layanantersedia.create.form
);
if (res.status === 200) {
layanantersedia.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
layanantersedia.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.LayananJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.layanantersedia[
"find-many"
].get();
if (res.status === 200) {
layanantersedia.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
jadwalkegiatanState.edit.loading = true;
const payload = {
content: jadwalkegiatanState.edit.form.content,
informasiJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
lokasi: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
},
layananJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan.content,
},
dokumenJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
namaOrangtua: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.namaOrangtua,
nomor: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
alamat: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
catatan: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
},
};
/* Syarat dan Ketentuan */
const templateSyaratKetentuan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type SyaratKetentuan = Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const syaratketentuan = proxy({
create: {
form: {} as SyaratKetentuan,
loading: false,
async create() {
const cek = templateSyaratKetentuan.safeParse(
syaratketentuan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
syaratketentuan.create.loading = true;
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"create"
].post(syaratketentuan.create.form);
if (res.status === 200) {
syaratketentuan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
syaratketentuan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"find-many"
].get();
if (res.status === 200) {
syaratketentuan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Dokumen Yang Diperlukan */
const templateDokumenDiperlukan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type DokumenDiperlukan = Prisma.DokumenJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const dokumenjadwalkegiatan = proxy({
create: {
form: {} as DokumenDiperlukan,
loading: false,
async create() {
const cek = templateDokumenDiperlukan.safeParse(
dokumenjadwalkegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
dokumenjadwalkegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"create"
].post(dokumenjadwalkegiatan.create.form);
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
dokumenjadwalkegiatan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DokumenJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"find-many"
].get();
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Pendaftaran */
const templatePendaftaran = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
tanggal: z.string().min(1),
namaOrangtua: z.string().min(3, "Nama minimal 3 karakter"),
nomor: z.string().min(9, "Nama minimal 9 karakter"),
alamat: z.string().min(7, "Alamat minimal 7 karakter"),
catatan: z.string().min(3, "Catatan minimal 3 karakter"),
})
type Pendaftaran = Prisma.PendaftaranJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
namaOrangtua: true;
nomor: true;
alamat: true;
catatan: true;
}
}>
const pendaftaranjadwal = proxy({
create: {
form: {} as Pendaftaran,
loading: false,
async create() {
const cek = templatePendaftaran.safeParse(pendaftaranjadwal.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pendaftaranjadwal.create.loading = true;
const res = await ApiFetch.api.kesehatan.pendaftaranJadwalKegiatan["create"].post(
pendaftaranjadwal.create.form);
if (res.status === 200) {
pendaftaranjadwal.findMany.load();
return toast.success("success create")
const res = await fetch(
`/api/kesehatan/jadwal-kegiatan/${jadwalkegiatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
return toast.error("failed create")
} catch (error) {
console.log((error as Error).message)
);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update jadwal kegiatan");
await jadwalkegiatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
pendaftaranjadwal.create.loading = false;
jadwalkegiatanState.edit.loading = false;
}
},
resetForm() {
jadwalkegiatanState.edit.id = "";
jadwalkegiatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string){
try {
jadwalkegiatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Jadwal kegiatan berhasil dihapus");
await jadwalkegiatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
jadwalkegiatanState.delete.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PendaftaranJadwalKegiatanGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
pendaftaranJadwalKegiatan["find-many"].get();
if(res.status === 200) {
pendaftaranjadwal.findMany.data = res.data?.data ?? [];
}
}
}
})
const stateJadwalKegiatan = proxy({
informasiKegiatan,
deskripsiKegiatan,
layanantersedia,
syaratketentuan,
dokumenjadwalkegiatan,
pendaftaranjadwal
});
export default stateJadwalKegiatan;
export default jadwalkegiatanState;

View File

@@ -34,23 +34,25 @@ const persentasekelahiran = proxy({
async create() {
const cek = templatePersentase.safeParse(persentasekelahiran.create.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => `${v.path.join(".")}`).join("\n")}] required`;
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
persentasekelahiran.create.loading = true;
const res = await ApiFetch.api.kesehatan.persentasekelahiran["create"].post(
persentasekelahiran.create.form
);
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"create"
].post(persentasekelahiran.create.form);
if (res.status === 200) {
const uuid = res.data?.data?.uuid;
if (uuid) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
persentasekelahiran.create.form = { ...defaultForm };
persentasekelahiran.findMany.load();
return uuid;
return id;
}
}
toast.error("failed create");
@@ -65,24 +67,27 @@ const persentasekelahiran = proxy({
},
findMany: {
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}>[] | null,
data: null as
| Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.persentasekelahiran["find-many"].get();
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"find-many"
].get();
if (res.status === 200) {
persentasekelahiran.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}> | null,
async load(uuid: string) {
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/persentasekelahiran/${uuid}`);
const res = await fetch(`/api/kesehatan/persentasekelahiran/${id}`);
if (res.ok) {
const data = await res.json();
persentasekelahiran.findUnique.data = data.data ?? null;
@@ -97,85 +102,90 @@ const persentasekelahiran = proxy({
},
},
update: {
uuid: "",
form: { ...defaultForm },
loading: false,
async submit() {
const uuid = this.uuid;
if (!uuid) {
toast.warn("UUID tidak valid");
return null;
}
const formData = {
tahun: this.form.tahun,
kematianKasar: this.form.kematianKasar,
kelahiranKasar: this.form.kelahiranKasar,
kematianBayi: this.form.kematianBayi,
};
const cek = templatePersentase.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/persentasekelahiran/${uuid}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
update: {
id: "",
form: { ...defaultForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
toast.success("Berhasil update data!");
await persentasekelahiran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data persentase kelahiran");
throw error;
} finally {
this.loading = false;
}
},
},
const formData = {
tahun: this.form.tahun,
kematianKasar: this.form.kematianKasar,
kelahiranKasar: this.form.kelahiranKasar,
kematianBayi: this.form.kematianBayi,
};
const cek = templatePersentase.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/persentasekelahiran/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await persentasekelahiran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data persentase kelahiran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(uuid: string) {
if (!uuid) return toast.warn("UUID tidak valid");
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
persentasekelahiran.delete.loading = true;
const response = await fetch(`/api/kesehatan/persentasekelahiran/del/${uuid}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const response = await fetch(
`/api/kesehatan/persentasekelahiran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Persentase kelahiran berhasil dihapus");
toast.success(
result.message || "Persentase kelahiran berhasil dihapus"
);
await persentasekelahiran.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus persentase kelahiran");
toast.error(
result?.message || "Gagal menghapus persentase kelahiran"
);
}
} catch (error) {
console.error("Gagal delete:", error);

View File

@@ -0,0 +1,221 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
icon: "",
};
const pengelolaanSampahState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(pengelolaanSampahState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengelolaanSampahState.create.loading = true;
const res = await ApiFetch.api.lingkungan.pengelolaansampah["create"].post(
pengelolaanSampahState.create.form
);
if (res.status === 200) {
pengelolaanSampahState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
pengelolaanSampahState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
pengelolaanSampahState.findMany.loading = true; // Use the full path to access the property
pengelolaanSampahState.findMany.page = page;
try {
const res = await ApiFetch.api.lingkungan.pengelolaansampah["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pengelolaanSampahState.findMany.data = res.data.data || [];
pengelolaanSampahState.findMany.total = res.data.total || 0;
pengelolaanSampahState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load pengelolaan sampah:",
res.data?.message
);
pengelolaanSampahState.findMany.data = [];
pengelolaanSampahState.findMany.total = 0;
pengelolaanSampahState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
pengelolaanSampahState.findMany.data = [];
pengelolaanSampahState.findMany.total = 0;
pengelolaanSampahState.findMany.totalPages = 1;
} finally {
pengelolaanSampahState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/lingkungan/pengelolaansampah/${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,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/lingkungan/pengelolaansampah/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await pengelolaanSampahState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data pengelolaan sampah");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ProgramKreatifGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/pengelolaansampah/${id}`);
if (res.ok) {
const data = await res.json();
pengelolaanSampahState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pengelolaanSampahState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
pengelolaanSampahState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengelolaanSampahState.delete.loading = true;
const response = await fetch(`/api/lingkungan/pengelolaansampah/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "pengelolaan sampah berhasil dihapus");
await pengelolaanSampahState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengelolaan sampah");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengelolaan sampah");
} finally {
pengelolaanSampahState.delete.loading = false;
}
},
},
});
export default pengelolaanSampahState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -16,17 +17,9 @@ const defaultForm = {
tanggal: "",
};
type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{
select: {
jenisInformasi: true;
deskripsi: true;
tanggal: true;
};
}>;
const daftarInformasiPublik = proxy({
create: {
form: {} as DaftarInformasi,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateDaftarInformasi.safeParse(
@@ -56,15 +49,38 @@ const daftarInformasiPublik = proxy({
},
},
findMany: {
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get();
if (res.status === 200) {
daftarInformasiPublik.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
daftarInformasiPublik.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
daftarInformasiPublik.findMany.data = res.data.data || [];
daftarInformasiPublik.findMany.total = res.data.total || 0;
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load daftar informasi publik:", res.data?.message);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading daftar informasi publik:", error);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
} finally {
daftarInformasiPublik.findMany.loading = false;
}
},
},
@@ -186,7 +202,9 @@ const daftarInformasiPublik = proxy({
}
try {
daftarInformasiPublik.edit.loading = true;
const formattedTanggal = this.form.tanggal
? new Date(this.form.tanggal).toISOString()
: undefined;
const response = await fetch(
`/api/ppid/daftarinformasipublik/${this.id}`,
{
@@ -197,7 +215,7 @@ const daftarInformasiPublik = proxy({
body: JSON.stringify({
jenisInformasi: this.form.jenisInformasi,
deskripsi: this.form.deskripsi,
tanggal: this.form.tanggal,
tanggal: formattedTanggal,
}),
}
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -9,71 +10,75 @@ const templateGrafikJenisKelamin = z.object({
perempuan: z.string().min(1, "Data perempuan harus diisi"),
});
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
select: {
id: true;
laki: true;
perempuan: true;
};
}>;
const defaultForm: Omit<GrafikJenisKelamin, 'id'> & { id?: string } = {
const defaultForm = {
laki: "",
perempuan: "",
};
const grafikBerdasarkanJenisKelamin = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikJenisKelamin.safeParse(
grafikBerdasarkanJenisKelamin.create.form
);
async create(){
const cek = templateGrafikJenisKelamin.safeParse(grafikBerdasarkanJenisKelamin.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const err = cek.error.issues.map((i) => i.message).join("\n");
toast.error(err);
return;
}
try {
grafikBerdasarkanJenisKelamin.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"create"
].post(grafikBerdasarkanJenisKelamin.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanJenisKelamin.create.form = {
laki: "",
perempuan: "",
};
grafikBerdasarkanJenisKelamin.findMany.load();
return id;
}
toast.success("Grafik berdasarkan jenis kelamin berhasil ditambahkan");
await grafikBerdasarkanJenisKelamin.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin");
} finally {
grafikBerdasarkanJenisKelamin.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanJenisKelamin.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanJenisKelamin.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanJenisKelamin.findMany.data = res.data.data || [];
grafikBerdasarkanJenisKelamin.findMany.total = res.data.total || 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan jenis kelamin:", res.data?.message);
grafikBerdasarkanJenisKelamin.findMany.data = [];
grafikBerdasarkanJenisKelamin.findMany.total = 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
grafikBerdasarkanJenisKelamin.findMany.data = [];
grafikBerdasarkanJenisKelamin.findMany.total = 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
} finally {
grafikBerdasarkanJenisKelamin.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,17 +12,7 @@ const templateGrafikResponden = z.object({
tidakbaik: z.string().min(1, "Data tidak baik harus diisi"),
});
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
select: {
id: true;
sangatbaik: true;
baik: true;
kurangbaik: true;
tidakbaik: true;
};
}>;
const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
const defaultForm = {
sangatbaik: "",
baik: "",
kurangbaik: "",
@@ -30,7 +21,7 @@ const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
const grafikBerdasarkanResponden = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikResponden.safeParse(
@@ -48,40 +39,52 @@ const grafikBerdasarkanResponden = proxy({
"create"
].post(grafikBerdasarkanResponden.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanResponden.create.form = {
sangatbaik: "",
baik: "",
kurangbaik: "",
tidakbaik: "",
};
grafikBerdasarkanResponden.findMany.load();
return id;
}
toast.success("Grafik berdasarkan responden berhasil ditambahkan");
await grafikBerdasarkanResponden.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan responden");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan responden");
} finally {
grafikBerdasarkanResponden.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanRespondenGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanResponden.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanResponden.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanResponden.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanResponden.findMany.data = res.data.data || [];
grafikBerdasarkanResponden.findMany.total = res.data.total || 0;
grafikBerdasarkanResponden.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan responden:", res.data?.message);
grafikBerdasarkanResponden.findMany.data = [];
grafikBerdasarkanResponden.findMany.total = 0;
grafikBerdasarkanResponden.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafikBerdasarkanResponden:", error);
grafikBerdasarkanResponden.findMany.data = [];
grafikBerdasarkanResponden.findMany.total = 0;
grafikBerdasarkanResponden.findMany.totalPages = 1;
} finally {
grafikBerdasarkanResponden.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,17 +12,7 @@ const templateGrafikUmur = z.object({
lansia: z.string().min(1, "Data lansia harus diisi"),
});
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
select: {
id: true;
remaja: true;
dewasa: true;
orangtua: true;
lansia: true;
};
}>;
const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
const defaultForm = {
remaja: "",
dewasa: "",
orangtua: "",
@@ -30,7 +21,7 @@ const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
const grafikBerdasarkanUmur = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikUmur.safeParse(
@@ -70,18 +61,38 @@ const grafikBerdasarkanUmur = proxy({
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanUmur.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanUmur.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanUmur.findMany.data = res.data.data || [];
grafikBerdasarkanUmur.findMany.total = res.data.total || 0;
grafikBerdasarkanUmur.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan umur:", res.data?.message);
grafikBerdasarkanUmur.findMany.data = [];
grafikBerdasarkanUmur.findMany.total = 0;
grafikBerdasarkanUmur.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan umur:", error);
grafikBerdasarkanUmur.findMany.data = [];
grafikBerdasarkanUmur.findMany.total = 0;
grafikBerdasarkanUmur.findMany.totalPages = 1;
} finally {
grafikBerdasarkanUmur.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -9,22 +10,14 @@ const templateGrafikHasilKepuasanMasyarakat = z.object({
kepuasan: z.string().min(1, "Kepuasan harus diisi"),
});
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
select: {
id: true;
label: true;
kepuasan: true;
};
}>;
const defaultForm: Omit<GrafikHasilKepuasanMasyarakat, 'id'> & { id?: string } = {
const defaultForm = {
label: "",
kepuasan: "",
};
const grafikHasilKepuasanMasyarakat = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(
@@ -38,42 +31,52 @@ const grafikHasilKepuasanMasyarakat = proxy({
}
try {
grafikHasilKepuasanMasyarakat.create.loading = true;
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(
grafikHasilKepuasanMasyarakat.create.form
);
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(grafikHasilKepuasanMasyarakat.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikHasilKepuasanMasyarakat.create.form = {
label: "",
kepuasan: "",
};
grafikHasilKepuasanMasyarakat.findMany.load();
return id;
}
toast.success("Grafik hasil kepuasan masyarakat berhasil ditambahkan");
await grafikHasilKepuasanMasyarakat.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik hasil kepuasan masyarakat");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik hasil kepuasan masyarakat");
} finally {
grafikHasilKepuasanMasyarakat.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IndeksKepuasanMasyarakatGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat[
"find-many"
].get();
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikHasilKepuasanMasyarakat.findMany.loading = true; // Use the full path to access the property
grafikHasilKepuasanMasyarakat.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data.data || [];
grafikHasilKepuasanMasyarakat.findMany.total = res.data.total || 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik hasil kepuasan masyarakat:", res.data?.message);
grafikHasilKepuasanMasyarakat.findMany.data = [];
grafikHasilKepuasanMasyarakat.findMany.total = 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik hasil kepuasan masyarakat:", error);
grafikHasilKepuasanMasyarakat.findMany.data = [];
grafikHasilKepuasanMasyarakat.findMany.total = 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = 1;
} finally {
grafikHasilKepuasanMasyarakat.findMany.loading = false;
}
},
},

View File

@@ -18,8 +18,6 @@ import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";

View File

@@ -35,9 +35,7 @@ function DetailBerita() {
if (!beritaState.berita.findUnique.data) {
return (
<Stack py={10}>
{Array.from({ length: 10 }).map((_, k) => (
<Skeleton key={k} h={40} />
))}
<Skeleton h={40} />
</Stack>
)
}

View File

@@ -1,104 +1,135 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import stateDashboardBerita from '../../_state/desa/berita';
function Page() {
function Berita() {
const [search, setSearch] = useState("");
return (
<Box>
<Grid>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>Berita</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius={"lg"}
placeholder='pencarian'
leftSection={<IconSearch size={20} />}
/>
</Paper>
</GridCol>
</Grid>
<BeritaList />
<HeaderSearch
title='Berita'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListBerita search={search} />
</Box>
);
}
function BeritaList() {
function ListBerita({ search }: { search: string }) {
const beritaState = useProxy(stateDashboardBerita)
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = beritaState.berita.findMany;
if (!beritaState.berita.findMany.data) {
// Fetch pertama kali
useShallowEffect(() => {
load(page, 10); // awal page = 1
}, [page]);
const filteredData = (data || []).filter((item) => {
const keyword = search.toLowerCase();
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
item.judul.toLowerCase().includes(keyword) ||
item.kategoriBerita?.name.toLowerCase().includes(keyword)
);
});
if (loading || !data) {
return <Skeleton h={500} />;
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors["white-1"]} p={"md"}>
<Stack>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
<Text fz={"xl"} fw={"bold"}>
List Berita
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push("/admin/desa/berita/create")} bg={colors['blue-button']}>
<Button
onClick={() => router.push("/admin/desa/berita/create")}
bg={colors["blue-button"]}
>
<IconCircleDashedPlus size={25} />
</Button>
</GridCol>
</Grid>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<Table
striped
withRowBorders
withTableBorder
style={{ minWidth: "700px" }}
>
<TableThead>
<TableTr>
<TableTh w={250}>Judul</TableTh>
<TableTh w={250}>Kategori</TableTh>
<TableTh w={250}>Image</TableTh>
<TableTh w={200}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody >
{beritaState.berita.findMany.data?.map((item) => (
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd >
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
<Text truncate="end" fz={"sm"}>
{item.judul}
</Text>
</Box>
</TableTd>
<TableTd >{item.kategoriBerita?.name}</TableTd>
<TableTd>{item.kategoriBerita?.name}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
<TableTd>
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
<Button
bg={"green"}
onClick={() =>
router.push(`/admin/desa/berita/${item.id}`)
}
>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table> </Box>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
);
}
export default Page;
export default Berita;

View File

@@ -3,12 +3,30 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';
import HeaderSearch from '../../../_com/header';
import { useState } from 'react';
function Foto() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListFoto search={search} />
</Box>
);
}
function ListFoto({ search }: { search: string }) {
const fotoState = useProxy(stateGallery.foto)
const router = useRouter();
@@ -16,6 +34,14 @@ function Foto() {
fotoState.findMany.load()
}, [])
const filteredData = (fotoState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!fotoState.findMany.data) {
return (
<Box py={10}>
@@ -43,7 +69,7 @@ function Foto() {
</TableTr>
</TableThead>
<TableTbody>
{fotoState.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>

View File

@@ -3,12 +3,30 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';
import HeaderSearch from '../../../_com/header';
import { useState } from 'react';
function Video() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListVideo search={search} />
</Box>
);
}
function ListVideo({ search }: { search: string }) {
const videoState = useProxy(stateGallery.video)
const router = useRouter();
@@ -16,6 +34,14 @@ function Video() {
videoState.findMany.load()
}, [])
const filteredData = (videoState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!videoState.findMany.data) {
return (
<Box py={10}>
@@ -43,7 +69,7 @@ function Video() {
</TableTr>
</TableThead>
<TableTbody>
{videoState.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
@@ -7,8 +7,26 @@ import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import HeaderSearch from '../../../_com/header';
function SuratKeterangan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListSuratKeterangan search={search} />
</Box>
);
}
function ListSuratKeterangan({ search }: { search: string }) {
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
const router = useRouter()
@@ -16,6 +34,14 @@ function SuratKeterangan() {
suratKeteranganState.findMany.load()
}, [])
const filteredData = (suratKeteranganState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!suratKeteranganState.findMany.data) {
return (
<Stack py={10}>
@@ -43,7 +69,7 @@ function SuratKeterangan() {
</TableTr>
</TableThead>
<TableTbody>
{suratKeteranganState.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
@@ -7,8 +7,26 @@ import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import HeaderSearch from '../../../_com/header';
function PelayananTelunjukSakti() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
}
function ListPelayananTelunjukSakti({ search }: { search: string }) {
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
@@ -16,6 +34,14 @@ function PelayananTelunjukSakti() {
telunjukSaktiState.findMany.load()
}, [])
const filteredData = (telunjukSaktiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!telunjukSaktiState.findMany.data) {
return (
<Stack py={10}>
@@ -42,7 +68,7 @@ function PelayananTelunjukSakti() {
</TableTr>
</TableThead>
<TableTbody>
{telunjukSaktiState.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd><Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /></TableTd>

View File

@@ -6,15 +6,41 @@ import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../_com/jusulListTab';
import JudulListTab from '../../_com/judulListTab';
import { useState } from 'react';
import HeaderSearch from '../../_com/header';
function Penghargaan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPenghargaan search={search} />
</Box>
);
}
function ListPenghargaan({ search }: { search: string }) {
const state = useProxy(penghargaanState)
const router = useRouter()
useShallowEffect(() => {
state.findMany.load()
}, [])
const filteredData = (state.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!state.findMany.data) {
return(
<Stack py={10}>
@@ -41,7 +67,7 @@ function Penghargaan() {
</TableTr>
</TableThead>
<TableTbody>
{state.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>

View File

@@ -11,21 +11,25 @@ import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
import { useState } from 'react';
function Page() {
function Pengumuman() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Pengumuman'
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<PengumumanList />
<ListPengumuman search={search} />
</Box>
);
}
function PengumumanList() {
function ListPengumuman({ search }: { search: string }) {
const pengumumanState = useProxy(stateDesaPengumuman)
const router = useRouter()
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
@@ -33,7 +37,6 @@ function PengumumanList() {
pengumumanState.pengumuman.findMany.load()
}, [])
const router = useRouter()
const handleHapus = () => {
if (selectedId) {
@@ -43,6 +46,14 @@ function PengumumanList() {
}
}
const filteredData = (pengumumanState.pengumuman.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
item.CategoryPengumuman?.name.toLowerCase().includes(keyword)
);
});
if (!pengumumanState.pengumuman.findMany.data) {
return (
<Stack py={10}>
@@ -76,7 +87,7 @@ function PengumumanList() {
</TableTr>
</TableThead>
<TableTbody >
{pengumumanState.pengumuman.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd >
<Box w={100}>
@@ -108,4 +119,4 @@ function PengumumanList() {
)
}
export default Page;
export default Pengumuman;

View File

@@ -9,29 +9,40 @@ import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import potensiDesaState from '../../_state/desa/potensi';
import { useState } from 'react';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Potensi Desa'
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi />
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi() {
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
useShallowEffect(() => {
potensiState.findMany.load()
}, [])
const router = useRouter()
const filteredData = (potensiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori.toLowerCase().includes(keyword)
);
});
if (!potensiState.findMany.data) {
return (
@@ -60,7 +71,7 @@ function ListPotensi() {
</TableTr>
</TableThead>
<TableTbody>
{potensiState.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>

View File

@@ -0,0 +1,73 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabs({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "APB Desa",
value: "apbdesa",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
},
{
label: "Pendapatan",
value: "pendapatan",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
},
{
label: "Belanja",
value: "belanja",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
},
{
label: "Pembiayaan",
value: "pembiayaan",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
},
];
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}>Pendapatan Asli 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 LayoutTabs;

View File

@@ -0,0 +1,234 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
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 EditAPBDesa() {
const apbState = useProxy(PendapatanAsliDesa.ApbDesa);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
tahun: apbState.update.form.tahun || '',
pendapatanIds: apbState.update.form.pendapatanIds || [],
belanjaIds: apbState.update.form.belanjaIds || [],
pembiayaanIds: apbState.update.form.pembiayaanIds || [],
});
// Load APB desa by id saat pertama kali
useEffect(() => {
const loadAPBdesa = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await apbState.update.load(id);
if (data) {
setFormData({
tahun: data.tahun || 0,
pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [],
belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
});
}
} catch (error) {
console.error("Error loading APBdesa:", error);
toast.error("Gagal memuat data APBdesa");
}
};
loadAPBdesa();
}, [params?.id]); // ✅ hapus beritaState dari dependency
const handleSubmit = async () => {
try {
// Update global state with form data
apbState.update.form = {
...apbState.update.form,
tahun: Number(formData.tahun),
pendapatanIds: formData.pendapatanIds,
belanjaIds: formData.belanjaIds,
pembiayaanIds: formData.pembiayaanIds,
};
await apbState.update.update();
toast.success("APB Desa berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa");
} catch (error) {
console.error("Error updating APBdesa:", error);
toast.error("Terjadi kesalahan saat memperbarui APBdesa");
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit APB Desa</Title>
<TextInput
type='number'
value={formData.tahun}
onChange={(val) => {
setFormData({ ...formData, tahun: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
placeholder="masukkan tahun"
/>
<SelectPendapatan
selectedIds={formData.pendapatanIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, pendapatanIds: ids });
}}
/>
<SelectBelanja
selectedIds={formData.belanjaIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, belanjaIds: ids });
}}
/>
<SelectPembiayaan
selectedIds={formData.pembiayaanIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, pembiayaanIds: ids });
}}
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
/* Select Pendapatan */
interface SelectPendapatanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPendapatan({
selectedIds = [],
onSelectionChange,
}: SelectPendapatanProps) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
useShallowEffect(() => {
pendapatanState.findMany.load().then(() => {
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
});
}, []);
if (!pendapatanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
data={pendapatanState.findMany.data.map(p => ({
value: p.id,
label: p.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pendapatan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Belanja */
interface SelectBelanjaProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectBelanja({
selectedIds = [],
onSelectionChange,
}: SelectBelanjaProps) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
useShallowEffect(() => {
belanjaState.findMany.load().then(() => {
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
});
}, []);
if (!belanjaState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
data={belanjaState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih belanja..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Pembiayaan */
interface SelectPembiayaanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPembiayaan({
selectedIds = [],
onSelectionChange,
}: SelectPembiayaanProps) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
useShallowEffect(() => {
pembiayaanState.findMany.load().then(() => {
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
});
}, []);
if (!pembiayaanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
data={pembiayaanState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pembiayaan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
}
export default EditAPBDesa;

View File

@@ -0,0 +1,152 @@
'use client'
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function DetailAPBDesa() {
const apbState = useProxy(PendapatanAsliDesa.ApbDesa)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
console.log("PARAM ID:", params?.id)
apbState.findUnique.load(params?.id as string)
}, [])
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
const handleHapus = () => {
if (selectedId) {
apbState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
}
}
if (!apbState.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail APB Desa</Text>
{apbState.findUnique.data ? (
<Paper key={apbState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Tahun</Text>
<Text fz={"lg"}>{apbState.findUnique.data?.tahun}</Text>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Pembiayaan</Text>
{(apbState.findUnique.data?.pembiayaan || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Belanja</Text>
{(apbState.findUnique.data?.belanja || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Pendapatan</Text>
{(apbState.findUnique.data?.pendapatan || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (apbState.findUnique.data) {
setSelectedId(apbState.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={apbState.delete.loading || !apbState.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (apbState.findUnique.data) {
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${apbState.findUnique.data.id}/edit`);
}
}}
disabled={!apbState.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus APB Desa ini?'
/>
</Box>
);
}
export default DetailAPBDesa;

View File

@@ -0,0 +1,190 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateAPBDesa() {
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
const router = useRouter()
const resetForm = () => {
apbDesaState.create.form = {
tahun: 0,
pendapatanIds: [],
belanjaIds: [],
pembiayaanIds: [],
}
}
const handleSubmit = async () => {
await apbDesaState.create.submit()
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Create APB Desa</Title>
<TextInput
type='number'
value={apbDesaState.create.form.tahun}
onChange={(val) => {
apbDesaState.create.form.tahun = Number(val.target.value);
}}
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
placeholder="masukkan tahun"
/>
<SelectPendapatan
selectedIds={apbDesaState.create.form.pendapatanIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.pendapatanIds = ids;
}}
/>
<SelectBelanja
selectedIds={apbDesaState.create.form.belanjaIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.belanjaIds = ids;
}}
/>
<SelectPembiayaan
selectedIds={apbDesaState.create.form.pembiayaanIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.pembiayaanIds = ids;
}}
/>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
/* Select Pendapatan */
interface SelectPendapatanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPendapatan({
selectedIds = [],
onSelectionChange,
}: SelectPendapatanProps) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
useShallowEffect(() => {
pendapatanState.findMany.load().then(() => {
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
});
}, []);
if (!pendapatanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
data={pendapatanState.findMany.data.map(p => ({
value: p.id,
label: p.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pendapatan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Belanja */
interface SelectBelanjaProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectBelanja({
selectedIds = [],
onSelectionChange,
}: SelectBelanjaProps) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
useShallowEffect(() => {
belanjaState.findMany.load().then(() => {
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
});
}, []);
if (!belanjaState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
data={belanjaState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih belanja..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Pembiayaan */
interface SelectPembiayaanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPembiayaan({
selectedIds = [],
onSelectionChange,
}: SelectPembiayaanProps) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
useShallowEffect(() => {
pembiayaanState.findMany.load().then(() => {
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
});
}, []);
if (!pembiayaanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
data={pembiayaanState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pembiayaan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
}
export default CreateAPBDesa;

View File

@@ -0,0 +1,106 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
function APBDesa() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='APB Desa'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListAPBDesa search={search} />
</Box>
);
}
function ListAPBDesa({ search }: { search: string }) {
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
const router = useRouter();
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
useShallowEffect(() => {
apbDesaState.findMany.load();
}, [])
const filteredData = (apbDesaState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.tahun.toString().toLowerCase().includes(keyword) ||
item.pembiayaan.map((item) => item.value.toString()).includes(keyword) ||
item.belanja.map((item) => item.value.toString()).includes(keyword) ||
item.pendapatan.map((item) => item.value.toString()).includes(keyword)
);
});
if (!apbDesaState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List APB Desa'
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Tahun</TableTh>
<TableTh>Pembiayaan</TableTh>
<TableTh>Belanja</TableTh>
<TableTh>Pendapatan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.tahun}</TableTd>
<TableTd>{formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>{formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>{formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>
<Button
bg={"green"}
onClick={() =>
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`)
}
>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default APBDesa;

View File

@@ -0,0 +1,112 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, 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 EditBelanja() {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
name: belanjaState.update.form.name || '',
value: belanjaState.update.form.value || '',
});
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
useEffect(() => {
const loadBelanja = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await belanjaState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
value: data.value || '',
});
}
} catch (error) {
console.error("Error loading belanja:", error);
toast.error("Gagal memuat data belanja");
}
};
loadBelanja();
}, [params?.id]);
const handleSubmit = async () => {
try {
belanjaState.update.form = {
...belanjaState.update.form,
name: formData.name,
value: Number(formData.value),
}
await belanjaState.update.update();
toast.success("Jenis Belanja berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja");
} catch (error) {
console.error("Error updating jenis belanja:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis belanja");
}
}
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 Jenis Pendapatan</Title>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({ ...formData, name: val.target.value });
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
placeholder='Masukkan nama Jenis Pendapatan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
value={formatRupiah(formData.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue });
}}
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditBelanja;

View File

@@ -0,0 +1,77 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateBelanja() {
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
const router = useRouter()
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
const resetForm = () => {
belanjaState.create.form = {
name: "",
value: 0,
}
}
const handleSubmit = async () => {
await belanjaState.create.submit();
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja")
}
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}>Create Jenis Belanja</Title>
<TextInput
value={belanjaState.create.form.name}
onChange={(val) => {
belanjaState.create.form.name = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Belanja</Text>}
placeholder='Masukkan nama jenis belanja'
/>
<TextInput
type='text'
value={formatRupiah(belanjaState.create.form.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
belanjaState.create.form.value = cleanValue;
}}
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateBelanja;

View File

@@ -0,0 +1,139 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
function Belanja() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Belanja'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListBelanja search={search} />
</Box>
);
}
function ListBelanja({ search }: { search: string }) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
const totalBelanja = belanjaState.findMany.data.reduce((sum, item) => sum + item.value, 0);
const handleDelete = () => {
if (selectedId) {
belanjaState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
belanjaState.findMany.load()
}
}
useShallowEffect(() => {
belanjaState.findMany.load();
}, [])
const filteredData = (belanjaState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.value.toString().toLowerCase().includes(keyword)
);
});
if (!belanjaState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Belanja'
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Nilai</TableTh>
<TableTh>Persentase</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{formatRupiah(item.value)}</TableTd>
<TableTd>{((item.value / totalBelanja) * 100).toFixed(0)}%</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={belanjaState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
<TableTr>
<TableTd colSpan={4}>
<Text fw={'bold'}>Total</Text>
</TableTd>
<TableTd>
{formatRupiah(belanjaState.findMany.data.reduce((total, item) => total + item.value, 0))}
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus belanja ini?'
/>
</Box>
);
}
export default Belanja;

View File

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

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
PADesa-pendapatan-asli-desa
</div>
);
}
export default Page;

View File

@@ -0,0 +1,112 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, 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 EditPembiayaan() {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
name: pembiayaanState.update.form.name || '',
value: pembiayaanState.update.form.value || '',
});
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
useEffect(() => {
const loadPembiayaan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await pembiayaanState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
value: data.value || '',
});
}
} catch (error) {
console.error("Error loading pembiayaan:", error);
toast.error("Gagal memuat data pembiayaan");
}
};
loadPembiayaan();
}, [params?.id]);
const handleSubmit = async () => {
try {
pembiayaanState.update.form = {
...pembiayaanState.update.form,
name: formData.name,
value: Number(formData.value),
}
await pembiayaanState.update.update();
toast.success("Jenis Pembiayaan berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan");
} catch (error) {
console.error("Error updating jenis pembiayaan:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis pembiayaan");
}
}
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 Jenis Pembiayaan</Title>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({ ...formData, name: val.target.value });
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
placeholder='Masukkan nama Jenis Pembiayaan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
value={formatRupiah(formData.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue });
}}
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditPembiayaan;

View File

@@ -0,0 +1,78 @@
'use client'
import React from 'react';
import { useProxy } from 'valtio/utils';
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import { useRouter } from 'next/navigation';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
function CreatePembiayaan() {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
const router = useRouter()
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
const resetForm = () => {
pembiayaanState.create.form = {
name: "",
value: 0,
}
}
const handleSubmit = async () => {
await pembiayaanState.create.submit();
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan")
}
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}>Create Jenis Pembiayaan</Title>
<TextInput
value={pembiayaanState.create.form.name}
onChange={(val) => {
pembiayaanState.create.form.name = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
placeholder='Masukkan nama jenis pembiayaan'
/>
<TextInput
type='text'
value={formatRupiah(pembiayaanState.create.form.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
pembiayaanState.create.form.value = cleanValue;
}}
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreatePembiayaan;

View File

@@ -0,0 +1,138 @@
'use client'
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import React, { useState } from 'react';
import HeaderSearch from '../../../_com/header';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
import { useProxy } from 'valtio/utils';
import { useRouter } from 'next/navigation';
import { useShallowEffect } from '@mantine/hooks';
import colors from '@/con/colors';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function Pembiayaan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Pembiayaan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPembiayaan search={search} />
</Box>
);
}
function ListPembiayaan({ search }: { search: string }) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
const totalPembiayaan = pembiayaanState.findMany.data.reduce((sum, item) => sum + item.value, 0);
const handleDelete = () => {
if (selectedId) {
pembiayaanState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
pembiayaanState.findMany.load()
}
}
useShallowEffect(() => {
pembiayaanState.findMany.load();
}, [])
const filteredData = (pembiayaanState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.value.toString().toLowerCase().includes(keyword)
);
});
if (!pembiayaanState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Pembiayaan'
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Nilai</TableTh>
<TableTh>Persentase</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{formatRupiah(item.value)}</TableTd>
<TableTd>{((item.value / totalPembiayaan) * 100).toFixed(0)}%</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={pembiayaanState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
<TableTr>
<TableTd colSpan={4}>
<Text fw={'bold'}>Total</Text>
</TableTd>
<TableTd>
{formatRupiah(pembiayaanState.findMany.data.reduce((total, item) => total + item.value, 0))}
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus pembiayaan ini?'
/>
</Box>
)
}
export default Pembiayaan;

View File

@@ -0,0 +1,112 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, 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 EditPendapatan() {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
name: pendapatanState.update.form.name || '',
value: pendapatanState.update.form.value || '',
});
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
useEffect(() => {
const loadPendapatan = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await pendapatanState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
value: data.value || '',
});
}
} catch (error) {
console.error("Error loading pendapatan:", error);
toast.error("Gagal memuat data pendapatan");
}
};
loadPendapatan();
}, [params?.id]);
const handleSubmit = async () => {
try {
pendapatanState.update.form = {
...pendapatanState.update.form,
name: formData.name,
value: Number(formData.value),
}
await pendapatanState.update.update();
toast.success("Jenis Pendapatan berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan");
} catch (error) {
console.error("Error updating jenis pendapatan:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis pendapatan");
}
}
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 Jenis Pendapatan</Title>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({ ...formData, name: val.target.value });
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
placeholder='Masukkan nama Jenis Pendapatan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
value={formatRupiah(formData.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue });
}}
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditPendapatan;

View File

@@ -0,0 +1,77 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreatePendapatan() {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
const router = useRouter()
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
const resetForm = () => {
pendapatanState.create.form = {
name: "",
value: 0,
}
}
const handleSubmit = async () => {
await pendapatanState.create.submit();
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan")
}
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}>Create Jenis Pendapatan</Title>
<TextInput
value={pendapatanState.create.form.name}
onChange={(val) => {
pendapatanState.create.form.name = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
placeholder='Masukkan nama jenis pendapatan'
/>
<TextInput
type='text'
value={formatRupiah(pendapatanState.create.form.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
pendapatanState.create.form.value = cleanValue;
}}
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreatePendapatan;

View File

@@ -0,0 +1,136 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
function Pendapatan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Pendapatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPendapatan search={search} />
</Box>
);
}
function ListPendapatan({ search }: { search: string }) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
const handleDelete = () => {
if (selectedId) {
pendapatanState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
pendapatanState.findMany.load()
}
}
useShallowEffect(() => {
pendapatanState.findMany.load();
}, [])
const filteredData = (pendapatanState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.value.toString().toLowerCase().includes(keyword)
);
});
if (!pendapatanState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Pendapatan'
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Nilai</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{formatRupiah(item.value)}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={pendapatanState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
<TableTr>
<TableTd colSpan={4}>
<Text fw={'bold'}>Total</Text>
</TableTd>
<TableTd>
{formatRupiah(pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0))}
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus pendapatan ini?'
/>
</Box>
);
}
export default Pendapatan;

View File

@@ -0,0 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
function EditDemografiPekerjaan() {
const router = useRouter()
const params = useParams() as { id: string }
const stateDemografi = useProxy(demografiPekerjaan)
const id = params.id
useEffect(() => {
if (!id) return;
stateDemografi.update.id = id;
stateDemografi.findUnique.load(id)
.then(() => {
const data = stateDemografi.findUnique.data;
if (data) {
stateDemografi.update.form = {
pekerjaan: String(data.pekerjaan || ''),
lakiLaki: Number(data.lakiLaki || 0),
perempuan: Number(data.perempuan || 0)
};
}
})
.catch(error => {
console.error('Error loading data:', error);
toast.error('Gagal memuat data');
});
}, [id]);
// Di handleSubmit, ubah menjadi:
const handleSubmit = async () => {
try {
stateDemografi.update.id = id;
await stateDemografi.update.submit();
toast.success('Data berhasil diperbarui');
router.push('/admin/ekonomi/demografi-pekerjaan');
} catch (error) {
console.error('Error updating data:', error);
toast.error('Gagal memperbarui data');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Edit Demografi Pekerjaan</Title>
<TextInput
label="Pekerjaan"
placeholder="masukkan pekerjaan"
value={stateDemografi.update.form.pekerjaan}
onChange={(val) => {
stateDemografi.update.form.pekerjaan = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah Pekerja Laki - Laki"
type="number"
placeholder="masukkan jumlah pekerja laki - laki"
value={stateDemografi.update.form.lakiLaki}
onChange={(val) => {
stateDemografi.update.form.lakiLaki = Number(val.currentTarget.value);
}}
/>
<TextInput
label="Jumlah Pekerja Perempuan"
type="number"
placeholder="masukkan jumlah pekerja perempuan"
value={stateDemografi.update.form.perempuan}
onChange={(val) => {
stateDemografi.update.form.perempuan = Number(val.currentTarget.value);
}}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Simpan Perubahan
</Button>
</Stack>
</Paper>
</Box>
)
}
export default EditDemografiPekerjaan;

View File

@@ -1,41 +1,90 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
function CreateDemografiPekerjaan() {
const router = useRouter();
const stateDemografi = useProxy(demografiPekerjaan);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter()
const resetForm = () => {
stateDemografi.create.form = {
pekerjaan: "",
lakiLaki: 0,
perempuan: 0,
}
}
const handleSubmit = async () => {
const id = await stateDemografi.create.create();
if (id) {
const idStr = String(id);
await stateDemografi.findUnique.load(idStr);
if (stateDemografi.findUnique.data) {
setChartData([stateDemografi.findUnique.data]);
}
}
resetForm();
router.push("/admin/ekonomi/demografi-pekerjaan");
}
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Demografi Pekerjaan</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Pekerjaan</Text>}
placeholder='Masukkan pekerjaan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Pekerja Laki - Laki</Text>}
placeholder='Masukkan jumlah pekerja laki - laki'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Pekerja Perempuan</Text>}
placeholder='Masukkan jumlah pekerja perempuan'
/>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Demografi Pekerjaan</Title>
<Stack gap={"xs"}>
<TextInput
label="Pekerjaan"
type="text"
value={stateDemografi.create.form.pekerjaan}
placeholder="Masukkan pekerjaan"
onChange={(val) => {
stateDemografi.create.form.pekerjaan = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah Pekerja Laki - Laki"
type="number"
value={stateDemografi.create.form.lakiLaki}
placeholder="Masukkan jumlah pekerja laki - laki"
onChange={(val) => {
stateDemografi.create.form.lakiLaki = Number(val.currentTarget.value);
}}
/>
<TextInput
label="Jumlah Pekerja Perempuan"
type="number"
value={stateDemografi.create.form.perempuan}
placeholder="Masukkan jumlah pekerja perempuan"
onChange={(val) => {
stateDemografi.create.form.perempuan = Number(val.currentTarget.value);
}}
/>
<Group>
<Button
bg={colors['blue-button']}
mt={10}
onClick={handleSubmit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
</Box>
);
}

View File

@@ -1,62 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailDemografiPekerjaan() {
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 Demografi Pekerjaan</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"}>Pekerjaan</Text>
<Text>Karyawan</Text>
</Box>
<Box>
<Text fw={"bold"}>Jumlah Pekerja Laki - Laki</Text>
<Text>200</Text>
</Box>
<Box>
<Text fw={"bold"}>Jumlah Pekerja Perempuan</Text>
<Text>100</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/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 DetailDemografiPekerjaan;

View File

@@ -1,42 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function EditDemografiPekerjaan() {
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 Demografi Pekerjaan</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Pekerjaan</Text>}
placeholder='Masukkan pekerjaan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Pekerja Laki - Laki</Text>}
placeholder='Masukkan jumlah pekerja laki - laki'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Pekerja Perempuan</Text>}
placeholder='Masukkan jumlah pekerja perempuan'
/>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditDemografiPekerjaan;

View File

@@ -1,26 +1,83 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { BarChart } from '@mantine/charts';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import { useRouter } from 'next/navigation';
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
import demografiPekerjaan from '../../_state/ekonomi/demografi-pekerjaan';
function DemografiPekerjaan() {
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Demografi Pekerjaan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListDemografiPekerjaan/>
<ListDemografiPekerjaan search={search} />
</Box>
);
}
function ListDemografiPekerjaan() {
function ListDemografiPekerjaan({ search }: { search: string }) {
type DemografiPekerjaan = {
id: string;
pekerjaan: string;
lakiLaki: number;
perempuan: number;
}
const router = useRouter();
const stateDemografi = useProxy(demografiPekerjaan)
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const handleDelete = () => {
if (selectedId) {
stateDemografi.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateDemografi.findMany.load()
}
}
useShallowEffect(() => {
setMounted(true)
stateDemografi.findMany.load()
}, [])
useEffect(() => {
setMounted(true);
if (stateDemografi.findMany.data) {
setChartData(stateDemografi.findMany.data.map((item) => ({
id: item.id,
pekerjaan: item.pekerjaan,
lakiLaki: Number(item.lakiLaki),
perempuan: Number(item.perempuan),
})));
}
}, [stateDemografi.findMany.data]);
const filteredData = (stateDemografi.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.pekerjaan.toLowerCase().includes(keyword) ||
item.lakiLaki.toString().toLowerCase().includes(keyword) ||
item.perempuan.toString().toLowerCase().includes(keyword)
);
});
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -34,23 +91,75 @@ function ListDemografiPekerjaan() {
<TableTh>Pekerjaan</TableTh>
<TableTh>Jumlah Pekerja Laki - Laki</TableTh>
<TableTh>Jumlah Pekerja Perempuan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Karyawan</TableTd>
<TableTd>200</TableTd>
<TableTd>100</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.pekerjaan}</TableTd>
<TableTd>{item.lakiLaki}</TableTd>
<TableTd>{item.perempuan}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={stateDemografi.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<Box w={{ base: '100%', md: '30%' }}>
<BarChart
h={450}
data={chartData}
dataKey="pekerjaan"
type="stacked"
series={[
{ name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
]}
/>
</Box>
)}
</Paper>
</Box>
)}
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus demografi pekerjaan ini?'
/>
</Box>
);
}

View File

@@ -1,38 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function CreateJumlahPendudukMiskin() {
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}>Create Jumlah Penduduk Miskin</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tahun</Text>}
placeholder='Masukkan tahun'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Penduduk Miskin</Text>}
placeholder='Masukkan jumlah penduduk miskin'
/>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateJumlahPendudukMiskin;

View File

@@ -1,58 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailJumlahPendudukMiskin() {
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 Jumlah Penduduk Miskin</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"}>Tahun</Text>
<Text>2024</Text>
</Box>
<Box>
<Text fw={"bold"}>Jumlah Penduduk Miskin</Text>
<Text>100</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/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 DetailJumlahPendudukMiskin;

View File

@@ -1,38 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
function EditJumlahPendudukMiskin() {
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 Jumlah Penduduk Miskin</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tahun</Text>}
placeholder='Masukkan tahun'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Jumlah Penduduk Miskin</Text>}
placeholder='Masukkan jumlah penduduk miskin'
/>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditJumlahPendudukMiskin;

View File

@@ -1,56 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import { useRouter } from 'next/navigation';
function JumlahPendudukMiskin() {
return (
<Box>
<HeaderSearch
title='Jumlah Penduduk Miskin'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
/>
<ListJumlahPendudukMiskin/>
</Box>
);
}
function ListJumlahPendudukMiskin() {
const router = useRouter();
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Jumlah Penduduk Miskin'
href='/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Tahun</TableTh>
<TableTh>Jumlah Penduduk Miskin</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>2024</TableTd>
<TableTd>100</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default JumlahPendudukMiskin;

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