diff --git a/package.json b/package.json index a9039100..ea32368a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "desa-darmasaba", - "version": "0.1.44", + "version": "0.1.45", "private": true, "scripts": { "dev": "next dev", diff --git a/prisma/migrations/20260430000000_add_sosial_fields/migration.sql b/prisma/migrations/20260430000000_add_sosial_fields/migration.sql new file mode 100644 index 00000000..a62ec2aa --- /dev/null +++ b/prisma/migrations/20260430000000_add_sosial_fields/migration.sql @@ -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") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f29f5eff..b863283d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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) +} + diff --git a/src/app/api/[[...slugs]]/_lib/desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/index.ts index c6d4b754..bed69bfd 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/index.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/index.ts @@ -13,6 +13,7 @@ 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"; const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] }) @@ -30,6 +31,7 @@ const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] }) .use(KategoriPengumuman) .use(AjukanPermohonan) .use(Musik) + .use(KegiatanDesa) export default Desa; diff --git a/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/create.ts b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/create.ts new file mode 100644 index 00000000..be45715a --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/create.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/del.ts b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/del.ts new file mode 100644 index 00000000..e2c55643 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/del.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/find-many.ts new file mode 100644 index 00000000..263aa326 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/find-many.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/index.ts new file mode 100644 index 00000000..542aa85e --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/index.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/updt.ts new file mode 100644 index 00000000..7273bbfe --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/kegiatan-desa/updt.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts index ccf09347..ed5d28d1 100644 --- a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/findUnique.ts new file mode 100644 index 00000000..764ca361 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/findUnique.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts new file mode 100644 index 00000000..8de4ec93 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/index.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/updt.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/updt.ts new file mode 100644 index 00000000..412f5302 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/ringkasan-kesehatan/updt.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/findUnique.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/findUnique.ts new file mode 100644 index 00000000..a5cd7044 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/findUnique.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/index.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/index.ts new file mode 100644 index 00000000..f75d2af4 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/index.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/updt.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/updt.ts new file mode 100644 index 00000000..e7321cc0 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/beasiswa-config/updt.ts @@ -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; diff --git a/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/index.ts b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/index.ts index 3358a11d..e5805f3d 100644 --- a/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/index.ts +++ b/src/app/api/[[...slugs]]/_lib/pendidikan/beasiswa-desa/index.ts @@ -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 \ No newline at end of file