feat(sosial-dashboard): tambah API ringkasan pendidikan & beasiswa + CRUD event budaya - bump 0.1.55
- API GET /api/pendidikan/ringkasan/stats: siswa per jenjang, jumlah lembaga & pengajar - API GET /api/pendidikan/beasiswa/ringkasan/stats: jumlah penerima, dana, tahun ajaran - Schema + migration: model EventBudaya (nama, tanggal, lokasi, deskripsi) - API CRUD /api/desa/eventbudaya: create, find-many, findUnique, updt, del - State admin: eventBudaya.ts (valtio proxy, create/findMany/edit/delete) - Admin CMS: /admin/desa/event-budaya (list, create, edit) - Navbar: tambah entry Desa_9 Event Budaya di semua role - Seeder: 8 event budaya Bali untuk Desa Darmasaba Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "desa-darmasaba",
|
"name": "desa-darmasaba",
|
||||||
"version": "0.1.54",
|
"version": "0.1.55",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
30
prisma/_seeder_list/desa/event-budaya/seed_event_budaya.ts
Normal file
30
prisma/_seeder_list/desa/event-budaya/seed_event_budaya.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const eventBudayaJson = loadJsonData("desa/event-budaya/event-budaya.json");
|
||||||
|
|
||||||
|
export async function seedEventBudaya() {
|
||||||
|
console.log("🔄 Seeding Event Budaya...");
|
||||||
|
|
||||||
|
for (const item of eventBudayaJson) {
|
||||||
|
await prisma.eventBudaya.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
lokasi: item.lokasi,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
lokasi: item.lokasi,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` ✅ Event: ${item.nama}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 Event Budaya seed selesai");
|
||||||
|
}
|
||||||
58
prisma/data/desa/event-budaya/event-budaya.json
Normal file
58
prisma/data/desa/event-budaya/event-budaya.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "event-budaya-1",
|
||||||
|
"nama": "Hari Kesaktian Pancasila",
|
||||||
|
"tanggal": "2025-10-01T07:00:00.000Z",
|
||||||
|
"lokasi": "Balai Desa Darmasaba",
|
||||||
|
"deskripsi": "Peringatan Hari Kesaktian Pancasila diikuti seluruh perangkat desa dan warga Desa Darmasaba dengan upacara bendera dan kegiatan budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-2",
|
||||||
|
"nama": "Upacara Ngusaba Desa",
|
||||||
|
"tanggal": "2025-11-15T08:00:00.000Z",
|
||||||
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
|
"deskripsi": "Upacara adat tahunan Ngusaba Desa sebagai bentuk rasa syukur kepada Ida Sang Hyang Widhi Wasa atas keselamatan dan kemakmuran desa."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-3",
|
||||||
|
"nama": "Festival Budaya Desa Darmasaba",
|
||||||
|
"tanggal": "2026-05-20T09:00:00.000Z",
|
||||||
|
"lokasi": "Lapangan Desa Darmasaba",
|
||||||
|
"deskripsi": "Festival tahunan menampilkan kesenian tradisional Bali seperti tari kecak, legong, dan barong oleh sanggar seni dari Desa Darmasaba."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-4",
|
||||||
|
"nama": "Perayaan HUT Desa Darmasaba",
|
||||||
|
"tanggal": "2026-08-17T07:30:00.000Z",
|
||||||
|
"lokasi": "Balai Desa Darmasaba",
|
||||||
|
"deskripsi": "Peringatan Hari Ulang Tahun Kemerdekaan Republik Indonesia sekaligus hari jadi Desa Darmasaba dengan berbagai lomba dan pertunjukan budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-5",
|
||||||
|
"nama": "Perayaan Galungan dan Kuningan",
|
||||||
|
"tanggal": "2026-03-04T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Rangkaian perayaan Hari Raya Galungan dan Kuningan sebagai hari kemenangan dharma melawan adharma, dirayakan seluruh umat Hindu di Desa Darmasaba."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-6",
|
||||||
|
"nama": "Lomba Ogoh-Ogoh Desa",
|
||||||
|
"tanggal": "2026-03-18T15:00:00.000Z",
|
||||||
|
"lokasi": "Lapangan Desa Darmasaba",
|
||||||
|
"deskripsi": "Lomba pembuatan dan parade ogoh-ogoh antar banjar se-Desa Darmasaba dalam rangka menyambut Hari Raya Nyepi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-7",
|
||||||
|
"nama": "Pementasan Wayang Kulit",
|
||||||
|
"tanggal": "2026-06-10T19:00:00.000Z",
|
||||||
|
"lokasi": "Wantilan Desa Darmasaba",
|
||||||
|
"deskripsi": "Pementasan wayang kulit semalam suntuk oleh dalang dari Desa Darmasaba sebagai bagian dari pelestarian seni budaya Bali."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-8",
|
||||||
|
"nama": "Upacara Melaspas Gedung Balai Banjar",
|
||||||
|
"tanggal": "2026-09-05T08:00:00.000Z",
|
||||||
|
"lokasi": "Banjar Desa Darmasaba",
|
||||||
|
"deskripsi": "Upacara Melaspas sebagai ritual penyucian bangunan baru balai banjar agar membawa keselamatan dan kesejahteraan bagi krama banjar."
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "PasarDesa" DROP CONSTRAINT "PasarDesa_kategoriProdukId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "KategoriProdukUmkm" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "EventBudaya" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"nama" TEXT NOT NULL,
|
||||||
|
"tanggal" TIMESTAMP(3) NOT NULL,
|
||||||
|
"lokasi" TEXT NOT NULL,
|
||||||
|
"deskripsi" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
CONSTRAINT "EventBudaya_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_kategoriProdukId_fkey" FOREIGN KEY ("kategoriProdukId") REFERENCES "KategoriProdukUmkm"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -2553,3 +2553,15 @@ model RingkasanKesehatanDesa {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= EVENT BUDAYA ========================================= //
|
||||||
|
model EventBudaya {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
tanggal DateTime
|
||||||
|
lokasi String
|
||||||
|
deskripsi String? @db.Text
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { seedBerita } from "./_seeder_list/desa/berita/seed_berita";
|
import { seedBerita } from "./_seeder_list/desa/berita/seed_berita";
|
||||||
import { seedKegiatanDesa } from "./_seeder_list/desa/seed_kegiatan_desa";
|
import { seedKegiatanDesa } from "./_seeder_list/desa/seed_kegiatan_desa";
|
||||||
|
import { seedEventBudaya } from "./_seeder_list/desa/event-budaya/seed_event_budaya";
|
||||||
import { seedFoto } from "./_seeder_list/desa/gallery/foto/seed_foto";
|
import { seedFoto } from "./_seeder_list/desa/gallery/foto/seed_foto";
|
||||||
import { seedVideo } from "./_seeder_list/desa/gallery/video/seed_video";
|
import { seedVideo } from "./_seeder_list/desa/gallery/video/seed_video";
|
||||||
import { seedLayanan } from "./_seeder_list/desa/layanan/seed_layanan";
|
import { seedLayanan } from "./_seeder_list/desa/layanan/seed_layanan";
|
||||||
@@ -392,6 +393,7 @@ import seedAssets from "./seed_assets";
|
|||||||
// ===== SOSIAL DASHBOARD =====
|
// ===== SOSIAL DASHBOARD =====
|
||||||
await seedRingkasanKesehatan();
|
await seedRingkasanKesehatan();
|
||||||
await seedKegiatanDesa();
|
await seedKegiatanDesa();
|
||||||
|
await seedEventBudaya();
|
||||||
|
|
||||||
// ===== DESA =====
|
// ===== DESA =====
|
||||||
await seedMusikDesa();
|
await seedMusikDesa();
|
||||||
|
|||||||
211
src/app/admin/(dashboard)/_state/desa/eventBudaya.ts
Normal file
211
src/app/admin/(dashboard)/_state/desa/eventBudaya.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const templateForm = z.object({
|
||||||
|
nama: z.string().min(1, "Nama event harus diisi"),
|
||||||
|
tanggal: z.string().min(1, "Tanggal harus diisi"),
|
||||||
|
lokasi: z.string().min(1, "Lokasi harus diisi"),
|
||||||
|
deskripsi: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
nama: "",
|
||||||
|
tanggal: "",
|
||||||
|
lokasi: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventBudayaState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateForm.safeParse(eventBudayaState.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eventBudayaState.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.desa["eventbudaya"]["create"].post(
|
||||||
|
eventBudayaState.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
eventBudayaState.findMany.load();
|
||||||
|
toast.success("Event budaya berhasil disimpan!");
|
||||||
|
eventBudayaState.create.form = { ...defaultForm };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
toast.error(res.data?.message || "Gagal menyimpan event budaya");
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Gagal menyimpan event budaya");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findMany: {
|
||||||
|
data: null as Prisma.EventBudayaGetPayload<object>[] | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
eventBudayaState.findMany.loading = true;
|
||||||
|
eventBudayaState.findMany.page = page;
|
||||||
|
eventBudayaState.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.desa["eventbudaya"]["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
eventBudayaState.findMany.data = res.data.data ?? [];
|
||||||
|
eventBudayaState.findMany.total = res.data.total ?? 0;
|
||||||
|
eventBudayaState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
eventBudayaState.findMany.data = [];
|
||||||
|
eventBudayaState.findMany.total = 0;
|
||||||
|
eventBudayaState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading event budaya:", error);
|
||||||
|
eventBudayaState.findMany.data = [];
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.EventBudayaGetPayload<object> | null,
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) return;
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/desa/eventbudaya/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const result = await res.json();
|
||||||
|
eventBudayaState.findUnique.data = result.data ?? null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching event budaya:", error);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
edit: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
eventBudayaState.edit.loading = true;
|
||||||
|
const res = await fetch(`/api/desa/eventbudaya/${id}`);
|
||||||
|
const result = await res.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
eventBudayaState.edit.id = data.id;
|
||||||
|
eventBudayaState.edit.form = {
|
||||||
|
nama: data.nama,
|
||||||
|
tanggal: data.tanggal
|
||||||
|
? new Date(data.tanggal).toISOString().split("T")[0]
|
||||||
|
: "",
|
||||||
|
lokasi: data.lokasi,
|
||||||
|
deskripsi: data.deskripsi ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading event budaya for edit:", error);
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
const cek = templateForm.safeParse(eventBudayaState.edit.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eventBudayaState.edit.loading = true;
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/desa/eventbudaya/${eventBudayaState.edit.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(eventBudayaState.edit.form),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Event budaya berhasil diupdate");
|
||||||
|
eventBudayaState.findMany.load();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
toast.error(result.message);
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.edit.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
eventBudayaState.edit.id = "";
|
||||||
|
eventBudayaState.edit.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
eventBudayaState.delete.loading = true;
|
||||||
|
const res = await fetch(`/api/desa/eventbudaya/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (res.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Event budaya berhasil dihapus");
|
||||||
|
await eventBudayaState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus event budaya");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Gagal menghapus event budaya");
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default eventBudayaState;
|
||||||
110
src/app/admin/(dashboard)/desa/event-budaya/[id]/edit/page.tsx
Normal file
110
src/app/admin/(dashboard)/desa/event-budaya/[id]/edit/page.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
'use client';
|
||||||
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function EditEventBudayaPage() {
|
||||||
|
const state = useProxy(eventBudayaState);
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const id = params.id as string;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) state.edit.load(id);
|
||||||
|
return () => state.edit.reset();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
const ok = await state.edit.save();
|
||||||
|
if (ok) router.push('/admin/desa/event-budaya');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.edit.loading && !state.edit.form.nama) {
|
||||||
|
return <Skeleton h={400} radius="md" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Group mb="lg">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya')}
|
||||||
|
>
|
||||||
|
<IconArrowBack size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
<Title order={4}>Edit Event Budaya</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Event"
|
||||||
|
placeholder="Contoh: Festival Budaya Desa"
|
||||||
|
required
|
||||||
|
value={state.edit.form.nama}
|
||||||
|
onChange={(e) => (state.edit.form.nama = e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tanggal"
|
||||||
|
type="date"
|
||||||
|
required
|
||||||
|
value={state.edit.form.tanggal}
|
||||||
|
onChange={(e) =>
|
||||||
|
(state.edit.form.tanggal = e.currentTarget.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Lokasi"
|
||||||
|
placeholder="Contoh: Balai Desa Darmasaba"
|
||||||
|
required
|
||||||
|
value={state.edit.form.lokasi}
|
||||||
|
onChange={(e) => (state.edit.form.lokasi = e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Deskripsi"
|
||||||
|
placeholder="Deskripsi singkat event (opsional)"
|
||||||
|
rows={4}
|
||||||
|
value={state.edit.form.deskripsi}
|
||||||
|
onChange={(e) =>
|
||||||
|
(state.edit.form.deskripsi = e.currentTarget.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya')}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="blue"
|
||||||
|
loading={state.edit.loading}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditEventBudayaPage;
|
||||||
95
src/app/admin/(dashboard)/desa/event-budaya/create/page.tsx
Normal file
95
src/app/admin/(dashboard)/desa/event-budaya/create/page.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
'use client';
|
||||||
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
function CreateEventBudayaPage() {
|
||||||
|
const state = useProxy(eventBudayaState);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const ok = await state.create.create();
|
||||||
|
if (ok) router.push('/admin/desa/event-budaya');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Group mb="lg">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya')}
|
||||||
|
>
|
||||||
|
<IconArrowBack size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
<Title order={4}>Tambah Event Budaya</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Event"
|
||||||
|
placeholder="Contoh: Festival Budaya Desa"
|
||||||
|
required
|
||||||
|
value={state.create.form.nama}
|
||||||
|
onChange={(e) => (state.create.form.nama = e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tanggal"
|
||||||
|
type="date"
|
||||||
|
required
|
||||||
|
value={state.create.form.tanggal}
|
||||||
|
onChange={(e) => (state.create.form.tanggal = e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Lokasi"
|
||||||
|
placeholder="Contoh: Balai Desa Darmasaba"
|
||||||
|
required
|
||||||
|
value={state.create.form.lokasi}
|
||||||
|
onChange={(e) => (state.create.form.lokasi = e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Deskripsi"
|
||||||
|
placeholder="Deskripsi singkat event (opsional)"
|
||||||
|
rows={4}
|
||||||
|
value={state.create.form.deskripsi}
|
||||||
|
onChange={(e) =>
|
||||||
|
(state.create.form.deskripsi = e.currentTarget.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya')}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="blue"
|
||||||
|
loading={state.create.loading}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateEventBudayaPage;
|
||||||
167
src/app/admin/(dashboard)/desa/event-budaya/page.tsx
Normal file
167
src/app/admin/(dashboard)/desa/event-budaya/page.tsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client';
|
||||||
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
import { IconCalendarEvent, IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
|
||||||
|
function EventBudayaPage() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title="Event Budaya"
|
||||||
|
placeholder="Cari nama atau lokasi..."
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListEventBudaya search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListEventBudaya({ search }: { search: string }) {
|
||||||
|
const state = useProxy(eventBudayaState);
|
||||||
|
const router = useRouter();
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||||
|
|
||||||
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10, debouncedSearch);
|
||||||
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py="md">
|
||||||
|
<Skeleton h={400} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py="md">
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Group justify="space-between" mb="lg">
|
||||||
|
<Title order={4}>List Event Budaya</Title>
|
||||||
|
<Button
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya/create')}
|
||||||
|
>
|
||||||
|
Tambah Event
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover layout="fixed" withColumnBorders={false}>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh w="30%">Nama Event</TableTh>
|
||||||
|
<TableTh w="20%">Tanggal</TableTh>
|
||||||
|
<TableTh w="25%">Lokasi</TableTh>
|
||||||
|
<TableTh w="25%">Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{data.length > 0 ? (
|
||||||
|
data.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Group gap="xs">
|
||||||
|
<IconCalendarEvent size={16} color="blue" />
|
||||||
|
<Text fz="sm" fw={500} truncate="end" lineClamp={1}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Badge variant="light" color="indigo">
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz="sm" c="dimmed" truncate="end" lineClamp={1}>
|
||||||
|
{item.lokasi}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Group gap="xs">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/desa/event-budaya/${item.id}/edit`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
loading={state.delete.loading}
|
||||||
|
onClick={() => state.delete.byId(item.id)}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Text ta="center" c="dimmed" py="xl">
|
||||||
|
Belum ada data event budaya
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Group justify="center" mt="lg">
|
||||||
|
<Pagination
|
||||||
|
total={totalPages}
|
||||||
|
value={page}
|
||||||
|
onChange={(p) => load(p, 10, search)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EventBudayaPage;
|
||||||
@@ -123,6 +123,11 @@ export const devBar = [
|
|||||||
id: "Desa_8",
|
id: "Desa_8",
|
||||||
name: "Kegiatan Desa",
|
name: "Kegiatan Desa",
|
||||||
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_9",
|
||||||
|
name: "Event Budaya",
|
||||||
|
path: "/admin/desa/event-budaya"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -559,6 +564,11 @@ export const navBar = [
|
|||||||
id: "Desa_8",
|
id: "Desa_8",
|
||||||
name: "Kegiatan Desa",
|
name: "Kegiatan Desa",
|
||||||
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_9",
|
||||||
|
name: "Event Budaya",
|
||||||
|
path: "/admin/desa/event-budaya"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -1010,6 +1020,11 @@ export const role1 = [
|
|||||||
id: "Desa_8",
|
id: "Desa_8",
|
||||||
name: "Kegiatan Desa",
|
name: "Kegiatan Desa",
|
||||||
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
path: "/admin/desa/kegiatan-desa/list-kegiatan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_9",
|
||||||
|
name: "Event Budaya",
|
||||||
|
path: "/admin/desa/event-budaya"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
29
src/app/api/[[...slugs]]/_lib/desa/event-budaya/create.ts
Normal file
29
src/app/api/[[...slugs]]/_lib/desa/event-budaya/create.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function eventBudayaCreate(context: Context) {
|
||||||
|
const body = context.body as {
|
||||||
|
nama: string;
|
||||||
|
tanggal: string;
|
||||||
|
lokasi: string;
|
||||||
|
deskripsi?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.eventBudaya.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
tanggal: new Date(body.tanggal),
|
||||||
|
lokasi: body.lokasi,
|
||||||
|
deskripsi: body.deskripsi || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: "Event budaya berhasil dibuat", data };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaCreate:", e);
|
||||||
|
return { success: false, message: "Gagal membuat event budaya" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaCreate;
|
||||||
20
src/app/api/[[...slugs]]/_lib/desa/event-budaya/del.ts
Normal file
20
src/app/api/[[...slugs]]/_lib/desa/event-budaya/del.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function eventBudayaDelete(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.eventBudaya.update({
|
||||||
|
where: { id },
|
||||||
|
data: { isActive: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: "Event budaya berhasil dihapus" };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaDelete:", e);
|
||||||
|
return { success: false, message: "Gagal menghapus event budaya" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaDelete;
|
||||||
46
src/app/api/[[...slugs]]/_lib/desa/event-budaya/find-many.ts
Normal file
46
src/app/api/[[...slugs]]/_lib/desa/event-budaya/find-many.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function eventBudayaFindMany(context: Context) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const where: any = { isActive: true };
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
where.OR = [
|
||||||
|
{ nama: { contains: search, mode: "insensitive" } },
|
||||||
|
{ lokasi: { contains: search, mode: "insensitive" } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.eventBudaya.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { tanggal: "asc" },
|
||||||
|
}),
|
||||||
|
prisma.eventBudaya.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mengambil event budaya",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaFindMany:", e);
|
||||||
|
return { success: false, message: "Gagal mengambil data event budaya" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaFindMany;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function eventBudayaFindUnique(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.eventBudaya.findFirst({
|
||||||
|
where: { id, isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return { success: false, message: "Event budaya tidak ditemukan" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaFindUnique:", e);
|
||||||
|
return { success: false, message: "Gagal mengambil data event budaya" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaFindUnique;
|
||||||
29
src/app/api/[[...slugs]]/_lib/desa/event-budaya/index.ts
Normal file
29
src/app/api/[[...slugs]]/_lib/desa/event-budaya/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import eventBudayaFindMany from "./find-many";
|
||||||
|
import eventBudayaFindUnique from "./findUnique";
|
||||||
|
import eventBudayaCreate from "./create";
|
||||||
|
import eventBudayaDelete from "./del";
|
||||||
|
import eventBudayaUpdate from "./updt";
|
||||||
|
|
||||||
|
const EventBudaya = new Elysia({ prefix: "/eventbudaya", tags: ["Desa/Event Budaya"] })
|
||||||
|
.get("/find-many", eventBudayaFindMany)
|
||||||
|
.get("/:id", eventBudayaFindUnique)
|
||||||
|
.post("/create", eventBudayaCreate, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
tanggal: t.String(),
|
||||||
|
lokasi: t.String(),
|
||||||
|
deskripsi: t.Optional(t.String()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.put("/:id", eventBudayaUpdate, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
tanggal: t.String(),
|
||||||
|
lokasi: t.String(),
|
||||||
|
deskripsi: t.Optional(t.String()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.delete("/del/:id", eventBudayaDelete);
|
||||||
|
|
||||||
|
export default EventBudaya;
|
||||||
31
src/app/api/[[...slugs]]/_lib/desa/event-budaya/updt.ts
Normal file
31
src/app/api/[[...slugs]]/_lib/desa/event-budaya/updt.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
async function eventBudayaUpdate(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
const body = context.body as {
|
||||||
|
nama: string;
|
||||||
|
tanggal: string;
|
||||||
|
lokasi: string;
|
||||||
|
deskripsi?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.eventBudaya.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
tanggal: new Date(body.tanggal),
|
||||||
|
lokasi: body.lokasi,
|
||||||
|
deskripsi: body.deskripsi || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: "Event budaya berhasil diupdate", data };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaUpdate:", e);
|
||||||
|
return { success: false, message: "Gagal mengupdate event budaya" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaUpdate;
|
||||||
@@ -15,6 +15,7 @@ import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
|||||||
import Musik from "./musik";
|
import Musik from "./musik";
|
||||||
import KegiatanDesa from "./kegiatan-desa";
|
import KegiatanDesa from "./kegiatan-desa";
|
||||||
import KategoriKegiatan from "./kegiatan-desa/kategori-kegiatan";
|
import KategoriKegiatan from "./kegiatan-desa/kategori-kegiatan";
|
||||||
|
import EventBudaya from "./event-budaya";
|
||||||
|
|
||||||
|
|
||||||
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
||||||
@@ -34,6 +35,7 @@ const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
|||||||
.use(Musik)
|
.use(Musik)
|
||||||
.use(KegiatanDesa)
|
.use(KegiatanDesa)
|
||||||
.use(KategoriKegiatan)
|
.use(KategoriKegiatan)
|
||||||
|
.use(EventBudaya)
|
||||||
|
|
||||||
|
|
||||||
export default Desa;
|
export default Desa;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Elysia from "elysia";
|
|||||||
import BeasiswaPendaftar from "./beasiswa-pendaftar";
|
import BeasiswaPendaftar from "./beasiswa-pendaftar";
|
||||||
import KeunggulanProgram from "./keunggulan-program";
|
import KeunggulanProgram from "./keunggulan-program";
|
||||||
import BeasiswaConfig from "./beasiswa-config";
|
import BeasiswaConfig from "./beasiswa-config";
|
||||||
|
import RingkasanBeasiswa from "./ringkasan";
|
||||||
|
|
||||||
const Beasiswa = new Elysia({
|
const Beasiswa = new Elysia({
|
||||||
prefix: "/beasiswa",
|
prefix: "/beasiswa",
|
||||||
@@ -10,5 +11,6 @@ const Beasiswa = new Elysia({
|
|||||||
.use(BeasiswaPendaftar)
|
.use(BeasiswaPendaftar)
|
||||||
.use(KeunggulanProgram)
|
.use(KeunggulanProgram)
|
||||||
.use(BeasiswaConfig)
|
.use(BeasiswaConfig)
|
||||||
|
.use(RingkasanBeasiswa)
|
||||||
|
|
||||||
export default Beasiswa
|
export default Beasiswa
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import beasiswaRingkasanStats from "./stats";
|
||||||
|
|
||||||
|
const RingkasanBeasiswa = new Elysia({
|
||||||
|
prefix: "/ringkasan",
|
||||||
|
tags: ["Pendidikan/Beasiswa Desa"],
|
||||||
|
}).get("/stats", beasiswaRingkasanStats);
|
||||||
|
|
||||||
|
export default RingkasanBeasiswa;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
type StatsResult = {
|
||||||
|
jumlahPenerima: number;
|
||||||
|
danaTersalurkan: bigint;
|
||||||
|
tahunAjaran: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function beasiswaRingkasanStats(): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
data?: StatsResult;
|
||||||
|
message?: string;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
const [jumlahPenerima, config] = await Promise.all([
|
||||||
|
prisma.beasiswaPendaftar.count({ where: { isActive: true } }),
|
||||||
|
prisma.beasiswaConfig.findFirst({
|
||||||
|
where: { isActive: true },
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
jumlahPenerima,
|
||||||
|
danaTersalurkan: config?.danaTersalurkan ?? BigInt(0),
|
||||||
|
tahunAjaran: config?.tahunAjaran ?? "-",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("beasiswaRingkasanStats error:", e);
|
||||||
|
return { success: false, message: "Gagal menghitung statistik beasiswa" };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import PendidikanNonFormal from "./pendidikan-non-formal";
|
|||||||
import DataPendidikan from "./data-pendidikan";
|
import DataPendidikan from "./data-pendidikan";
|
||||||
import Beasiswa from "./beasiswa-desa";
|
import Beasiswa from "./beasiswa-desa";
|
||||||
import PerpustakaanDigital from "./perpustakaan-digital";
|
import PerpustakaanDigital from "./perpustakaan-digital";
|
||||||
|
import RingkasanPendidikan from "./ringkasan";
|
||||||
|
|
||||||
const Pendidikan = new Elysia({
|
const Pendidikan = new Elysia({
|
||||||
prefix: "/pendidikan",
|
prefix: "/pendidikan",
|
||||||
@@ -19,5 +20,6 @@ const Pendidikan = new Elysia({
|
|||||||
.use(DataPendidikan)
|
.use(DataPendidikan)
|
||||||
.use(Beasiswa)
|
.use(Beasiswa)
|
||||||
.use(PerpustakaanDigital)
|
.use(PerpustakaanDigital)
|
||||||
|
.use(RingkasanPendidikan)
|
||||||
|
|
||||||
export default Pendidikan;
|
export default Pendidikan;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Elysia from "elysia";
|
||||||
|
import pendidikanRingkasanStats from "./stats";
|
||||||
|
|
||||||
|
const RingkasanPendidikan = new Elysia({
|
||||||
|
prefix: "/ringkasan",
|
||||||
|
tags: ["Pendidikan/Ringkasan"],
|
||||||
|
}).get("/stats", pendidikanRingkasanStats);
|
||||||
|
|
||||||
|
export default RingkasanPendidikan;
|
||||||
46
src/app/api/[[...slugs]]/_lib/pendidikan/ringkasan/stats.ts
Normal file
46
src/app/api/[[...slugs]]/_lib/pendidikan/ringkasan/stats.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
type PerJenjang = { nama: string; jumlahSiswa: number };
|
||||||
|
|
||||||
|
type StatsResult = {
|
||||||
|
perJenjang: PerJenjang[];
|
||||||
|
jumlahLembaga: number;
|
||||||
|
jumlahPengajar: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function pendidikanRingkasanStats(): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
data?: StatsResult;
|
||||||
|
message?: string;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
const [jenjangList, jumlahLembaga, jumlahPengajar] = await Promise.all([
|
||||||
|
prisma.jenjangPendidikan.findMany({
|
||||||
|
where: { isActive: true },
|
||||||
|
include: {
|
||||||
|
lembagas: {
|
||||||
|
where: { isActive: true },
|
||||||
|
include: {
|
||||||
|
_count: { select: { siswa: { where: { isActive: true } } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.lembaga.count({ where: { isActive: true } }),
|
||||||
|
prisma.pengajar.count({ where: { isActive: true } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const perJenjang = jenjangList.map((j) => ({
|
||||||
|
nama: j.nama,
|
||||||
|
jumlahSiswa: j.lembagas.reduce((acc, l) => acc + l._count.siswa, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: { perJenjang, jumlahLembaga, jumlahPengajar },
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("pendidikanRingkasanStats error:", e);
|
||||||
|
return { success: false, message: "Gagal menghitung statistik pendidikan" };
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user