Compare commits

..

5 Commits

Author SHA1 Message Date
23c955597e feat(api): add KategoriKegiatan CRUD API and register module - bump to 0.1.46
- Add KategoriKegiatan CRUD (create, findMany, findUnique, update, del)
- Register KategoriKegiatan in Desa API router
- Support soft delete for categories
2026-04-30 11:33:29 +08:00
28a22e8d77 chore(merge): merge sosial seeder branch into stg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 11:02:14 +08:00
67efe6ce35 feat(seeder): add seeders for Sosial dashboard APIs
- Add seed_ringkasan_kesehatan.ts (ibuHamil=87, balita=342, stunting=12)
- Add seed_beasiswa_config.ts (tahun 2025/2026, dana Rp 1.2M)
- Add seed_kegiatan_desa.ts (KategoriKegiatan + KegiatanDesa incl. Budaya)
- Add kategori-kegiatan.json + kegiatan-desa.json data files
- Update posyandu.json: 1 → 8 posyandu (Mawar, Melati, Dahlia, Anggrek, dll)
- Update program-kesehatan.json: add persentase field + 4 stat entries
- Update seed_program_kesehatan.ts: include persentase in upsert
- Update seed.ts: import + call new seeders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 11:00:44 +08:00
b39f9b39da feat(sosial): add schema fields + API endpoints for Sosial dashboard - bump to 0.1.45
- Add persentase field to ProgramKesehatan (for health stats bar chart)
- Add BeasiswaConfig model (dana tersalurkan + tahun ajaran)
- Add RingkasanKesehatanDesa model (ibu hamil, balita, alert stunting)
- Add KegiatanDesa CRUD API (GET /api/desa/kegiatandesa/find-many?kategori=Budaya)
- Add BeasiswaConfig API (GET/PUT /api/pendidikan/beasiswa/beasiswaconfig/...)
- Add RingkasanKesehatan API (GET/PUT /api/kesehatan/ringkasankesehatan/...)
- Migration: 20260430000000_add_sosial_fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 00:16:25 +08:00
6041cdf552 chore: bump version to 0.1.44 for stg deploy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 14:35:03 +08:00
32 changed files with 868 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.43",
"version": "0.1.46",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,48 @@
import prisma from "@/lib/prisma";
import { loadJsonData } from "../../load-json";
const kategoriKegiatanJson = loadJsonData("desa/kegiatan-desa/kategori-kegiatan.json");
const kegiatanDesaJson = loadJsonData("desa/kegiatan-desa/kegiatan-desa.json");
export async function seedKegiatanDesa() {
console.log("🔄 Seeding Kategori Kegiatan Desa...");
for (const k of kategoriKegiatanJson) {
await prisma.kategoriKegiatan.upsert({
where: { id: k.id },
update: { nama: k.nama },
create: { id: k.id, nama: k.nama },
});
console.log(` ✅ Kategori: ${k.nama}`);
}
console.log("🔄 Seeding Kegiatan Desa...");
for (const item of kegiatanDesaJson) {
await prisma.kegiatanDesa.upsert({
where: { id: item.id },
update: {
judul: item.judul,
deskripsiSingkat: item.deskripsiSingkat,
deskripsiLengkap: item.deskripsiLengkap,
tanggal: new Date(item.tanggal),
lokasi: item.lokasi,
partisipan: item.partisipan,
kategoriKegiatanId: item.kategoriKegiatanId,
},
create: {
id: item.id,
judul: item.judul,
deskripsiSingkat: item.deskripsiSingkat,
deskripsiLengkap: item.deskripsiLengkap,
tanggal: new Date(item.tanggal),
lokasi: item.lokasi,
partisipan: item.partisipan,
kategoriKegiatanId: item.kategoriKegiatanId,
},
});
console.log(` ✅ Kegiatan: ${item.judul}`);
}
console.log("🎉 Kegiatan Desa seed selesai");
}

View File

@@ -28,6 +28,7 @@ export async function seedProgramKesehatan() {
name: p.name,
deskripsiSingkat: p.deskripsiSingkat,
deskripsi: p.deskripsi,
persentase: p.persentase ?? 0,
imageId,
},
create: {
@@ -35,6 +36,7 @@ export async function seedProgramKesehatan() {
name: p.name,
deskripsiSingkat: p.deskripsiSingkat,
deskripsi: p.deskripsi,
persentase: p.persentase ?? 0,
imageId,
},
});

View File

@@ -0,0 +1,24 @@
import prisma from "@/lib/prisma";
const SINGLETON_ID = "ringkasan_kesehatan_desa_001";
export async function seedRingkasanKesehatan() {
console.log("🔄 Seeding Ringkasan Kesehatan Desa...");
await prisma.ringkasanKesehatanDesa.upsert({
where: { id: SINGLETON_ID },
update: {
ibuHamilAkh: 87,
balitaTerdaftar: 342,
alertStunting: 12,
},
create: {
id: SINGLETON_ID,
ibuHamilAkh: 87,
balitaTerdaftar: 342,
alertStunting: 12,
},
});
console.log("✅ Ringkasan Kesehatan Desa seeded");
}

View File

@@ -0,0 +1,22 @@
import prisma from "@/lib/prisma";
const SINGLETON_ID = "beasiswa_config_desa_001";
export async function seedBeasiswaConfig() {
console.log("🔄 Seeding Beasiswa Config...");
await prisma.beasiswaConfig.upsert({
where: { id: SINGLETON_ID },
update: {
tahunAjaran: "2025/2026",
danaTersalurkan: BigInt(1200000000),
},
create: {
id: SINGLETON_ID,
tahunAjaran: "2025/2026",
danaTersalurkan: BigInt(1200000000),
},
});
console.log("✅ Beasiswa Config seeded");
}

View File

@@ -0,0 +1,22 @@
[
{
"id": "katbudaya000001",
"nama": "Budaya"
},
{
"id": "katsosia000001",
"nama": "Sosial"
},
{
"id": "katolahraga0001",
"nama": "Olahraga"
},
{
"id": "katkeagamaan01",
"nama": "Keagamaan"
},
{
"id": "katpemberday01",
"nama": "Pemberdayaan Masyarakat"
}
]

View File

@@ -0,0 +1,52 @@
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567801",
"judul": "Hari Kesaktian Pancasila",
"deskripsiSingkat": "Peringatan Hari Kesaktian Pancasila di Balai Desa Darmasaba sebagai momentum menguatkan nilai-nilai Pancasila dalam kehidupan bermasyarakat.",
"deskripsiLengkap": "Pemerintah Desa Darmasaba menyelenggarakan upacara peringatan Hari Kesaktian Pancasila yang dihadiri oleh perangkat desa, tokoh masyarakat, perwakilan pemuda, dan warga desa. Kegiatan ini sebagai bentuk penghormatan atas perjuangan bangsa dan komitmen untuk terus mengamalkan nilai-nilai Pancasila dalam kehidupan sehari-hari di lingkungan desa.",
"tanggal": "2025-10-01T00:00:00.000Z",
"lokasi": "Balai Desa",
"partisipan": 250,
"kategoriKegiatanId": "katbudaya000001"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567802",
"judul": "Festival Budaya Desa",
"deskripsiSingkat": "Festival tahunan menampilkan kesenian dan budaya lokal Bali sebagai upaya pelestarian warisan budaya Desa Darmasaba.",
"deskripsiLengkap": "Festival Budaya Desa Darmasaba merupakan agenda tahunan yang menampilkan berbagai pertunjukan seni tradisional Bali seperti tari Kecak, Legong, Barong, serta pameran kerajinan tangan dan kuliner khas desa. Festival ini bertujuan untuk melestarikan warisan budaya leluhur, memperkenalkan kekayaan budaya kepada generasi muda, dan meningkatkan daya tarik wisata budaya Desa Darmasaba.",
"tanggal": "2026-05-20T00:00:00.000Z",
"lokasi": "Lapangan Desa",
"partisipan": 500,
"kategoriKegiatanId": "katbudaya000001"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567803",
"judul": "Perayaan HUT Desa",
"deskripsiSingkat": "Perayaan hari ulang tahun Desa Darmasaba dengan berbagai kegiatan seni budaya dan olahraga yang melibatkan seluruh lapisan masyarakat.",
"deskripsiLengkap": "Hari Ulang Tahun Desa Darmasaba dirayakan dengan serangkaian kegiatan meriah meliputi upacara adat, pertunjukan seni budaya Bali, lomba olahraga tradisional, dan pameran produk unggulan desa. Perayaan ini menjadi ajang mempererat kebersamaan warga, mengenang sejarah desa, dan merayakan pencapaian pembangunan yang telah diraih bersama.",
"tanggal": "2026-08-17T00:00:00.000Z",
"lokasi": "Balai Desa",
"partisipan": 600,
"kategoriKegiatanId": "katbudaya000001"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567804",
"judul": "Gotong Royong Pembersihan Desa",
"deskripsiSingkat": "Kegiatan gotong royong rutin membersihkan lingkungan desa sebagai wujud kebersamaan dan kepedulian masyarakat terhadap kebersihan.",
"deskripsiLengkap": "Kegiatan gotong royong pembersihan lingkungan desa dilaksanakan secara rutin setiap bulan dengan melibatkan seluruh warga, kader PKK, karang taruna, dan perangkat desa. Kegiatan ini mencakup pembersihan jalan desa, sungai, tempat ibadah, dan fasilitas umum sebagai bentuk nyata kepedulian masyarakat terhadap kebersihan dan keindahan lingkungan.",
"tanggal": "2026-03-15T00:00:00.000Z",
"lokasi": "Seluruh Wilayah Desa",
"partisipan": 400,
"kategoriKegiatanId": "katsosia000001"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567805",
"judul": "Turnamen Olahraga Antar Banjar",
"deskripsiSingkat": "Turnamen olahraga tahunan antar banjar untuk mempererat tali persaudaraan dan mendorong gaya hidup sehat masyarakat desa.",
"deskripsiLengkap": "Turnamen olahraga antar banjar di Desa Darmasaba menampilkan berbagai cabang olahraga seperti bola voli, bulu tangkis, tenis meja, dan sepak bola. Kegiatan ini diikuti oleh perwakilan dari setiap banjar di desa dan bertujuan untuk menumbuhkan semangat sportivitas, mempererat silaturahmi antar warga, serta mendorong pola hidup sehat melalui olahraga.",
"tanggal": "2026-06-10T00:00:00.000Z",
"lokasi": "Lapangan Desa",
"partisipan": 350,
"kategoriKegiatanId": "katolahraga0001"
}
]

View File

@@ -3,8 +3,57 @@
"id": "cmkanjnmx0006vntz1cn7owpb",
"name": "Posyandu Pudak Amara",
"nomor": "(0361) 8463263",
"deskripsi": "<p>Posyandu Pudak Amara merupakan salah satu posyandu aktif di Desa Darmasaba dan pernah berkompetisi dalam lomba kader dan posyandu berprestasi tingkat Provinsi Bali tahun 2025.</p><p>Kegiatan ini melibatkan kader posyandu serta didampingi pihak desa dan puskesmas setempat untuk meningkatkan pelayanan kesehatan ibu dan anak.</p>",
"jadwalPelayanan": "<p>Setiap bulan pada satu hari tertentu (mis. minggu ke-2): 08:00 12:00 WITA (posyandu balita & ibu hamil)</p>",
"deskripsi": "<p>Posyandu Pudak Amara merupakan salah satu posyandu aktif di Desa Darmasaba dan pernah berkompetisi dalam lomba kader dan posyandu berprestasi tingkat Provinsi Bali tahun 2025.</p>",
"jadwalPelayanan": "Senin, 10 Feb 2026, 08:00 - 11:00 WITA",
"imageName": "TDQReg1lQ73s39crXW0ra-mobile.webp"
},
{
"id": "posyandu_mawar_001",
"name": "Posyandu Mawar",
"nomor": "(0361) 8463264",
"deskripsi": "<p>Posyandu Mawar melayani kesehatan ibu dan anak di wilayah Banjar Mawar, Desa Darmasaba, dengan fokus pada pemantauan tumbuh kembang balita dan kesehatan ibu hamil.</p>",
"jadwalPelayanan": "Senin, 15 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_melati_001",
"name": "Posyandu Melati",
"nomor": "(0361) 8463265",
"deskripsi": "<p>Posyandu Melati berperan aktif dalam pelayanan kesehatan dasar masyarakat di Banjar Melati, meliputi imunisasi, penimbangan balita, dan konsultasi gizi.</p>",
"jadwalPelayanan": "Selasa, 16 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_dahlia_001",
"name": "Posyandu Dahlia",
"nomor": "(0361) 8463266",
"deskripsi": "<p>Posyandu Dahlia aktif melayani masyarakat Banjar Dahlia dengan program unggulan pemantauan stunting dan pemberian makanan tambahan bagi balita berisiko.</p>",
"jadwalPelayanan": "Rabu, 17 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_anggrek_001",
"name": "Posyandu Anggrek",
"nomor": "(0361) 8463267",
"deskripsi": "<p>Posyandu Anggrek melayani ibu hamil, ibu menyusui, dan balita di wilayah Banjar Anggrek dengan dukungan tenaga kesehatan dari Puskesmas Abiansemal 3.</p>",
"jadwalPelayanan": "Kamis, 18 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_kamboja_001",
"name": "Posyandu Kamboja",
"nomor": "(0361) 8463268",
"deskripsi": "<p>Posyandu Kamboja hadir untuk mendukung kesehatan masyarakat Banjar Kamboja melalui layanan pemeriksaan rutin, imunisasi lengkap, dan edukasi gizi keluarga.</p>",
"jadwalPelayanan": "Jumat, 19 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_melur_001",
"name": "Posyandu Melur",
"nomor": "(0361) 8463269",
"deskripsi": "<p>Posyandu Melur aktif memberikan layanan kesehatan preventif bagi ibu dan anak di Banjar Melur, termasuk deteksi dini stunting dan pemantauan gizi balita.</p>",
"jadwalPelayanan": "Sabtu, 20 Feb 2026, 08:00 - 11:00 WITA"
},
{
"id": "posyandu_kenanga_001",
"name": "Posyandu Kenanga",
"nomor": "(0361) 8463270",
"deskripsi": "<p>Posyandu Kenanga melayani masyarakat Banjar Kenanga dengan program kesehatan ibu dan anak, pemberian vitamin A, dan konseling laktasi bagi ibu menyusui.</p>",
"jadwalPelayanan": "Senin, 23 Feb 2026, 08:00 - 11:00 WITA"
}
]

View File

@@ -1,9 +1,38 @@
[
{
"id": "prog_kes_imunisasi_001",
"name": "Imunisasi Lengkap",
"deskripsiSingkat": "<p>Persentase balita yang mendapatkan imunisasi lengkap sesuai jadwal di Desa Darmasaba.</p>",
"deskripsi": "<p>Program imunisasi lengkap mencakup vaksin BCG, DPT-HB-Hib, Polio, Campak, dan PCV yang diberikan kepada seluruh balita di Desa Darmasaba melalui posyandu dan puskesmas setempat untuk membangun kekebalan komunitas.</p>",
"persentase": 92
},
{
"id": "prog_kes_pemeriksaan_001",
"name": "Pemeriksaan Rutin",
"deskripsiSingkat": "<p>Persentase ibu hamil dan balita yang melakukan pemeriksaan kesehatan rutin secara berkala.</p>",
"deskripsi": "<p>Pemeriksaan kesehatan rutin dilakukan setiap bulan di posyandu dan puskesmas, mencakup penimbangan berat badan, pengukuran tinggi badan, pemeriksaan tekanan darah ibu hamil, dan konsultasi gizi untuk memantau perkembangan kesehatan masyarakat.</p>",
"persentase": 88
},
{
"id": "prog_kes_gizi_001",
"name": "Gizi Baik",
"deskripsiSingkat": "<p>Persentase balita dengan status gizi baik berdasarkan hasil pemantauan tumbuh kembang.</p>",
"deskripsi": "<p>Program pemantauan gizi balita dilaksanakan melalui posyandu dengan penimbangan bulanan dan pengukuran tinggi badan. Balita dengan status gizi baik menunjukkan berat dan tinggi badan sesuai standar WHO, didukung dengan program pemberian makanan tambahan bagi balita berisiko.</p>",
"persentase": 86
},
{
"id": "prog_kes_stunting_001",
"name": "Target Stunting",
"deskripsiSingkat": "<p>Persentase balita yang teridentifikasi berisiko stunting dan perlu penanganan khusus.</p>",
"deskripsi": "<p>Penanganan stunting di Desa Darmasaba dilakukan melalui deteksi dini, intervensi gizi spesifik, dan intervensi gizi sensitif. Program ini melibatkan kader posyandu, bidan desa, dan puskesmas untuk memantau pertumbuhan balita secara berkala dan memberikan penanganan tepat.</p>",
"persentase": 14
},
{
"id": "cmkawkji50002vn6yzyrlqhh1",
"name": "Gerakan Kulkul PKK dan Posyandu Desa Darmasaba",
"deskripsiSingkat": "<p>Kegiatan bersama PKK dan Posyandu untuk meningkatkan pelayanan kesehatan masyarakat.</p>",
"deskripsi": "<p>Pada hari Minggu, 11 Januari 2025, Pemerintah Desa Darmasaba melalui TP PKK dan TP Posyandu melaksanakan kegiatan Gerakan Kulkul PKK dan Posyandu yang berlangsung serentak di seluruh wilayah Desa Darmasaba untuk memperkuat pelayanan kesehatan dasar dan peningkatan partisipasi masyarakat dalam program Posyandu.</p>",
"persentase": 0,
"imageName": "hLeF0GRFZqDUngZnDMAAk-mobile.webp"
},
{
@@ -11,6 +40,7 @@
"name": "Pendampingan Kunjungan Rumah oleh Puskesmas Abiansemal 3",
"deskripsiSingkat": "<p>Pendataan kesehatan penyandang disabilitas lewat kunjungan rumah di Desa Darmasaba.</p>",
"deskripsi": "<p>Pemerintah Desa Darmasaba bersama Kelian Banjar Dinas dan kader kesehatan mendampingi kegiatan kunjungan rumah yang dilaksanakan oleh Puskesmas Abiansemal 3 pada 21 Juli 2025, difokuskan pada pendataan dan pemantauan kondisi kesehatan penyandang disabilitas di Banjar Bersih, Desa Darmasaba.</p>",
"persentase": 0,
"imageName": "hyyTFi8EApjzFEZ9EvJgB-mobile.webp"
},
{
@@ -18,6 +48,7 @@
"name": "Kegiatan Aksi Sosial Tim Penggerak Posyandu Provinsi Bali di Desa Darmasaba",
"deskripsiSingkat": "<p>Aksi sosial TP Posyandu Bali untuk memperkuat pelayanan posyandu di desa.</p>",
"deskripsi": "<p>Pada 10 Desember 2025, Desa Darmasaba menjadi lokasi pelaksanaan Aksi Sosial Tim Penggerak Posyandu Provinsi Bali yang bertujuan memperkuat pelayanan Posyandu serta meningkatkan kesejahteraan masyarakat, khususnya keluarga dan balita.</p>",
"persentase": 0,
"imageName": "l4qsUEw2JiclGAkkrXp9g-mobile.webp"
},
{
@@ -25,6 +56,7 @@
"name": "Inovasi BAJRA dalam Penanggulangan Rabies",
"deskripsiSingkat": "<p>Program BAJRA untuk penanggulangan rabies di Desa Darmasaba.</p>",
"deskripsi": "<p>Desa Darmasaba mengembangkan inovasi BAJRA (Bersama Jaga Rabies), sebuah program berbasis komunitas untuk penanggulangan rabies yang mengintegrasikan pelaporan cepat masyarakat, edukasi berkelanjutan dan koordinasi lintas sektor antara kesehatan hewan, manusia, dan pemerintahan desa.</p>",
"persentase": 0,
"imageName": "Gc79mlIlGuoRQuTqskFj--mobile.webp"
},
{
@@ -32,6 +64,7 @@
"name": "Posyandu Pudak Amara Berkompetisi",
"deskripsiSingkat": "<p>Partisipasi Posyandu Pudak Amara dalam lomba prestasi Posyandu tingkat provinsi.</p>",
"deskripsi": "<p>Kader Posyandu Pudak Amara Br. Cabe mendapat pendampingan dari Perbekel Darmasaba, Dinas Kesehatan Kab. Badung, Puskesmas Abiansemal III, dan Pustu Desa Darmasaba dalam ajang lomba kader dan Posyandu berprestasi tingkat Provinsi Bali tahun 2025.</p>",
"persentase": 0,
"imageName": "OsMY3AYPyGC_CoN1xUjOn-mobile.webp"
},
{
@@ -39,13 +72,15 @@
"name": "Outbound Kader Posyandu Darmasaba",
"deskripsiSingkat": "<p>Program pembinaan dan pengembangan kapasitas kader Posyandu.</p>",
"deskripsi": "<p>Pemdes Darmasaba melaksanakan kegiatan Outbound Posyandu untuk meningkatkan kapasitas dan wawasan Kader Posyandu se-Desa Darmasaba sebagai bagian dari upaya peningkatan kualitas pelayanan kesehatan dasar di masyarakat.</p>",
"persentase": 0,
"imageName": "M9QlgVKIEfCdY3g4F_tRZ-mobile.webp"
},
{
"id": "cmkdu8ki10004vn4lpbxm2zqo",
"name": "PEMBANGUNAN JAMBAN BAGI MASYARAKAT",
"deskripsiSingkat": "<p>Program pengadaan jamban bagi Masyarakat ini diharapkan menjadi stimulus agar masyarakat peduli terhadap lingkungan sehat sehingga Badung Open Defection Free atau terbebas dari buang air besar di tempat terbuka dapat terwujud.</p>",
"deskripsi": "<p>Desa Darmasaba sebagai desa yang berkomitmen selalu selaras dengan pembangunan Pemerintah Kabupaten Badung pada tahun anggaran 2023 ini turut ambil bagian dalam menyukseskan program Bupati Badung I Nyoman Giri Prasta, S.Sos dalam bidang kesehatan sanitasi masyarakat. Program pengadaan jamban bagi Masyarakat ini diharapkan menjadi stimulus agar masyarakat peduli terhadap lingkungan sehat sehingga Badung Open Defection Free atau terbebas dari buang air besar di tempat terbuka dapat terwujud.</p><p style=\"text-align: justify\">Pemberian bantuan jamban ini dilaksanakan di 11 banjar dengan menyasar 22 keluarga yang memang belum memiliki jamban yang sumber dananya sepenuhnya dari APBDes Darmasaba T. A. 2023. Pembangunan Jamban bagi Masyarakat ini juga menjadi bukti komitmen Pemerintah Desa Darmasaba dalam melaksanakan salah satu visi mewujudkan masyarakat yang sejahtera dan berbudaya untuk menjaga lingkungan yang bersih dan sehat.</p>",
"deskripsi": "<p>Desa Darmasaba sebagai desa yang berkomitmen selalu selaras dengan pembangunan Pemerintah Kabupaten Badung pada tahun anggaran 2023 ini turut ambil bagian dalam menyukseskan program Bupati Badung I Nyoman Giri Prasta, S.Sos dalam bidang kesehatan sanitasi masyarakat.</p>",
"persentase": 0,
"imageName": "6DQbAvn0St-xHdPGW3vpY-mobile.webp"
}
]

View File

@@ -0,0 +1,27 @@
-- Add persentase field to ProgramKesehatan (untuk Statistik Kesehatan bar chart)
ALTER TABLE "ProgramKesehatan" ADD COLUMN "persentase" INTEGER NOT NULL DEFAULT 0;
-- Create BeasiswaConfig (untuk dana tersalurkan + tahun ajaran beasiswa desa)
CREATE TABLE "BeasiswaConfig" (
"id" TEXT NOT NULL,
"tahunAjaran" TEXT NOT NULL,
"danaTersalurkan" BIGINT NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "BeasiswaConfig_pkey" PRIMARY KEY ("id")
);
-- Create RingkasanKesehatanDesa (untuk stat cards: ibu hamil, balita, stunting)
CREATE TABLE "RingkasanKesehatanDesa" (
"id" TEXT NOT NULL,
"ibuHamilAkh" INTEGER NOT NULL DEFAULT 0,
"balitaTerdaftar" INTEGER NOT NULL DEFAULT 0,
"alertStunting" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "RingkasanKesehatanDesa_pkey" PRIMARY KEY ("id")
);

View File

@@ -1213,6 +1213,7 @@ model ProgramKesehatan {
name String
deskripsiSingkat String
deskripsi String
persentase Int @default(0)
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
@@ -2470,3 +2471,24 @@ model PenjualanProduk {
@@index([tanggal])
}
// ========================================= BEASISWA CONFIG ========================================= //
model BeasiswaConfig {
id String @id @default(cuid())
tahunAjaran String
danaTersalurkan BigInt @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isActive Boolean @default(true)
}
// ========================================= RINGKASAN KESEHATAN DESA ========================================= //
model RingkasanKesehatanDesa {
id String @id @default(cuid())
ibuHamilAkh Int @default(0)
balitaTerdaftar Int @default(0)
alertStunting Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isActive Boolean @default(true)
}

View File

@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import prisma from "@/lib/prisma";
import { seedBerita } from "./_seeder_list/desa/berita/seed_berita";
import { seedKegiatanDesa } from "./_seeder_list/desa/seed_kegiatan_desa";
import { seedFoto } from "./_seeder_list/desa/gallery/foto/seed_foto";
import { seedVideo } from "./_seeder_list/desa/gallery/video/seed_video";
import { seedLayanan } from "./_seeder_list/desa/layanan/seed_layanan";
@@ -46,6 +47,7 @@ import { seedProgramKesehatan } from "./_seeder_list/kesehatan/program-kesehatan
import { seedPuskesmas } from "./_seeder_list/kesehatan/puskesmas/seed_puskesmas";
import { seedGrafikKepuasan } from "./_seeder_list/kesehatan/seed_grafik_kepuasan";
import { seedKelahiranKematian } from "./_seeder_list/kesehatan/seed_kelahiran_kematian";
import { seedRingkasanKesehatan } from "./_seeder_list/kesehatan/seed_ringkasan_kesehatan";
import { seedDesaAntiKorupsi } from "./_seeder_list/landing-page/desa-anti-korupsi/seed_desa_anti_korupsi";
import { seedPrestasiDesa } from "./_seeder_list/landing-page/prestasi-desa/seed_prestasi_desa";
import { seedMediaSosial } from "./_seeder_list/landing-page/profil_landing_page/seed_media_sosial";
@@ -59,6 +61,7 @@ import { seedKonservasiAdatBali } from "./_seeder_list/lingkungan/seed_konservas
import { seedPengelolaanSampah } from "./_seeder_list/lingkungan/seed_pengelolaan_sampah";
import { seedProgramPenghijauan } from "./_seeder_list/lingkungan/seed_program_penghijauan";
import { seedBeasiswaPendaftar } from "./_seeder_list/pendidikan/seed_beasiswa_pendaftar";
import { seedBeasiswaConfig } from "./_seeder_list/pendidikan/seed_beasiswa_config";
import { seedBimbinganBelajar } from "./_seeder_list/pendidikan/seed_bimbingan_belajar";
import { seedDataPendidikan } from "./_seeder_list/pendidikan/seed_data_pendidikan";
import { seedDataPerpustakaan } from "./_seeder_list/pendidikan/seed_data_perpustakaan";
@@ -378,6 +381,11 @@ import seedAssets from "./seed_assets";
// ===== PENDIDIKAN =====
await seedKeunggulanProgram();
await seedBeasiswaPendaftar();
await seedBeasiswaConfig();
// ===== SOSIAL DASHBOARD =====
await seedRingkasanKesehatan();
await seedKegiatanDesa();
// ===== DESA =====
await seedMusikDesa();

View File

@@ -13,6 +13,8 @@ import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
import MantanPerbekel from "./profile/profile-mantan-perbekel";
import AjukanPermohonan from "./layanan/ajukan_permohonan";
import Musik from "./musik";
import KegiatanDesa from "./kegiatan-desa";
import KategoriKegiatan from "./kegiatan-desa/kategori-kegiatan";
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
@@ -30,6 +32,8 @@ const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
.use(KategoriPengumuman)
.use(AjukanPermohonan)
.use(Musik)
.use(KegiatanDesa)
.use(KategoriKegiatan)
export default Desa;

View File

@@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function kegiatanDesaCreate(context: Context) {
const body = context.body as any;
try {
const data = await prisma.kegiatanDesa.create({
data: {
judul: body.judul,
deskripsiSingkat: body.deskripsiSingkat,
deskripsiLengkap: body.deskripsiLengkap,
tanggal: new Date(body.tanggal),
lokasi: body.lokasi,
partisipan: Number(body.partisipan) || 0,
kategoriKegiatanId: body.kategoriKegiatanId,
imageId: body.imageId || null,
},
include: { kategoriKegiatan: true, image: true },
});
return { success: true, message: "Kegiatan desa berhasil dibuat", data };
} catch (e) {
console.error("Error di kegiatanDesaCreate:", e);
return { success: false, message: "Gagal membuat kegiatan desa" };
}
}
export default kegiatanDesaCreate;

View File

@@ -0,0 +1,19 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function kegiatanDesaDelete(context: Context) {
const { id } = context.params as { id: string };
try {
await prisma.kegiatanDesa.update({
where: { id },
data: { isActive: false },
});
return { success: true, message: "Kegiatan desa berhasil dihapus" };
} catch (e) {
console.error("Error di kegiatanDesaDelete:", e);
return { success: false, message: "Gagal menghapus kegiatan desa" };
}
}
export default kegiatanDesaDelete;

View File

@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function kegiatanDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const kategori = (context.query.kategori as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
if (search) {
where.OR = [
{ judul: { contains: search, mode: 'insensitive' } },
{ lokasi: { contains: search, mode: 'insensitive' } },
];
}
if (kategori) {
where.kategoriKegiatan = {
nama: { contains: kategori, mode: 'insensitive' },
};
}
try {
const [data, total] = await Promise.all([
prisma.kegiatanDesa.findMany({
where,
include: {
kategoriKegiatan: true,
image: true,
},
skip,
take: limit,
orderBy: { tanggal: 'asc' },
}),
prisma.kegiatanDesa.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kegiatan desa",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di kegiatanDesaFindMany:", e);
return { success: false, message: "Gagal mengambil data kegiatan desa" };
}
}
export default kegiatanDesaFindMany;

View File

@@ -0,0 +1,35 @@
import Elysia, { t } from "elysia";
import kegiatanDesaFindMany from "./find-many";
import kegiatanDesaCreate from "./create";
import kegiatanDesaDelete from "./del";
import kegiatanDesaUpdate from "./updt";
const KegiatanDesa = new Elysia({ prefix: "/kegiatandesa", tags: ["Desa/Kegiatan Desa"] })
.get("/find-many", kegiatanDesaFindMany)
.post("/create", kegiatanDesaCreate, {
body: t.Object({
judul: t.String(),
deskripsiSingkat: t.String(),
deskripsiLengkap: t.String(),
tanggal: t.String(),
lokasi: t.String(),
partisipan: t.Optional(t.Number()),
kategoriKegiatanId: t.String(),
imageId: t.Optional(t.String()),
}),
})
.put("/:id", kegiatanDesaUpdate, {
body: t.Object({
judul: t.String(),
deskripsiSingkat: t.String(),
deskripsiLengkap: t.String(),
tanggal: t.String(),
lokasi: t.String(),
partisipan: t.Optional(t.Number()),
kategoriKegiatanId: t.String(),
imageId: t.Optional(t.String()),
}),
})
.delete("/del/:id", kegiatanDesaDelete);
export default KegiatanDesa;

View File

@@ -0,0 +1,26 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormCreate = {
nama: string;
}
export default async function kategoriKegiatanCreate(context: Context) {
const body = (await context.body) as FormCreate;
try {
const result = await prisma.kategoriKegiatan.create({
data: {
nama: body.nama,
},
});
return {
success: true,
message: "Berhasil membuat kategori kegiatan",
data: result,
};
} catch (error) {
console.error("Error creating kategori kegiatan:", error);
throw new Error("Gagal membuat kategori kegiatan: " + (error as Error).message);
}
}

View File

@@ -0,0 +1,50 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriKegiatanDelete(context: Context) {
try {
const id = context.params?.id as string;
if (!id) {
return Response.json({
success: false,
message: "ID tidak boleh kosong",
}, { status: 400 });
}
// ✅ Cek apakah kategori masih digunakan oleh kegiatan
const kegiatanCount = await prisma.kegiatanDesa.count({
where: {
kategoriKegiatanId: id,
isActive: true,
},
});
if (kegiatanCount > 0) {
return Response.json({
success: false,
message: `Kategori tidak dapat dihapus karena masih digunakan oleh ${kegiatanCount} kegiatan`,
}, { status: 400 });
}
// ✅ Soft delete (bukan hard delete)
await prisma.kategoriKegiatan.update({
where: { id },
data: {
deletedAt: new Date(),
isActive: false,
},
});
return {
success: true,
message: "Kategori kegiatan berhasil dihapus",
};
} catch (error) {
console.error("Delete kategori error:", error);
return Response.json({
success: false,
message: "Gagal menghapus kategori: " + (error instanceof Error ? error.message : 'Unknown error'),
}, { status: 500 });
}
}

View File

@@ -0,0 +1,52 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function kategoriKegiatanFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.kategoriKegiatan.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'asc' },
}),
prisma.kategoriKegiatan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kategori kegiatan dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data kategori kegiatan",
};
}
}
export default kategoriKegiatanFindMany;

View File

@@ -0,0 +1,46 @@
import prisma from "@/lib/prisma";
export default async function kategoriKegiatanFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return {
success: false,
message: "ID is required",
}
}
try {
if (typeof id !== 'string') {
return {
success: false,
message: "ID is required",
}
}
const data = await prisma.kategoriKegiatan.findUnique({
where: { id },
});
if (!data) {
return {
success: false,
message: "Data not found",
}
}
return {
success: true,
message: "Success get kategori kegiatan",
data,
}
} catch (error) {
console.error("Find by ID error:", error);
return {
success: false,
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
}
}
}

View File

@@ -0,0 +1,33 @@
import Elysia, { t } from "elysia";
import kategoriKegiatanCreate from "./create";
import kategoriKegiatanDelete from "./del";
import kategoriKegiatanFindMany from "./findMany";
import kategoriKegiatanFindUnique from "./findUnique";
import kategoriKegiatanUpdate from "./updt";
const KategoriKegiatan = new Elysia({
prefix: "/kategorikegiatan",
tags: ["Desa / Kegiatan Desa / Kategori Kegiatan"],
})
.post("/create", kategoriKegiatanCreate, {
body: t.Object({
nama: t.String(),
}),
})
.get("/findMany", kategoriKegiatanFindMany)
.get("/:id", async (context) => {
const response = await kategoriKegiatanFindUnique(
new Request(context.request)
);
return response;
})
.put("/:id", kategoriKegiatanUpdate, {
body: t.Object({
nama: t.String(),
}),
})
.delete("/del/:id", kategoriKegiatanDelete);
export default KategoriKegiatan;

View File

@@ -0,0 +1,28 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
nama: string;
}
export default async function kategoriKegiatanUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.kategoriKegiatan.update({
where: { id },
data: {
nama: body.nama,
},
});
return {
success: true,
message: "Berhasil mengupdate kategori kegiatan",
data: result,
};
} catch (error) {
console.error("Error updating kategori kegiatan:", error);
throw new Error("Gagal mengupdate kategori kegiatan: " + (error as Error).message);
}
}

View File

@@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function kegiatanDesaUpdate(context: Context) {
const { id } = context.params as { id: string };
const body = context.body as any;
try {
const data = await prisma.kegiatanDesa.update({
where: { id },
data: {
judul: body.judul,
deskripsiSingkat: body.deskripsiSingkat,
deskripsiLengkap: body.deskripsiLengkap,
tanggal: new Date(body.tanggal),
lokasi: body.lokasi,
partisipan: Number(body.partisipan) || 0,
kategoriKegiatanId: body.kategoriKegiatanId,
imageId: body.imageId || null,
},
include: { kategoriKegiatan: true, image: true },
});
return { success: true, message: "Kegiatan desa berhasil diupdate", data };
} catch (e) {
console.error("Error di kegiatanDesaUpdate:", e);
return { success: false, message: "Gagal mengupdate kegiatan desa" };
}
}
export default kegiatanDesaUpdate;

View File

@@ -21,6 +21,7 @@ import Kematian from "./data_kesehatan_warga/persentase_kelahiran_kematian/kemat
import DokterTenagaMedis from "./data_kesehatan_warga/fasilitas_kesehatan/dokter-tenaga-medis";
import PendaftaranJadwalKegiatan from "./data_kesehatan_warga/jadwal_kegiatan/pendaftaran";
import TarifLayanan from "./data_kesehatan_warga/fasilitas_kesehatan/tarif-layanan";
import RingkasanKesehatan from "./ringkasan-kesehatan";
const Kesehatan = new Elysia({
@@ -49,4 +50,5 @@ const Kesehatan = new Elysia({
.use(DokterTenagaMedis)
.use(TarifLayanan)
.use(PendaftaranJadwalKegiatan)
.use(RingkasanKesehatan)
export default Kesehatan;

View File

@@ -0,0 +1,17 @@
import prisma from "@/lib/prisma";
async function ringkasanKesehatanFindUnique() {
try {
const data = await prisma.ringkasanKesehatanDesa.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'desc' },
});
return { success: true, data };
} catch (e) {
console.error("Error di ringkasanKesehatanFindUnique:", e);
return { success: false, message: "Gagal mengambil ringkasan kesehatan" };
}
}
export default ringkasanKesehatanFindUnique;

View File

@@ -0,0 +1,15 @@
import Elysia, { t } from "elysia";
import ringkasanKesehatanFindUnique from "./findUnique";
import ringkasanKesehatanUpdate from "./updt";
const RingkasanKesehatan = new Elysia({ prefix: "/ringkasankesehatan", tags: ["Kesehatan/Ringkasan"] })
.get("/find", ringkasanKesehatanFindUnique)
.put("/update", ringkasanKesehatanUpdate, {
body: t.Object({
ibuHamilAkh: t.Number(),
balitaTerdaftar: t.Number(),
alertStunting: t.Number(),
}),
});
export default RingkasanKesehatan;

View File

@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function ringkasanKesehatanUpdate(context: Context) {
const body = context.body as any;
try {
const existing = await prisma.ringkasanKesehatanDesa.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'desc' },
});
const data = existing
? await prisma.ringkasanKesehatanDesa.update({
where: { id: existing.id },
data: {
ibuHamilAkh: Number(body.ibuHamilAkh),
balitaTerdaftar: Number(body.balitaTerdaftar),
alertStunting: Number(body.alertStunting),
},
})
: await prisma.ringkasanKesehatanDesa.create({
data: {
ibuHamilAkh: Number(body.ibuHamilAkh),
balitaTerdaftar: Number(body.balitaTerdaftar),
alertStunting: Number(body.alertStunting),
},
});
return { success: true, message: "Ringkasan kesehatan berhasil disimpan", data };
} catch (e) {
console.error("Error di ringkasanKesehatanUpdate:", e);
return { success: false, message: "Gagal menyimpan ringkasan kesehatan" };
}
}
export default ringkasanKesehatanUpdate;

View File

@@ -0,0 +1,17 @@
import prisma from "@/lib/prisma";
async function beasiswaConfigFindUnique() {
try {
const data = await prisma.beasiswaConfig.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'desc' },
});
return { success: true, data };
} catch (e) {
console.error("Error di beasiswaConfigFindUnique:", e);
return { success: false, message: "Gagal mengambil konfigurasi beasiswa" };
}
}
export default beasiswaConfigFindUnique;

View File

@@ -0,0 +1,14 @@
import Elysia, { t } from "elysia";
import beasiswaConfigFindUnique from "./findUnique";
import beasiswaConfigUpdate from "./updt";
const BeasiswaConfig = new Elysia({ prefix: "/beasiswaconfig", tags: ["Pendidikan/Beasiswa Desa/Config"] })
.get("/find", beasiswaConfigFindUnique)
.put("/update", beasiswaConfigUpdate, {
body: t.Object({
tahunAjaran: t.String(),
danaTersalurkan: t.String(),
}),
});
export default BeasiswaConfig;

View File

@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function beasiswaConfigUpdate(context: Context) {
const body = context.body as any;
try {
const existing = await prisma.beasiswaConfig.findFirst({
where: { isActive: true },
orderBy: { createdAt: 'desc' },
});
const data = existing
? await prisma.beasiswaConfig.update({
where: { id: existing.id },
data: {
tahunAjaran: body.tahunAjaran,
danaTersalurkan: BigInt(body.danaTersalurkan),
},
})
: await prisma.beasiswaConfig.create({
data: {
tahunAjaran: body.tahunAjaran,
danaTersalurkan: BigInt(body.danaTersalurkan),
},
});
return { success: true, message: "Konfigurasi beasiswa berhasil disimpan", data: { ...data, danaTersalurkan: data.danaTersalurkan.toString() } };
} catch (e) {
console.error("Error di beasiswaConfigUpdate:", e);
return { success: false, message: "Gagal menyimpan konfigurasi beasiswa" };
}
}
export default beasiswaConfigUpdate;

View File

@@ -1,6 +1,7 @@
import Elysia from "elysia";
import BeasiswaPendaftar from "./beasiswa-pendaftar";
import KeunggulanProgram from "./keunggulan-program";
import BeasiswaConfig from "./beasiswa-config";
const Beasiswa = new Elysia({
prefix: "/beasiswa",
@@ -8,5 +9,6 @@ const Beasiswa = new Elysia({
})
.use(BeasiswaPendaftar)
.use(KeunggulanProgram)
.use(BeasiswaConfig)
export default Beasiswa