From 97d08734c5ece429da8fd98d2cdf3c12117bad8d Mon Sep 17 00:00:00 2001
From: nico
Date: Thu, 28 May 2026 15:51:32 +0800
Subject: [PATCH] feat(kesehatan): posyandu banjar relation, redesign halaman
publik, fix tips keamanan image
- Tambah model Banjar + relasi ke Posyandu (migration + seeder)
- Update API posyandu (create/update/find) untuk support banjarId
- Tambah endpoint banjar di kesehatan API
- Redesign halaman publik posyandu dengan tabs: ringkasan, data posyandu, balita, ibu hamil
- Update halaman admin posyandu list/create/edit/detail untuk banjar
- Fix image ketukar pada seed tips keamanan
- Hapus seeder core yang sudah tidak dipakai
Co-Authored-By: Claude Sonnet 4.6
---
CLAUDE.md | 1 +
...banjar-publik-fix-tips-keamanan-summary.md | 98 +++
prisma/_seeder_list/core/seed_app_menu.ts | 57 --
prisma/_seeder_list/core/seed_core.ts | 69 --
.../kesehatan/posyandu/seed_posyandu.ts | 2 +
prisma/_seeder_list/kesehatan/seed_banjar.ts | 19 +
.../keamanan/tips-keamanan/tips-keamanan.json | 4 +-
prisma/data/kesehatan/banjar/banjar.json | 10 +
prisma/data/kesehatan/posyandu/posyandu.json | 24 +-
.../struktur-organisasi-ppid.json | 4 +-
.../migration.sql | 16 +
prisma/schema.prisma | 12 +
prisma/seed.ts | 14 +-
.../_state/kesehatan/balita/balita.ts | 10 +-
.../_state/kesehatan/ibu-hamil/ibuHamil.ts | 10 +-
.../_state/kesehatan/posyandu/posyandu.ts | 7 +-
.../ringkasan-kesehatan/ringkasanKesehatan.ts | 27 +-
.../kesehatan/posyandu/balita/page.tsx | 29 +-
.../kesehatan/posyandu/ibu-hamil/page.tsx | 27 +-
.../posyandu/list-posyandu/[id]/edit/page.tsx | 22 +-
.../posyandu/list-posyandu/[id]/page.tsx | 5 +
.../posyandu/list-posyandu/create/page.tsx | 16 +-
.../kesehatan/posyandu/list-posyandu/page.tsx | 32 +-
.../posyandu/ringkasan-kesehatan/page.tsx | 21 +-
.../_lib/kesehatan/balita/find-many.ts | 10 +-
.../_lib/kesehatan/banjar/find-many.ts | 19 +
.../_lib/kesehatan/banjar/index.ts | 9 +
.../_lib/kesehatan/ibu-hamil/find-many.ts | 10 +-
.../api/[[...slugs]]/_lib/kesehatan/index.ts | 2 +
.../_lib/kesehatan/posyandu/create.ts | 18 +-
.../_lib/kesehatan/posyandu/find-by-id.ts | 3 +-
.../_lib/kesehatan/posyandu/find-many.ts | 1 +
.../_lib/kesehatan/posyandu/index.ts | 2 +
.../_lib/kesehatan/posyandu/updt.ts | 23 +-
.../kesehatan/ringkasan-kesehatan/index.ts | 5 +-
.../kesehatan/ringkasan-kesehatan/stats.ts | 18 +-
.../(pages)/kesehatan/posyandu/[id]/page.tsx | 132 +--
.../(pages)/kesehatan/posyandu/page.tsx | 792 ++++++++++++++----
38 files changed, 1126 insertions(+), 454 deletions(-)
create mode 100644 MIND/SUMMARY/posyandu-banjar-publik-fix-tips-keamanan-summary.md
delete mode 100644 prisma/_seeder_list/core/seed_app_menu.ts
delete mode 100644 prisma/_seeder_list/core/seed_core.ts
create mode 100644 prisma/_seeder_list/kesehatan/seed_banjar.ts
create mode 100644 prisma/data/kesehatan/banjar/banjar.json
create mode 100644 prisma/migrations/20260528100000_add_banjar_to_posyandu/migration.sql
create mode 100644 src/app/api/[[...slugs]]/_lib/kesehatan/banjar/find-many.ts
create mode 100644 src/app/api/[[...slugs]]/_lib/kesehatan/banjar/index.ts
diff --git a/CLAUDE.md b/CLAUDE.md
index 7561ba9b..60c45a43 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -30,6 +30,7 @@ bun eslint . --fix
- Architecture, request flow, domain modules, key files: @.claude/ARCHITECTURE.md
- Database conventions, auth flow, file handling: @.claude/DATABASE.md
- Env vars, Docker, CI/CD, releasing: @.claude/DEPLOYMENT.md
+- UI/UX design system, tokens, komponen, pola halaman: @.claude/DESIGN.md
- AI collaboration contract, rules, and guidelines: @AI-CONTRACT.md
### Workflow for Code Changes
diff --git a/MIND/SUMMARY/posyandu-banjar-publik-fix-tips-keamanan-summary.md b/MIND/SUMMARY/posyandu-banjar-publik-fix-tips-keamanan-summary.md
new file mode 100644
index 00000000..755bc186
--- /dev/null
+++ b/MIND/SUMMARY/posyandu-banjar-publik-fix-tips-keamanan-summary.md
@@ -0,0 +1,98 @@
+# Summary: Posyandu Banjar + Halaman Publik + Fix Tips Keamanan
+
+**Tanggal:** 2026-05-28
+**Branch:** tasks/kesehatan/posyandu-banjar-publik-fix-tips-keamanan/20260528
+
+---
+
+## Apa yang Berubah
+
+### 1. Fitur Banjar pada Posyandu
+
+**Problem:** Posyandu tidak memiliki relasi ke wilayah banjar, sehingga tidak bisa dikelompokkan per banjar.
+
+**Perubahan:**
+- `prisma/schema.prisma` — Tambah model `Banjar` baru dan field `banjarId` (optional FK) pada model `Posyandu`
+- `prisma/migrations/20260528100000_add_banjar_to_posyandu/migration.sql` — Migration: CREATE TABLE `Banjar`, ALTER TABLE `Posyandu` ADD COLUMN `banjarId`
+- `prisma/_seeder_list/kesehatan/seed_banjar.ts` — Seeder baru untuk data banjar
+- `prisma/data/kesehatan/banjar/banjar.json` — Data seed 16 banjar Desa Darmasaba
+- `prisma/seed.ts` — Tambah `seedBanjar()` sebelum `seedTipsKeamanan()`
+
+**API changes:**
+- `src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/create.ts` — Terima `banjarId` optional saat create
+- `src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/updt.ts` — Terima `banjarId` optional saat update
+- `src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-by-id.ts` — Include relasi `banjar`
+- `src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-many.ts` — Include relasi `banjar`
+- `src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/index.ts` — Tambah endpoint banjar
+- `src/app/api/[[...slugs]]/_lib/kesehatan/index.ts` — Daftarkan endpoint banjar baru
+- `src/app/api/[[...slugs]]/_lib/kesehatan/banjar/` — Module API baru untuk list banjar
+- `src/app/api/[[...slugs]]/_lib/kesehatan/balita/find-many.ts` — Support filter per posyandu
+- `src/app/api/[[...slugs]]/_lib/kesehatan/ibu-hamil/find-many.ts` — Support filter per posyandu
+
+**Admin CMS changes:**
+- `src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/page.tsx` — Tampilkan info banjar di tabel
+- `src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/create/page.tsx` — Tambah Select banjar
+- `src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/edit/page.tsx` — Tambah Select banjar
+- `src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/page.tsx` — Tampilkan nama banjar di detail
+- `src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts` — Include `banjar` di state
+
+---
+
+### 2. Redesign Halaman Publik Posyandu
+
+**Problem:** Halaman publik `/darmasaba/kesehatan/posyandu` hanya menampilkan daftar posyandu saja, belum menampilkan data balita, ibu hamil, dan ringkasan statistik.
+
+**Perubahan:**
+- `src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx` — Redesign total dengan sistem Tab:
+ - **Tab Ringkasan** — Statistik kesehatan (total posyandu, balita, ibu hamil, angka stunting)
+ - **Tab Data Posyandu** — List posyandu dengan filter search dan info banjar
+ - **Tab Data Balita** — Tabel data balita dengan filter search + filter status stunting
+ - **Tab Ibu Hamil** — Tabel data ibu hamil dengan filter search + filter status
+- `src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx` — Halaman detail posyandu dengan tampilan tab balita & ibu hamil per posyandu
+- `src/app/admin/(dashboard)/_state/kesehatan/balita/balita.ts` — Tambah state `findMany` untuk halaman publik
+- `src/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil.ts` — Tambah state `findMany` untuk halaman publik
+- `src/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan.ts` — Refactor state ringkasan
+- `src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx` — Update admin balita page
+- `src/app/admin/(dashboard)/kesehatan/posyandu/ibu-hamil/page.tsx` — Update admin ibu hamil page
+- `src/app/admin/(dashboard)/kesehatan/posyandu/ringkasan-kesehatan/page.tsx` — Update admin ringkasan
+- `src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/stats.ts` — Perbaiki kalkulasi statistik
+- `src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts` — Update endpoint
+
+---
+
+### 3. Fix Image Tips Keamanan (Ketuker)
+
+**Problem:** Gambar pada data Tips Keamanan tertukar antara "Keamanan Rumah" dan "Keamanan Lingkungan Tanggungjawab Bersama".
+
+**Root cause:** Nilai `imageName` di `tips-keamanan.json` salah assign — nama file gambar di-swap antara 2 record.
+
+**Perubahan:**
+- `prisma/data/keamanan/tips-keamanan/tips-keamanan.json` — Tukar nilai `imageName`:
+ - "Keamanan Rumah" → `vwZsaxcoFWDlxG1PW7FC0-mobile.webp` (sebelumnya `dSe0xyvNLkP2t2f6iq-Hk-mobile.webp`)
+ - "Keamanan Lingkungan..." → `dSe0xyvNLkP2t2f6iq-Hk-mobile.webp` (sebelumnya `vwZsaxcoFWDlxG1PW7FC0-mobile.webp`)
+
+**Catatan:** `imageId` akan null di lokal karena MinIO lokal tidak punya file tersebut. Di STG, MinIO sudah punya kedua file — seed akan resolve dengan benar setelah deploy.
+
+---
+
+### 4. Cleanup Seed Core
+
+- `prisma/_seeder_list/core/seed_app_menu.ts` — Dihapus (sudah tidak dipakai)
+- `prisma/_seeder_list/core/seed_core.ts` — Dihapus (sudah tidak dipakai)
+- `prisma/seed.ts` — Hapus import + call ke seed core, tambah `seedBanjar` dan `seedTipsKeamanan`
+
+---
+
+### 5. Update Seed PPID & Data
+
+- `prisma/data/ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json` — Update data struktur organisasi PPID
+- `prisma/_seeder_list/kesehatan/posyandu/seed_posyandu.ts` — Update seeder posyandu untuk include `banjarId`
+- `prisma/data/kesehatan/posyandu/posyandu.json` — Update data seed posyandu dengan `banjarId`
+
+---
+
+## Catatan Penting
+
+- **Migration wajib dijalankan** saat deploy: `prisma migrate deploy` sudah otomatis via `docker-entrypoint.sh`
+- **Seed harus dijalankan ulang** di STG setelah deploy agar data banjar terisi dan imageId tips keamanan terkoreksi
+- Gambar tips keamanan akan tetap null di lokal (MinIO lokal tidak punya file), tapi akan resolve di STG
diff --git a/prisma/_seeder_list/core/seed_app_menu.ts b/prisma/_seeder_list/core/seed_app_menu.ts
deleted file mode 100644
index 98a57e1e..00000000
--- a/prisma/_seeder_list/core/seed_app_menu.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import prisma from "@/lib/prisma";
-import { loadJsonData } from "../../load-json";
-
-const appMenuJson = loadJsonData("core/app-menu.json");
-const appMenuChildJson = loadJsonData("core/app-menu-child.json");
-
-export async function seedAppMenu() {
- console.log("🔄 Seeding AppMenu...");
-
- for (const item of appMenuJson) {
- await prisma.appMenu.upsert({
- where: { id: item.id },
- update: {
- name: item.name,
- link: item.link,
- isActive: item.isActive,
- },
- create: {
- id: item.id,
- name: item.name,
- link: item.link,
- isActive: item.isActive,
- },
- });
-
- console.log(`✅ AppMenu seeded: ${item.name}`);
- }
-
- console.log("🎉 AppMenu seed selesai");
-}
-
-export async function seedAppMenuChild() {
- console.log("🔄 Seeding AppMenuChild...");
-
- for (const item of appMenuChildJson) {
- await prisma.appMenuChild.upsert({
- where: { id: item.id },
- update: {
- name: item.name,
- link: item.link,
- isActive: item.isActive,
- appMenuId: item.appMenuId,
- },
- create: {
- id: item.id,
- name: item.name,
- link: item.link,
- isActive: item.isActive,
- appMenuId: item.appMenuId,
- },
- });
-
- console.log(`✅ AppMenuChild seeded: ${item.name}`);
- }
-
- console.log("🎉 AppMenuChild seed selesai");
-}
diff --git a/prisma/_seeder_list/core/seed_core.ts b/prisma/_seeder_list/core/seed_core.ts
deleted file mode 100644
index 4838c28b..00000000
--- a/prisma/_seeder_list/core/seed_core.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import prisma from "@/lib/prisma";
-import { loadJsonData } from "../../load-json";
-
-const layananJson = loadJsonData("core/layanan.json");
-const potensiJson = loadJsonData("core/potensi.json");
-const landingPageLayananJson = loadJsonData("core/landingpage-layanan.json");
-
-export async function seedLayananCore() {
- console.log("🔄 Seeding Layanan...");
-
- for (const item of layananJson) {
- await prisma.layanan.upsert({
- where: { id: item.id },
- update: {
- name: item.name,
- },
- create: {
- id: item.id,
- name: item.name,
- },
- });
-
- console.log(`✅ Layanan seeded: ${item.name}`);
- }
-
- console.log("🎉 Layanan seed selesai");
-}
-
-export async function seedPotensiCore() {
- console.log("🔄 Seeding Potensi...");
-
- for (const item of potensiJson) {
- await prisma.potensi.upsert({
- where: { id: item.id },
- update: {
- name: item.name,
- },
- create: {
- id: item.id,
- name: item.name,
- },
- });
-
- console.log(`✅ Potensi seeded: ${item.name}`);
- }
-
- console.log("🎉 Potensi seed selesai");
-}
-
-export async function seedLandingPageLayanan() {
- console.log("🔄 Seeding LandingPage_Layanan...");
-
- for (const item of landingPageLayananJson) {
- await prisma.landingPage_Layanan.upsert({
- where: { id: item.id },
- update: {
- deksripsi: item.deksripsi,
- },
- create: {
- id: item.id,
- deksripsi: item.deksripsi,
- },
- });
-
- console.log(`✅ LandingPage_Layanan seeded: ${item.id}`);
- }
-
- console.log("🎉 LandingPage_Layanan seed selesai");
-}
diff --git a/prisma/_seeder_list/kesehatan/posyandu/seed_posyandu.ts b/prisma/_seeder_list/kesehatan/posyandu/seed_posyandu.ts
index 67130c76..70b25a01 100644
--- a/prisma/_seeder_list/kesehatan/posyandu/seed_posyandu.ts
+++ b/prisma/_seeder_list/kesehatan/posyandu/seed_posyandu.ts
@@ -32,6 +32,7 @@ export async function seedPosyandu() {
deskripsi: p.deskripsi,
jadwalPelayanan: p.jadwalPelayanan,
imageId,
+ banjarId: p.banjarId || null,
},
create: {
id: p.id,
@@ -40,6 +41,7 @@ export async function seedPosyandu() {
deskripsi: p.deskripsi,
jadwalPelayanan: p.jadwalPelayanan,
imageId,
+ banjarId: p.banjarId || null,
},
});
diff --git a/prisma/_seeder_list/kesehatan/seed_banjar.ts b/prisma/_seeder_list/kesehatan/seed_banjar.ts
new file mode 100644
index 00000000..2a2845a5
--- /dev/null
+++ b/prisma/_seeder_list/kesehatan/seed_banjar.ts
@@ -0,0 +1,19 @@
+import prisma from "@/lib/prisma";
+import { loadJsonData } from "../../load-json";
+
+const banjarJson = loadJsonData("kesehatan/banjar/banjar.json");
+
+export async function seedBanjar() {
+ console.log("🔄 Seeding Banjar...");
+
+ for (const b of banjarJson) {
+ await prisma.banjar.upsert({
+ where: { id: b.id },
+ update: { name: b.name },
+ create: { id: b.id, name: b.name },
+ });
+ console.log(`✅ Banjar seeded: ${b.name}`);
+ }
+
+ console.log("🎉 Banjar seed selesai");
+}
diff --git a/prisma/data/keamanan/tips-keamanan/tips-keamanan.json b/prisma/data/keamanan/tips-keamanan/tips-keamanan.json
index fcf6d4d5..64736201 100644
--- a/prisma/data/keamanan/tips-keamanan/tips-keamanan.json
+++ b/prisma/data/keamanan/tips-keamanan/tips-keamanan.json
@@ -3,12 +3,12 @@
"id": "cmkp70zau0002vnu9o1jtpi1i",
"judul": "Keamanan Rumah",
"deskripsi": "
Pastikan pintu dan jendela selalu terkunci saat meninggalkan rumah
Pasang lampu penerangan di halaman dan area sekitar rumah untuk mencegah tindak kejahatan.
Jangan mudah memberikan akses masuk ke orang yang tidak dikenal.
",
- "imageName": "dSe0xyvNLkP2t2f6iq-Hk-mobile.webp"
+ "imageName": "vwZsaxcoFWDlxG1PW7FC0-mobile.webp"
},
{
"id": "cmkp71pzo0005vnu9p3n9646d",
"judul": "Keamanan Lingkungan Tanggungjawab Bersama",
"deskripsi": "Pemerintah Desa Darmasaba melaksanakan sosialisasi dan pembinaan tentang keamanan dan ketertiban lingkungan kepada warga Perumahan Darmasaba Permai. Warga diajak berperan aktif dalam menjaga keamanan lingkungan serta mendukung penyediaan lampu penerangan jalan untuk mencegah tindak kriminal dan kecelakaan. Bhabinkamtibmas dan Babinsa turut memberikan materi keamanan dan ketertiban kepada warga, menekankan pentingnya partisipasi masyarakat dalam menjaga keamanan desa.
",
- "imageName": "vwZsaxcoFWDlxG1PW7FC0-mobile.webp"
+ "imageName": "dSe0xyvNLkP2t2f6iq-Hk-mobile.webp"
}
]
\ No newline at end of file
diff --git a/prisma/data/kesehatan/banjar/banjar.json b/prisma/data/kesehatan/banjar/banjar.json
new file mode 100644
index 00000000..f84fad1f
--- /dev/null
+++ b/prisma/data/kesehatan/banjar/banjar.json
@@ -0,0 +1,10 @@
+[
+ { "id": "banjar_pudak_amara_001", "name": "Banjar Pudak Amara" },
+ { "id": "banjar_mawar_001", "name": "Banjar Mawar" },
+ { "id": "banjar_melati_001", "name": "Banjar Melati" },
+ { "id": "banjar_dahlia_001", "name": "Banjar Dahlia" },
+ { "id": "banjar_anggrek_001", "name": "Banjar Anggrek" },
+ { "id": "banjar_kamboja_001", "name": "Banjar Kamboja" },
+ { "id": "banjar_melur_001", "name": "Banjar Melur" },
+ { "id": "banjar_kenanga_001", "name": "Banjar Kenanga" }
+]
diff --git a/prisma/data/kesehatan/posyandu/posyandu.json b/prisma/data/kesehatan/posyandu/posyandu.json
index 8c7a84e0..5892e7db 100644
--- a/prisma/data/kesehatan/posyandu/posyandu.json
+++ b/prisma/data/kesehatan/posyandu/posyandu.json
@@ -5,55 +5,63 @@
"nomor": "(0361) 8463263",
"deskripsi": "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.
",
"jadwalPelayanan": "Senin, 10 Feb 2026, 08:00 - 11:00 WITA",
- "imageName": "TDQReg1lQ73s39crXW0ra-mobile.webp"
+ "imageName": "TDQReg1lQ73s39crXW0ra-mobile.webp",
+ "banjarId": "banjar_pudak_amara_001"
},
{
"id": "posyandu_mawar_001",
"name": "Posyandu Mawar",
"nomor": "(0361) 8463264",
"deskripsi": "Posyandu Mawar melayani kesehatan ibu dan anak di wilayah Banjar Mawar, Desa Darmasaba, dengan fokus pada pemantauan tumbuh kembang balita dan kesehatan ibu hamil.
",
- "jadwalPelayanan": "Senin, 15 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Senin, 15 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_mawar_001"
},
{
"id": "posyandu_melati_001",
"name": "Posyandu Melati",
"nomor": "(0361) 8463265",
"deskripsi": "Posyandu Melati berperan aktif dalam pelayanan kesehatan dasar masyarakat di Banjar Melati, meliputi imunisasi, penimbangan balita, dan konsultasi gizi.
",
- "jadwalPelayanan": "Selasa, 16 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Selasa, 16 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_melati_001"
},
{
"id": "posyandu_dahlia_001",
"name": "Posyandu Dahlia",
"nomor": "(0361) 8463266",
"deskripsi": "Posyandu Dahlia aktif melayani masyarakat Banjar Dahlia dengan program unggulan pemantauan stunting dan pemberian makanan tambahan bagi balita berisiko.
",
- "jadwalPelayanan": "Rabu, 17 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Rabu, 17 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_dahlia_001"
},
{
"id": "posyandu_anggrek_001",
"name": "Posyandu Anggrek",
"nomor": "(0361) 8463267",
"deskripsi": "Posyandu Anggrek melayani ibu hamil, ibu menyusui, dan balita di wilayah Banjar Anggrek dengan dukungan tenaga kesehatan dari Puskesmas Abiansemal 3.
",
- "jadwalPelayanan": "Kamis, 18 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Kamis, 18 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_anggrek_001"
},
{
"id": "posyandu_kamboja_001",
"name": "Posyandu Kamboja",
"nomor": "(0361) 8463268",
"deskripsi": "Posyandu Kamboja hadir untuk mendukung kesehatan masyarakat Banjar Kamboja melalui layanan pemeriksaan rutin, imunisasi lengkap, dan edukasi gizi keluarga.
",
- "jadwalPelayanan": "Jumat, 19 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Jumat, 19 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_kamboja_001"
},
{
"id": "posyandu_melur_001",
"name": "Posyandu Melur",
"nomor": "(0361) 8463269",
"deskripsi": "Posyandu Melur aktif memberikan layanan kesehatan preventif bagi ibu dan anak di Banjar Melur, termasuk deteksi dini stunting dan pemantauan gizi balita.
",
- "jadwalPelayanan": "Sabtu, 20 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Sabtu, 20 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_melur_001"
},
{
"id": "posyandu_kenanga_001",
"name": "Posyandu Kenanga",
"nomor": "(0361) 8463270",
"deskripsi": "Posyandu Kenanga melayani masyarakat Banjar Kenanga dengan program kesehatan ibu dan anak, pemberian vitamin A, dan konseling laktasi bagi ibu menyusui.
",
- "jadwalPelayanan": "Senin, 23 Feb 2026, 08:00 - 11:00 WITA"
+ "jadwalPelayanan": "Senin, 23 Feb 2026, 08:00 - 11:00 WITA",
+ "banjarId": "banjar_kenanga_001"
}
]
diff --git a/prisma/data/ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json b/prisma/data/ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json
index b63819e3..e4ca38db 100644
--- a/prisma/data/ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json
+++ b/prisma/data/ppid/struktur-organisasi-ppid/struktur-organisasi-ppid.json
@@ -1,8 +1,8 @@
[
{
"id": "struktur-org-ppid-001",
- "posisiOrganisasiId": "posisi-001",
- "pegawaiId": "pegawai-001",
+ "posisiOrganisasiId": "kepala_desa",
+ "pegawaiId": "cmgewz4gt000704ib91i3f169",
"hubunganOrganisasiId": "hubungan-001"
}
]
diff --git a/prisma/migrations/20260528100000_add_banjar_to_posyandu/migration.sql b/prisma/migrations/20260528100000_add_banjar_to_posyandu/migration.sql
new file mode 100644
index 00000000..990d01e8
--- /dev/null
+++ b/prisma/migrations/20260528100000_add_banjar_to_posyandu/migration.sql
@@ -0,0 +1,16 @@
+-- CreateTable
+CREATE TABLE "Banjar" (
+ "id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "Banjar_pkey" PRIMARY KEY ("id")
+);
+
+-- AlterTable
+ALTER TABLE "Posyandu" ADD COLUMN "banjarId" TEXT;
+
+-- AddForeignKey
+ALTER TABLE "Posyandu" ADD CONSTRAINT "Posyandu_banjarId_fkey" FOREIGN KEY ("banjarId") REFERENCES "Banjar"("id") ON DELETE SET NULL ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 068ff545..3f5157f6 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1150,6 +1150,16 @@ model DoctorSign {
ArtikelKesehatan ArtikelKesehatan[]
}
+// ========================================= BANJAR ========================================= //
+model Banjar {
+ id String @id @default(cuid())
+ name String
+ isActive Boolean @default(true)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ posyandus Posyandu[]
+}
+
// ========================================= POSYANDU ========================================= //
model Posyandu {
id String @id @default(cuid())
@@ -1159,6 +1169,8 @@ model Posyandu {
jadwalPelayanan String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
+ banjar Banjar? @relation(fields: [banjarId], references: [id])
+ banjarId String?
ibuHamil IbuHamil[]
balita Balita[]
createdAt DateTime @default(now())
diff --git a/prisma/seed.ts b/prisma/seed.ts
index c0520f77..3922f027 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -38,6 +38,7 @@ import { seedKontakDaruratKeamanan } from "./_seeder_list/keamanan/seed_kontak_d
import { seedLaporanPublik } from "./_seeder_list/keamanan/seed_laporan_publik";
import { seedPencegahanKriminalitas } from "./_seeder_list/keamanan/seed_pencegahan_kriminalitas";
import { seedPolsekTerdekat } from "./_seeder_list/keamanan/seed_polsek_terdekat";
+import { seedTipsKeamanan } from "./_seeder_list/keamanan/seed_tips_keamanan";
import { seedArtikelKesehatan } from "./_seeder_list/kesehatan/artikel-kesehatan/seed_artikel_kesehatan";
import { seedFasilitasKesehatan } from "./_seeder_list/kesehatan/fasilitas-kesehatan/seed_fasilitas_kesehatan";
import { seedInfoWabahPenyakit } from "./_seeder_list/kesehatan/info-wabah-penyakit/seed_info_wabah_penyakit";
@@ -49,6 +50,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 { seedBanjar } from "./_seeder_list/kesehatan/seed_banjar";
import { seedRingkasanKesehatan } from "./_seeder_list/kesehatan/seed_ringkasan_kesehatan";
import { seedIbuHamil } from "./_seeder_list/kesehatan/seed_ibu_hamil";
import { seedBalita } from "./_seeder_list/kesehatan/seed_balita";
@@ -84,6 +86,7 @@ import { seedIkmPpid } from "./_seeder_list/ppid/ikm/seed_ikm";
import { seedProfilPpd } from "./_seeder_list/ppid/profil-ppid/seed_profil_ppd";
import { seedPegawaiPpid } from "./_seeder_list/ppid/struktur-ppid/seed_struktur_ppid";
import { seedVisiMisiPpid } from "./_seeder_list/ppid/visi-misi-ppid/seed_visi_misi_ppid";
+import { seedStrukturOrganisasiPpid, seedFormulirPermohonanKeberatan, seedIndeksKepuasanMasyarakat, seedGrafikBerdasarkanJenisKelamin, seedGrafikBerdasarkanResponden, seedGrafikBerdasarkanUmur } from "./_seeder_list/ppid/seed_ppid_extra";
import roles from "./data/user/roles.json";
import users from "./data/user/users.json";
import { safeSeedUnique } from "./safeseedUnique";
@@ -219,6 +222,12 @@ import seedAssets from "./seed_assets";
// // =========== SUBMENU INDEKS KEPUASAN MASYARAKAT ===========
await seedIkmPpid();
+ await seedStrukturOrganisasiPpid();
+ await seedFormulirPermohonanKeberatan();
+ await seedIndeksKepuasanMasyarakat();
+ await seedGrafikBerdasarkanJenisKelamin();
+ await seedGrafikBerdasarkanResponden();
+ await seedGrafikBerdasarkanUmur();
// // =========== MENU DESA ===========
// // =========== SUBMENU PROFILE ===========
@@ -245,6 +254,9 @@ import seedAssets from "./seed_assets";
await seedPenghargaan();
// // ====================== MENU KESEHATAN ========================
+ // // ==================== SUBMENU BANJAR =========================
+ await seedBanjar();
+
// // ==================== SUBMENU POSYANDU =========================
await seedPosyandu();
@@ -285,7 +297,7 @@ import seedAssets from "./seed_assets";
await seedCctv();
// // ==================== SUBMENU TIPS KEAMANAN ==================
- await seedKeamananLingkungan();
+ await seedTipsKeamanan();
// // ====================== MENU EKONOMI ========================
// // ==================== SUBMENU UMKM ==========================
diff --git a/src/app/admin/(dashboard)/_state/kesehatan/balita/balita.ts b/src/app/admin/(dashboard)/_state/kesehatan/balita/balita.ts
index e7f1a0c1..be20148d 100644
--- a/src/app/admin/(dashboard)/_state/kesehatan/balita/balita.ts
+++ b/src/app/admin/(dashboard)/_state/kesehatan/balita/balita.ts
@@ -82,7 +82,15 @@ const balitaState = proxy({
findMany: {
data: null as
| Prisma.BalitaGetPayload<{
- include: { posyandu: { select: { id: true; name: true } } };
+ include: {
+ posyandu: {
+ select: {
+ id: true;
+ name: true;
+ banjar: { select: { id: true; name: true } };
+ };
+ };
+ };
}>[]
| null,
page: 1,
diff --git a/src/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil.ts b/src/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil.ts
index 906541f4..2b410205 100644
--- a/src/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil.ts
+++ b/src/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil.ts
@@ -72,7 +72,15 @@ const ibuHamilState = proxy({
findMany: {
data: null as
| Prisma.IbuHamilGetPayload<{
- include: { posyandu: { select: { id: true; name: true } } };
+ include: {
+ posyandu: {
+ select: {
+ id: true;
+ name: true;
+ banjar: { select: { id: true; name: true } };
+ };
+ };
+ };
}>[]
| null,
page: 1,
diff --git a/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts b/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts
index 38ccec4c..bca47df1 100644
--- a/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts
+++ b/src/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu.ts
@@ -19,6 +19,7 @@ const defaultForm = {
deskripsi: "",
imageId: "",
jadwalPelayanan: "",
+ banjarId: "",
};
const posyandustate = proxy({
@@ -57,6 +58,7 @@ const posyandustate = proxy({
| Prisma.PosyanduGetPayload<{
include: {
image: true;
+ banjar: { select: { id: true; name: true } };
};
}>[]
| null,
@@ -92,10 +94,11 @@ const posyandustate = proxy({
},
},
findUnique: {
- data: null as
+ data: null as
| Prisma.PosyanduGetPayload<{
include: {
image: true;
+ banjar: { select: { id: true; name: true } };
}
}> | null,
async load(id: string) {
@@ -176,6 +179,7 @@ const posyandustate = proxy({
deskripsi: data.deskripsi,
imageId: data.imageId || "",
jadwalPelayanan: data.jadwalPelayanan || "",
+ banjarId: data.banjarId || "",
};
return data;
} else {
@@ -210,6 +214,7 @@ const posyandustate = proxy({
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
jadwalPelayanan: this.form.jadwalPelayanan,
+ banjarId: this.form.banjarId || undefined,
}),
});
diff --git a/src/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan.ts b/src/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan.ts
index 5728bac9..a4a7db76 100644
--- a/src/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan.ts
+++ b/src/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan.ts
@@ -18,14 +18,39 @@ const intPct = z
.min(0, { message: "Minimal 0" })
.max(100, { message: "Maksimal 100" });
+type BanjarOption = { id: string; name: string };
+
const ringkasanKesehatanState = proxy({
+ banjarId: "" as string,
+
+ findBanjar: {
+ data: [] as BanjarOption[],
+ loading: false,
+ async load() {
+ try {
+ ringkasanKesehatanState.findBanjar.loading = true;
+ const res = await fetch(`/api/kesehatan/banjar/find-many`);
+ if (res.ok) {
+ const result = await res.json();
+ ringkasanKesehatanState.findBanjar.data = result?.data ?? [];
+ }
+ } catch (error) {
+ console.error("Error fetching banjar:", error);
+ } finally {
+ ringkasanKesehatanState.findBanjar.loading = false;
+ }
+ },
+ },
+
findStats: {
data: null as StatsData | null,
loading: false,
async load() {
try {
ringkasanKesehatanState.findStats.loading = true;
- const res = await fetch(`/api/kesehatan/ringkasankesehatan/stats`);
+ const banjarId = ringkasanKesehatanState.banjarId;
+ const params = banjarId ? `?banjarId=${encodeURIComponent(banjarId)}` : "";
+ const res = await fetch(`/api/kesehatan/ringkasankesehatan/stats${params}`);
if (res.ok) {
const result = await res.json();
ringkasanKesehatanState.findStats.data = result?.data ?? null;
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx
index 7be1ae07..4f8f7eb7 100644
--- a/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx
+++ b/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx
@@ -115,13 +115,14 @@ function ListBalita({ search }: { search: string }) {
- Nama
- JK
- Tgl Lahir
- Imunisasi
- Gizi
- Pemeriksaan
- Stunting
+ Nama
+ JK
+ Tgl Lahir
+ Banjar
+ Imunisasi
+ Gizi
+ Pemeriksaan
+ Stunting
Aksi
@@ -136,6 +137,7 @@ function ListBalita({ search }: { search: string }) {
? new Date(d.tanggalLahir).toLocaleDateString('id-ID')
: '-'}
+ {d.posyandu?.banjar?.name ?? '-'}
{d.imunisasiLengkap ? 'Lengkap' : 'Belum'}
@@ -190,7 +192,7 @@ function ListBalita({ search }: { search: string }) {
))
) : (
-
+
Tidak ada data balita yang cocok
@@ -212,19 +214,22 @@ function ListBalita({ search }: { search: string }) {
{d.nama}
-
+
{d.jenisKelamin}
-
- ·
-
+ ·
{d.tanggalLahir
? new Date(d.tanggalLahir).toLocaleDateString('id-ID')
: '-'}
+ {d.posyandu?.banjar?.name && (
+
+ Banjar: {d.posyandu.banjar.name}
+
+ )}
- Nama
- NIK
- Usia Kehamilan
- No. HP
- Status
- Aksi
+ Nama
+ NIK
+ Usia Kehamilan
+ No. HP
+ Banjar
+ Status
+ Aksi
@@ -133,6 +134,7 @@ function ListIbuHamil({ search }: { search: string }) {
{d.nik || '-'}
{d.usiaKehamilan} minggu
{d.noHp || '-'}
+ {d.posyandu?.banjar?.name ?? '-'}
-
+
Tidak ada data ibu hamil yang cocok
@@ -194,17 +196,20 @@ function ListIbuHamil({ search }: { search: string }) {
{d.nama}
-
+
NIK: {d.nik || '-'}
-
- ·
-
+ ·
{d.usiaKehamilan} minggu
+ {d.posyandu?.banjar?.name && (
+
+ Banjar: {d.posyandu.banjar.name}
+
+ )}
{
+ ringkasanKesehatanState.findBanjar.load();
const loadPosyandu = async () => {
const id = params?.id as string;
if (!id) return;
@@ -82,6 +88,7 @@ function EditPosyandu() {
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
jadwalPelayanan: data.jadwalPelayanan || '',
+ banjarId: data.banjarId || '',
});
setOriginalData({
name: data.name || '',
@@ -89,6 +96,7 @@ function EditPosyandu() {
deskripsi: data.deskripsi || '',
imageId: data.imageId || '',
jadwalPelayanan: data.jadwalPelayanan || '',
+ banjarId: data.banjarId || '',
imageUrl: data.image?.link || '',
});
if (data?.image?.link) setPreviewImage(data.image.link);
@@ -129,7 +137,7 @@ function EditPosyandu() {
try {
setIsSubmitting(true);
- const updatedForm = { ...statePosyandu.edit.form, ...formData };
+ const updatedForm = { ...statePosyandu.edit.form, ...formData, banjarId: formData.banjarId };
// Upload file jika ada
if (file) {
@@ -160,6 +168,7 @@ function EditPosyandu() {
deskripsi: originalData.deskripsi,
imageId: originalData.imageId,
jadwalPelayanan: originalData.jadwalPelayanan,
+ banjarId: originalData.banjarId,
});
setPreviewImage(originalData.imageUrl || null);
setFile(null);
@@ -282,6 +291,15 @@ function EditPosyandu() {
required
/>
+ ({ value: b.id, label: b.name }))}
+ value={formData.banjarId || null}
+ onChange={(val) => setFormData({ ...formData, banjarId: val ?? '' })}
+ />
+
Deskripsi Posyandu
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/page.tsx
index 468d0349..7e24913f 100644
--- a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/page.tsx
+++ b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/[id]/page.tsx
@@ -82,6 +82,11 @@ function DetailPosyandu() {
+
+ Banjar
+ {data.banjar?.name || '-'}
+
+
Nomor Posyandu
{data.nomor || '-'}
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/create/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/create/page.tsx
index b990aa32..0ee6b217 100644
--- a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/create/page.tsx
+++ b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/create/page.tsx
@@ -11,6 +11,7 @@ import {
Image,
Loader,
Paper,
+ Select,
Stack,
Text,
TextInput,
@@ -19,19 +20,23 @@ import {
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
+import ringkasanKesehatanState from '../../../../_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan';
function CreatePosyandu() {
const statePosyandu = useProxy(posyandustate);
+ const stateBanjar = useProxy(ringkasanKesehatanState);
const router = useRouter();
const [file, setFile] = useState(null);
const [previewImage, setPreviewImage] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ useEffect(() => { ringkasanKesehatanState.findBanjar.load(); }, []);
+
// Helper function to check if HTML content is empty
const isHtmlEmpty = (html: string) => {
// Remove all HTML tags and check if there's any text content
@@ -57,6 +62,7 @@ function CreatePosyandu() {
deskripsi: '',
imageId: '',
jadwalPelayanan: '',
+ banjarId: '',
};
setFile(null);
setPreviewImage(null);
@@ -223,6 +229,14 @@ function CreatePosyandu() {
onChange={(e) => (statePosyandu.create.form.nomor = e.target.value)}
required
/>
+ ({ value: b.id, label: b.name }))}
+ value={statePosyandu.create.form.banjarId || null}
+ onChange={(val) => { statePosyandu.create.form.banjarId = val ?? ''; }}
+ />
Deskripsi Posyandu
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/page.tsx
index f7468bbf..7b30c83a 100644
--- a/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/page.tsx
+++ b/src/app/admin/(dashboard)/kesehatan/posyandu/list-posyandu/page.tsx
@@ -96,27 +96,33 @@ function ListPosyandu({ search }: { search: string }) {
>
- Nama Posyandu
- Nomor Posyandu
- Deskripsi
- Aksi
+ Nama Posyandu
+ Banjar
+ Nomor Posyandu
+ Deskripsi
+ Aksi
{filteredData.length > 0 ? (
filteredData.map((item) => (
-
+
{item.name}
-
+
+
+ {item.banjar?.name || '-'}
+
+
+
{item.nomor || '-'}
-
+
-
+
-
+
Tidak ada data posyandu yang cocok
@@ -169,6 +175,14 @@ function ListPosyandu({ search }: { search: string }) {
{item.name}
+
+
+ Banjar
+
+
+ {item.banjar?.name || '-'}
+
+
Nomor Posyandu
diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/ringkasan-kesehatan/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/ringkasan-kesehatan/page.tsx
index afab982f..f6774fa7 100644
--- a/src/app/admin/(dashboard)/kesehatan/posyandu/ringkasan-kesehatan/page.tsx
+++ b/src/app/admin/(dashboard)/kesehatan/posyandu/ringkasan-kesehatan/page.tsx
@@ -10,6 +10,7 @@ import {
Loader,
NumberInput,
Paper,
+ Select,
SimpleGrid,
Stack,
Text,
@@ -73,13 +74,29 @@ export default function RingkasanKesehatanPage() {
const stats = state.findStats.data;
const loadStats = useCallback(() => { ringkasanKesehatanState.findStats.load(); }, []);
- useEffect(() => { loadStats(); }, [loadStats]);
+ useEffect(() => {
+ ringkasanKesehatanState.findBanjar.load();
+ loadStats();
+ }, [loadStats]);
const isLoading = state.findStats.loading;
return (
- Ringkasan Kesehatan Desa
+
+ Ringkasan Kesehatan Desa
+ ({ value: b.id, label: b.name }))}
+ value={state.banjarId || null}
+ onChange={(val) => {
+ ringkasanKesehatanState.banjarId = val ?? "";
+ ringkasanKesehatanState.findStats.load();
+ }}
+ style={{ minWidth: 200 }}
+ />
+
{isLoading ? (
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/balita/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/balita/find-many.ts
index ef3aaf3f..79f6c106 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/balita/find-many.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/balita/find-many.ts
@@ -28,7 +28,15 @@ export default async function balitaFindMany(context: Context) {
const [data, total] = await Promise.all([
prisma.balita.findMany({
where,
- include: { posyandu: { select: { id: true, name: true } } },
+ include: {
+ posyandu: {
+ select: {
+ id: true,
+ name: true,
+ banjar: { select: { id: true, name: true } },
+ },
+ },
+ },
skip,
take: limit,
orderBy: { createdAt: "desc" },
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/find-many.ts
new file mode 100644
index 00000000..82fe1073
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/find-many.ts
@@ -0,0 +1,19 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+async function banjarFindMany(context: Context) {
+ try {
+ const data = await prisma.banjar.findMany({
+ where: { isActive: true },
+ select: { id: true, name: true },
+ orderBy: { name: "asc" },
+ });
+
+ return { success: true, data };
+ } catch (e) {
+ console.error("Error di banjarFindMany:", e);
+ return { success: false, message: "Gagal mengambil data banjar" };
+ }
+}
+
+export default banjarFindMany;
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/index.ts
new file mode 100644
index 00000000..4f601de1
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/banjar/index.ts
@@ -0,0 +1,9 @@
+import Elysia from "elysia";
+import banjarFindMany from "./find-many";
+
+const Banjar = new Elysia({
+ prefix: "/banjar",
+ tags: ["Kesehatan/Banjar"],
+}).get("/find-many", banjarFindMany);
+
+export default Banjar;
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ibu-hamil/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ibu-hamil/find-many.ts
index 937d0807..65f408b2 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/ibu-hamil/find-many.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ibu-hamil/find-many.ts
@@ -27,7 +27,15 @@ export default async function ibuHamilFindMany(context: Context) {
const [data, total] = await Promise.all([
prisma.ibuHamil.findMany({
where,
- include: { posyandu: { select: { id: true, name: true } } },
+ include: {
+ posyandu: {
+ select: {
+ id: true,
+ name: true,
+ banjar: { select: { id: true, name: true } },
+ },
+ },
+ },
skip,
take: limit,
orderBy: { createdAt: "desc" },
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts
index dff49da3..7aaab964 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts
@@ -24,6 +24,7 @@ import TarifLayanan from "./data_kesehatan_warga/fasilitas_kesehatan/tarif-layan
import RingkasanKesehatan from "./ringkasan-kesehatan";
import IbuHamil from "./ibu-hamil";
import Balita from "./balita";
+import Banjar from "./banjar";
const Kesehatan = new Elysia({
@@ -55,4 +56,5 @@ const Kesehatan = new Elysia({
.use(RingkasanKesehatan)
.use(IbuHamil)
.use(Balita)
+.use(Banjar)
export default Kesehatan;
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/create.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/create.ts
index cf3aad12..29f63931 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/create.ts
@@ -2,15 +2,14 @@ import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
-type FormCreate = Prisma.PosyanduGetPayload<{
- select: {
- name: true;
- nomor: true;
- deskripsi: true;
- imageId: true;
- jadwalPelayanan: true;
- };
-}>;
+type FormCreate = {
+ name: string;
+ nomor: string;
+ deskripsi: string;
+ imageId: string;
+ jadwalPelayanan: string;
+ banjarId?: string;
+};
export default async function posyanduCreate(context: Context) {
const body = context.body as FormCreate;
@@ -21,6 +20,7 @@ export default async function posyanduCreate(context: Context) {
deskripsi: body.deskripsi,
imageId: body.imageId,
jadwalPelayanan: body.jadwalPelayanan,
+ banjarId: body.banjarId || null,
}
})
return {
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-by-id.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-by-id.ts
index d992044f..fd3edabb 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-by-id.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-by-id.ts
@@ -23,7 +23,8 @@ export default async function findPosyanduById(request: Request) {
const data = await prisma.posyandu.findUnique({
where: {id},
include: {
- image: true
+ image: true,
+ banjar: { select: { id: true, name: true } },
}
})
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-many.ts
index 1b3cd1d8..d77cd189 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-many.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/find-many.ts
@@ -30,6 +30,7 @@ async function posyanduFindMany(context: Context) {
where,
include: {
image: true,
+ banjar: { select: { id: true, name: true } },
},
skip,
take: limit,
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/index.ts
index 085a8dd2..b376ac40 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/index.ts
@@ -16,6 +16,7 @@ const Posyandu = new Elysia({
deskripsi: t.String(),
imageId: t.String(),
jadwalPelayanan: t.String(),
+ banjarId: t.Optional(t.String()),
})
})
.get("/find-many", posyanduFindMany)
@@ -37,6 +38,7 @@ const Posyandu = new Elysia({
deskripsi: t.String(),
imageId: t.String(),
jadwalPelayanan: t.String(),
+ banjarId: t.Optional(t.String()),
})
}
)
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/updt.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/updt.ts
index 9c400e15..f48ca181 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/updt.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/posyandu/updt.ts
@@ -1,23 +1,20 @@
import prisma from "@/lib/prisma";
-import { Prisma } from "@prisma/client";
import { Context } from "elysia";
import minio, { MINIO_BUCKET } from "@/lib/minio";
-type FormUpdate = Prisma.PosyanduGetPayload<{
- select: {
- id: true;
- name: true;
- nomor: true;
- deskripsi: true;
- imageId: true;
- jadwalPelayanan: true;
- }
-}>
+type FormUpdate = {
+ name: string;
+ nomor: string;
+ deskripsi: string;
+ imageId: string;
+ jadwalPelayanan: string;
+ banjarId?: string;
+};
export default async function posyanduUpdate(context: Context) {
try {
const id = context.params?.id as string;
- const body = (await context.body) as Omit;
+ const body = (await context.body) as FormUpdate;
const {
name,
@@ -25,6 +22,7 @@ export default async function posyanduUpdate(context: Context) {
deskripsi,
imageId,
jadwalPelayanan,
+ banjarId,
} = body;
if(!id) {
@@ -80,6 +78,7 @@ export default async function posyanduUpdate(context: Context) {
deskripsi,
imageId,
jadwalPelayanan,
+ banjarId: banjarId || null,
}
})
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts
index 156a0554..eb50a859 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts
@@ -5,7 +5,10 @@ import ringkasanKesehatanStats from "./stats";
const RingkasanKesehatan = new Elysia({ prefix: "/ringkasankesehatan", tags: ["Kesehatan/Ringkasan"] })
.get("/find", ringkasanKesehatanFindUnique)
- .get("/stats", ringkasanKesehatanStats)
+ .get("/stats", (context) => {
+ const banjarId = (context.query.banjarId as string) || undefined;
+ return ringkasanKesehatanStats(banjarId);
+ })
.put("/update", ringkasanKesehatanUpdate, {
body: t.Object({
targetStuntingPct: t.Number({ minimum: 0, maximum: 100 }),
diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/stats.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/stats.ts
index 1e3660cf..9c593024 100644
--- a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/stats.ts
+++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/stats.ts
@@ -10,8 +10,12 @@ type StatsResult = {
targetStuntingPct: number;
};
-export default async function ringkasanKesehatanStats(): Promise<{ success: boolean; data?: StatsResult; message?: string }> {
+export default async function ringkasanKesehatanStats(
+ banjarId?: string
+): Promise<{ success: boolean; data?: StatsResult; message?: string }> {
try {
+ const posyanduFilter = banjarId ? { posyandu: { banjarId } } : {};
+
const [
ibuHamilAktif,
balitaTotal,
@@ -21,12 +25,12 @@ export default async function ringkasanKesehatanStats(): Promise<{ success: bool
giziBaik,
config,
] = await Promise.all([
- prisma.ibuHamil.count({ where: { status: "AKTIF", isActive: true } }),
- prisma.balita.count({ where: { isActive: true } }),
- prisma.balita.count({ where: { isActive: true, statusStunting: { in: ["ALERT", "STUNTING"] } } }),
- prisma.balita.count({ where: { isActive: true, imunisasiLengkap: true } }),
- prisma.balita.count({ where: { isActive: true, pemeriksaanRutin: true } }),
- prisma.balita.count({ where: { isActive: true, giziBaik: true } }),
+ prisma.ibuHamil.count({ where: { status: "AKTIF", isActive: true, ...posyanduFilter } }),
+ prisma.balita.count({ where: { isActive: true, ...posyanduFilter } }),
+ prisma.balita.count({ where: { isActive: true, statusStunting: { in: ["ALERT", "STUNTING"] }, ...posyanduFilter } }),
+ prisma.balita.count({ where: { isActive: true, imunisasiLengkap: true, ...posyanduFilter } }),
+ prisma.balita.count({ where: { isActive: true, pemeriksaanRutin: true, ...posyanduFilter } }),
+ prisma.balita.count({ where: { isActive: true, giziBaik: true, ...posyanduFilter } }),
prisma.ringkasanKesehatanDesa.findFirst({ where: { isActive: true }, orderBy: { createdAt: "desc" } }),
]);
diff --git a/src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx
index 50fcc8c4..f671898a 100644
--- a/src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/posyandu/[id]/page.tsx
@@ -2,19 +2,14 @@
import colors from '@/con/colors';
import {
- Button,
- Center,
- Flex,
- Group,
- Image,
- Paper,
- Skeleton,
- Stack,
- Text,
- Title,
+ Box, Button, Flex, Group, Image,
+ Paper, Skeleton, Stack, Text, Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
-import { IconArrowBack, IconCalendar, IconInfoCircle, IconPhone } from '@tabler/icons-react';
+import {
+ IconArrowBack, IconCalendar, IconInfoCircle,
+ IconMapPin, IconPhone,
+} from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import posyanduState from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
@@ -30,8 +25,8 @@ export default function DetailPosyanduUser() {
if (!statePosyandu.findUnique.data) {
return (
-
-
+
+
);
}
@@ -39,97 +34,104 @@ export default function DetailPosyanduUser() {
const data = statePosyandu.findUnique.data;
return (
-
- {/* Tombol Kembali */}
+
router.back()}
leftSection={ }
- mb="sm"
c={colors['blue-button']}
>
Kembali
- {/* Konten utama */}
-
- {/* Header — Dijadikan Title */}
-
+
+
{data.name || 'Posyandu Desa'}
- {/* Gambar */}
{data.image?.link ? (
-
-
-
+
) : (
-
-
- Tidak ada gambar
-
-
+
+ Tidak ada gambar
+
)}
- {/* Info utama */}
-
+
+ {data.banjar && (
+
+
+
+ Banjar {data.banjar.name}
+
+
+ )}
+
-
-
+
+
{data.nomor || 'Nomor tidak tersedia'}
-
-
+
+
-
-
+
+
@@ -137,4 +139,4 @@ export default function DetailPosyanduUser() {
);
-}
\ No newline at end of file
+}
diff --git a/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx b/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx
index 6f2bc289..ff4c89d1 100644
--- a/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx
+++ b/src/app/darmasaba/(pages)/kesehatan/posyandu/page.tsx
@@ -1,213 +1,651 @@
'use client'
+
+import balitaState from "@/app/admin/(dashboard)/_state/kesehatan/balita/balita";
+import ibuHamilState from "@/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil";
import posyandustate from "@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu";
+import ringkasanState from "@/app/admin/(dashboard)/_state/kesehatan/ringkasan-kesehatan/ringkasanKesehatan";
import colors from "@/con/colors";
-import { Badge, Box, Button, Center, Flex, Group, Image, List, ListItem, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core";
+import {
+ Badge, Box, Button, Center, Flex, Group, Image,
+ Pagination, Paper, Progress, ScrollArea, Select,
+ SimpleGrid, Skeleton, Stack, Tabs, Text, TextInput,
+ ThemeIcon, Title,
+} from "@mantine/core";
import { useDebouncedValue, useShallowEffect } from "@mantine/hooks";
-import { IconCalendar, IconInfoCircle, IconPhone, IconSearch } from "@tabler/icons-react";
+import {
+ IconActivity, IconCalendar, IconChartBar, IconHeart,
+ IconMapPin, IconPhone, IconSearch, IconShieldCheck,
+ IconUsers,
+} from "@tabler/icons-react";
import { useState } from "react";
import { useProxy } from "valtio/utils";
import BackButton from "../../desa/layanan/_com/BackButto";
import { useTransitionRouter } from "next-view-transitions";
+const stuntingBadgeColor = (s: string) =>
+ s === "STUNTING" ? "red" : s === "ALERT" ? "yellow" : "green";
+
+const ibuHamilBadgeColor = (s: string) => {
+ if (s === "AKTIF") return "green";
+ if (s === "MELAHIRKAN") return "blue";
+ if (s === "KEGUGURAN") return "gray";
+ return "red";
+};
+
export default function Page() {
- const state = useProxy(posyandustate);
- const [search, setSearch] = useState("");
- const [debouncedSearch] = useDebouncedValue(search, 1000);
+ const [activeTab, setActiveTab] = useState("ringkasan");
+
+ const stPosyandu = useProxy(posyandustate);
+ const stBalita = useProxy(balitaState);
+ const stIbuHamil = useProxy(ibuHamilState);
+ const stRingkasan = useProxy(ringkasanState);
+
+ const [searchPosyandu, setSearchPosyandu] = useState("");
+ const [debouncedSearchPosyandu] = useDebouncedValue(searchPosyandu, 800);
+
+ const [searchBalita, setSearchBalita] = useState("");
+ const [debouncedSearchBalita] = useDebouncedValue(searchBalita, 800);
+ const [filterStunting, setFilterStunting] = useState(null);
+
+ const [searchIbuHamil, setSearchIbuHamil] = useState("");
+ const [debouncedSearchIbuHamil] = useDebouncedValue(searchIbuHamil, 800);
+ const [filterStatusIbuHamil, setFilterStatusIbuHamil] = useState(null);
+
const router = useTransitionRouter();
- const { data, page, totalPages, loading, load } = state.findMany;
+ // Initial load: ringkasan
+ useShallowEffect(() => {
+ ringkasanState.findStats.load();
+ }, []);
+
+ // Load data sesuai tab aktif
+ useShallowEffect(() => {
+ if (activeTab === "ringkasan") {
+ ringkasanState.findStats.load();
+ } else if (activeTab === "posyandu") {
+ posyandustate.findMany.load(1, 6, debouncedSearchPosyandu);
+ } else if (activeTab === "balita") {
+ balitaState.findMany.load(1, 10, debouncedSearchBalita, filterStunting ?? "");
+ } else if (activeTab === "ibu-hamil") {
+ ibuHamilState.findMany.load(1, 10, debouncedSearchIbuHamil, filterStatusIbuHamil ?? "");
+ }
+ }, [activeTab]);
useShallowEffect(() => {
- load(page, 6, debouncedSearch);
- }, [page, debouncedSearch]);
+ if (activeTab === "posyandu") {
+ posyandustate.findMany.load(1, 6, debouncedSearchPosyandu);
+ }
+ }, [debouncedSearchPosyandu]);
- if (loading || !data) {
- return (
-
-
-
- );
- }
+ useShallowEffect(() => {
+ if (activeTab === "balita") {
+ balitaState.findMany.load(1, 10, debouncedSearchBalita, filterStunting ?? "");
+ }
+ }, [debouncedSearchBalita, filterStunting]);
- if (data.length === 0) {
- return (
-
-
-
-
-
- Posyandu Desa Darmasaba
-
- }
- w={{ base: "100%", md: "35%" }}
- value={search}
- onChange={(e) => setSearch(e.currentTarget.value)}
- />
-
-
- Belum ada posyandu yang terdaftar
-
- );
- }
+ useShallowEffect(() => {
+ if (activeTab === "ibu-hamil") {
+ ibuHamilState.findMany.load(1, 10, debouncedSearchIbuHamil, filterStatusIbuHamil ?? "");
+ }
+ }, [debouncedSearchIbuHamil, filterStatusIbuHamil]);
+
+ const stats = stRingkasan.findStats.data;
return (
-
+
+ {/* Header */}
-
-
- Posyandu Desa Darmasaba
+
+
+ Kesehatan Posyandu
- }
- w={{ base: "100%", md: "35%" }}
- value={search}
- onChange={(e) => setSearch(e.currentTarget.value)}
- />
-
+
+ Informasi posyandu, data balita, ibu hamil, dan ringkasan kesehatan Desa Darmasaba
+
+
+ {/* Tabs */}
-
-
+
+
- {data?.map((v, k) => (
- {
- (e.currentTarget as HTMLElement).style.transform = "translateY(-4px)";
- (e.currentTarget as HTMLElement).style.boxShadow =
- "0 8px 24px rgba(0,0,0,0.12)";
- }}
- onMouseLeave={(e) => {
- (e.currentTarget as HTMLElement).style.transform = "translateY(0)";
- (e.currentTarget as HTMLElement).style.boxShadow =
- "0 4px 12px rgba(0,0,0,0.08)";
- }}
- >
-
-
-
- {v.name}
-
-
- Aktif
-
-
+
+ }>
+ Ringkasan
+
+ }>
+ Data Posyandu
+
+ }>
+ Balita
+
+ }>
+ Ibu Hamil
+
+
+
+
+ {/* ===== RINGKASAN ===== */}
+
+ {stRingkasan.findStats.loading ? (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ ) : !stats ? (
+
+ Data ringkasan belum tersedia
+
+ ) : (
+
+ {/* KPI utama */}
+
+ {[
+ {
+ label: "Ibu Hamil Aktif",
+ value: stats.ibuHamilAktif,
+ color: colors["blue-button"],
+ icon: ,
+ },
+ {
+ label: "Balita Terdaftar",
+ value: stats.balitaTerdaftar,
+ color: "#22c55e",
+ icon: ,
+ },
+ {
+ label: "Alert Stunting",
+ value: stats.alertStunting,
+ color: "#ef4444",
+ icon: ,
+ },
+ ].map((s, i) => (
+
+
+
+ {s.icon}
+
+
+
+ {s.value}
+
+ {s.label}
+
+
+
+ ))}
+
+
+ {/* Persentase */}
+
+ {[
+ {
+ label: "Imunisasi Lengkap",
+ pct: stats.imunisasiLengkapPct,
+ color: colors["blue-button"],
+ icon: ,
+ },
+ {
+ label: "Gizi Baik",
+ pct: stats.giziBaikPct,
+ color: "#22c55e",
+ icon: ,
+ },
+ {
+ label: "Pemeriksaan Rutin",
+ pct: stats.pemeriksaanRutinPct,
+ color: "#f59e0b",
+ icon: ,
+ },
+ ].map((s, i) => (
+
+
+
+
+ {s.icon}
+
+ {s.label}
+
+
+
+
+ {s.pct}%
+
+
+
+
+ ))}
+
+
+ )}
+
+
+ {/* ===== DATA POSYANDU ===== */}
+
+
+ }
+ value={searchPosyandu}
+ onChange={(e) => setSearchPosyandu(e.currentTarget.value)}
+ w={{ base: "100%", md: "40%" }}
+ />
+
+ {stPosyandu.findMany.loading ? (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ ) : !stPosyandu.findMany.data?.length ? (
+
+ Tidak ada posyandu ditemukan
+
+ ) : (
+ <>
+
+ {stPosyandu.findMany.data.map((v, k) => (
+ {
+ (e.currentTarget as HTMLElement).style.transform = "translateY(-4px)";
+ (e.currentTarget as HTMLElement).style.boxShadow =
+ "0 8px 24px rgba(0,0,0,0.12)";
+ }}
+ onMouseLeave={(e) => {
+ (e.currentTarget as HTMLElement).style.transform = "translateY(0)";
+ (e.currentTarget as HTMLElement).style.boxShadow = "";
+ }}
+ >
+
+
+
+ {v.name}
+
+
+ Aktif
+
+
+
+
+
+ {v.banjar && (
+
+
+ Banjar {v.banjar.name}
+
+ )}
+
+
+
+ {v.nomor || "–"}
+
+
+
+
+
+
+
+
+ router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)
+ }
+ >
+ Lihat Detail
+
+
+
+ ))}
+
+
-
+ posyandustate.findMany.load(p, 6, searchPosyandu)
+ }
+ total={stPosyandu.findMany.totalPages}
+ radius="lg"
+ color={colors["blue-button"]}
/>
-
-
-
- {v.nomor || "Tidak tersedia"}
-
-
+ >
+ )}
+
+
-
-
-
- Jadwal: {" "}
-
-
-
+ {/* ===== BALITA ===== */}
+
+
+
+ }
+ value={searchBalita}
+ onChange={(e) => setSearchBalita(e.currentTarget.value)}
+ style={{ flex: 1, minWidth: 200 }}
+ />
+
+
-
-
-
-
- router.push(`/darmasaba/kesehatan/posyandu/${v.id}`)}>
- Detail
-
+ {stBalita.findMany.loading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
-
- ))}
-
+ ) : !stBalita.findMany.data?.length ? (
+
+ Tidak ada data balita ditemukan
+
+ ) : (
+ <>
+
+ Total: {stBalita.findMany.total} balita terdaftar
+
+
+ {stBalita.findMany.data.map((v, i) => (
+
+
+
+
+ {v.nama}
+
+ {v.jenisKelamin === "L" ? "Laki-laki" : "Perempuan"}
+ {v.tanggalLahir
+ ? ` · ${new Date(v.tanggalLahir).toLocaleDateString("id-ID", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ })}`
+ : ""}
+
+
+
+ {v.statusStunting}
+
+
-
- load(newPage)}
- total={totalPages}
- radius="lg"
- size="md"
- withControls
- mt="md"
- />
-
+ {v.posyandu && (
+
+
+
+ {v.posyandu.name}
+ {v.posyandu.banjar
+ ? ` · Banjar ${v.posyandu.banjar.name}`
+ : ""}
+
+
+ )}
-
-
-
-
- Layanan Utama Posyandu
-
-
-
- Penimbangan bayi dan balita
- Pemantauan status gizi
- Imunisasi dasar lengkap
- Konseling kesehatan
- Pemantauan kesehatan ibu hamil
-
-
-
+
+
+ {v.imunisasiLengkap
+ ? "Imunisasi Lengkap"
+ : "Imunisasi Belum Lengkap"}
+
+
+ {v.giziBaik ? "Gizi Baik" : "Gizi Kurang"}
+
+
+ {v.pemeriksaanRutin ? "Periksa Rutin" : "Tidak Rutin"}
+
+
+
+
+ ))}
+
+
+
+
+ balitaState.findMany.load(
+ p,
+ 10,
+ searchBalita,
+ filterStunting ?? ""
+ )
+ }
+ total={stBalita.findMany.totalPages}
+ radius="lg"
+ color={colors["blue-button"]}
+ />
+
+ >
+ )}
+
+
+
+ {/* ===== IBU HAMIL ===== */}
+
+
+
+ }
+ value={searchIbuHamil}
+ onChange={(e) => setSearchIbuHamil(e.currentTarget.value)}
+ style={{ flex: 1, minWidth: 200 }}
+ />
+
+
+
+ {stIbuHamil.findMany.loading ? (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ ) : !stIbuHamil.findMany.data?.length ? (
+
+ Tidak ada data ibu hamil ditemukan
+
+ ) : (
+ <>
+
+ Total: {stIbuHamil.findMany.total} data ibu hamil
+
+
+ {stIbuHamil.findMany.data.map((v, i) => (
+
+
+
+
+ {v.nama}
+
+ Usia kehamilan: {v.usiaKehamilan} minggu
+
+
+
+ {v.status}
+
+
+
+ {v.posyandu && (
+
+
+
+ {v.posyandu.name}
+ {v.posyandu.banjar
+ ? ` · Banjar ${v.posyandu.banjar.name}`
+ : ""}
+
+
+ )}
+
+ {v.taksiranLahir && (
+
+
+
+ Taksiran lahir:{" "}
+ {new Date(v.taksiranLahir).toLocaleDateString("id-ID", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ })}
+
+
+ )}
+
+
+ ))}
+
+
+
+
+ ibuHamilState.findMany.load(
+ p,
+ 10,
+ searchIbuHamil,
+ filterStatusIbuHamil ?? ""
+ )
+ }
+ total={stIbuHamil.findMany.totalPages}
+ radius="lg"
+ color={colors["blue-button"]}
+ />
+
+ >
+ )}
+
+
+
);
-}
\ No newline at end of file
+}