import { prisma } from "@/module/_global"; import cors from "@elysiajs/cors"; import { swagger } from "@elysiajs/swagger"; import Elysia, { t } from "elysia"; import _ from "lodash"; import moment from "moment"; import "moment/locale/id"; // Gabungkan semua ke dalam satu instance server yang dipasang di /api/noc const NocServer = new Elysia({ prefix: "/api/noc" }) .use(cors({ origin: "*", methods: ["GET", "POST", "OPTIONS"], })) .use(swagger({ path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs documentation: { info: { title: "Sistem Desa Mandiri - NOC API", version: "1.0.0", description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa", }, tags: [ { name: "NOC", description: "Endpoint khusus monitoring" } ] } })) // ── GET /api/noc/active-divisions ────────────────────────────────────────── .get( "/active-divisions", async ({ query, set }) => { const { idDesa, limit } = query; if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } const maxResults = Number(limit ?? 5); try { // Cek apakah desa ada const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } // Ambil semua divisi milik desa ini const divisions = await prisma.division.findMany({ where: { idVillage: idDesa, isActive: true, }, select: { id: true, name: true, idGroup: true, Group: { select: { name: true, }, }, _count: { select: { DivisionProject: true, }, }, }, }); // Hitung total kegiatan per divisi & urutkan descending, ambil top sesuai limit const ranked = divisions .map((d: any) => ({ id: d.id, division: d.name, group: d.Group.name, totalKegiatan: d._count.DivisionProject })) .sort((a: any, b: any) => b.totalKegiatan - a.totalKegiatan) .slice(0, maxResults); return { success: true, message: "Berhasil mendapatkan divisi teraktif", data: { idDesa: village.id, namaDesa: village.name, divisi: ranked, }, }; } catch (error) { console.error("[NOC] active-divisions error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), limit: t.Optional(t.String({ description: "Jumlah maksimal data (default: 5)" })), }), detail: { summary: "Divisi Teraktif", description: "Menu Beranda - Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.", tags: ["NOC"], }, } ) // ── GET /api/noc/latest-projects ────────────────────────────────────────── .get( "/latest-projects", async ({ query, set }) => { const { idDesa, limit } = query; if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } const maxResults = Math.min(Number(limit ?? 5), 50); try { // Cek apakah desa ada const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } // Ambil proyek umum terbaru dari desa ini const projects = await prisma.project.findMany({ where: { idVillage: idDesa, isActive: true, }, select: { id: true, title: true, status: true, desc: true, updatedAt: true, Group: { select: { name: true, }, }, User: { select: { name: true, }, }, }, orderBy: { updatedAt: "desc", }, take: maxResults, }); const mapped = projects.map((p: any) => ({ id: p.id, title: p.title, status: p.status, desc: p.desc, group: p.Group.name, createdBy: p.User.name, updatedAt: p.updatedAt, })); return { success: true, message: "Berhasil mendapatkan proyek terbaru", data: { idDesa: village.id, namaDesa: village.name, total: mapped.length, projects: mapped, }, }; } catch (error) { console.error("[NOC] latest-projects error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), limit: t.Optional( t.String({ description: "Jumlah maksimal proyek (default: 5, maks: 50)" }) ), }), detail: { summary: "Latest Projects General", description: "Menu kinerja divisi - Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.", tags: ["NOC"], }, } ) // ── GET /api/noc/upcoming-events ─────────────────────────────────────────── .get( "/upcoming-events", async ({ query, set }) => { const { idDesa, limit, filter } = query; if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } const maxResults = Math.min(Number(limit ?? 10), 50); const today = moment().startOf("day").toDate(); try { const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } const events = await prisma.divisionCalendarReminder.findMany({ where: { isActive: true, dateStart: { gte: today, }, Division: { idVillage: idDesa, isActive: true, }, DivisionCalendar: { isActive: true, }, }, select: { id: true, idCalendar: true, dateStart: true, dateEnd: true, timeStart: true, timeEnd: true, status: true, Division: { select: { id: true, name: true, }, }, DivisionCalendar: { select: { title: true, desc: true, linkMeet: true, repeatEventTyper: true, User: { select: { name: true, }, }, }, }, }, orderBy: [ { dateStart: "asc" }, { timeStart: "asc" }, ], take: maxResults, }); const todayMoment = moment().startOf("day"); const mapper = (e: any) => ({ id: e.id, idCalendar: e.idCalendar, title: e.DivisionCalendar.title, desc: e.DivisionCalendar.desc, linkMeet: e.DivisionCalendar.linkMeet ?? null, repeatEventTyper: e.DivisionCalendar.repeatEventTyper, dateStart: moment(e.dateStart).format("YYYY-MM-DD"), dateEnd: e.dateEnd ? moment(e.dateEnd).format("YYYY-MM-DD") : null, timeStart: moment.utc(e.timeStart).format("HH:mm"), timeEnd: moment.utc(e.timeEnd).format("HH:mm"), status: e.status, createdBy: e.DivisionCalendar.User.name, divisi: { id: e.Division.id, name: e.Division.name, }, }); const todayEvents = events.filter((e: any) => moment(e.dateStart).isSame(todayMoment, 'day')).map(mapper); const upcomingEvents = events.filter((e: any) => moment(e.dateStart).isAfter(todayMoment, 'day')).map(mapper); let data: any = { idDesa: village.id, namaDesa: village.name, }; if (filter === "today") { data.events = todayEvents; } else if (filter === "upcoming") { data.events = upcomingEvents; } else { data.today = todayEvents; data.upcoming = upcomingEvents; } return { success: true, message: "Berhasil mendapatkan events", data: data, }; } catch (error) { console.error("[NOC] upcoming-events error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), limit: t.Optional( t.String({ description: "Jumlah maksimal event (default: 10, maks: 50)" }) ), filter: t.Optional( t.String({ description: "Filter event: 'today' atau 'upcoming'" }) ), }), detail: { summary: "Events (Today & Upcoming)", description: "Menu beranda dan kinerja divisi - Mendapatkan daftar event pada hari ini dan yang akan datang untuk semua divisi pada desa tertentu.", tags: ["NOC"], }, } ) // ── GET /api/noc/diagram-jumlah-document ─────────────────────────────────────────────── .get( "/diagram-jumlah-document", async ({ query, set }) => { const { idDesa } = query; if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } try { const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } const documents = await prisma.divisionDocumentFolderFile.findMany({ where: { isActive: true, category: 'FILE', Division: { isActive: true, idVillage: idDesa, Group: { isActive: true, } } } }) const groupData = _.map(_.groupBy(documents, "extension"), (v: any) => ({ file: v[0].extension, jumlah: v.length, })) const image = ['jpg', 'jpeg', 'png', 'heic'] let hasilImage = { label: 'Gambar', value: 0, color: '#fac858' } let hasilFile = { label: 'Dokumen', value: 0, color: '#92cc76' } groupData.map((v: any) => { if (image.some((i: any) => i == v.file)) { hasilImage = { label: 'Gambar', value: hasilImage.value + v.jumlah, color: '#fac858' } } else { hasilFile = { label: 'Dokumen', value: hasilFile.value + v.jumlah, color: '#92cc76' } } }) const allData = [hasilImage, hasilFile] return { success: true, message: "Berhasil mendapatkan jumlah document", data: allData }; } catch (error) { console.error("[NOC] jumlah-document error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), }), detail: { summary: "Diagram Jumlah Document", description: "Menu kinerja divisi - Mendapatkan diagram jumlah document pada desa tertentu.", tags: ["NOC"], }, } ) // -- GET /api/noc/diagram-progres-kegiatan .get( "/diagram-progres-kegiatan", async ({ query, set }) => { const { idDesa } = query; if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } try { const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } const data = await prisma.project.groupBy({ where: { isActive: true, idVillage: idDesa, Group: { isActive: true, } }, by: ["status"], _count: true }) const dataStatus = [{ name: 'Segera dikerjakan', status: 0, color: '#177AD5' }, { name: 'Dikerjakan', status: 1, color: '#fac858' }, { name: 'Selesai dikerjakan', status: 2, color: '#92cc76' }, { name: 'Dibatalkan', status: 3, color: '#ED6665' }] const hasil: any[] = [] let input for (let index = 0; index < dataStatus.length; index++) { const cek = data.some((i: any) => i.status == dataStatus[index].status) if (cek) { const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n: any, { _count }: any) => n + _count, 0)).toFixed(2) const fix = find != "100.00" ? find.substr(-2, 2) == "00" ? find.substr(0, 2) : find : "100" input = { text: fix + '%', value: fix, color: dataStatus[index].color } } else { input = { text: '0%', value: 0, color: dataStatus[index].color } } hasil.push(input) } return { success: true, message: "Berhasil mendapatkan progres kegiatan", data: hasil }; } catch (error) { console.error("[NOC] progres-kegiatan error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), }), detail: { summary: "Diagram Progres Kegiatan", description: "Menu kinerja divisi - Mendapatkan diagram progres kegiatan pada desa tertentu.", tags: ["NOC"], }, } ) // -- GET /api/noc/latest-discussion .get( "/latest-discussion", async ({ query, set }) => { const { idDesa, limit } = query; const maxResults = Math.min(Number(limit ?? 5), 50); if (!idDesa) { set.status = 400; return { success: false, message: "Parameter idDesa wajib diisi", data: null, }; } try { const village = await prisma.village.findUnique({ where: { id: idDesa }, select: { id: true, name: true }, }); if (!village) { set.status = 404; return { success: false, message: "Desa tidak ditemukan", data: null, }; } const data = await prisma.discussion.findMany({ take: maxResults, where: { idVillage: idDesa, isActive: true, status: 1, }, select: { id: true, title: true, desc: true, createdAt: true, User: { select: { name: true } }, Group: { select: { name: true } } }, orderBy: { createdAt: "desc" } }) const allData = data.map((v: any) => ({ ..._.omit(v, ["createdAt", "User", "Group"]), date: moment(v.createdAt).format("ll"), user: v.User.name, group: v.Group.name })) return { success: true, message: "Berhasil mendapatkan latest discussion", data: allData }; } catch (error) { console.error("[NOC] latest-discussion error:", error); set.status = 500; return { success: false, message: "Terjadi kesalahan pada server", data: null, }; } }, { query: t.Object({ idDesa: t.String({ description: "ID Desa yang ingin dicari" }), limit: t.Optional(t.String({ description: "Limit data" })), }), detail: { summary: "Latest Discussion", description: "Menu kinerja divisi - Mendapatkan latest discussion pada desa tertentu.", tags: ["NOC"], }, } ); export const GET = NocServer.handle; export const POST = NocServer.handle;