diff --git a/src/app/api/noc/[[...slug]]/route.ts b/src/app/api/noc/[[...slug]]/route.ts index 155449f..6db08d7 100644 --- a/src/app/api/noc/[[...slug]]/route.ts +++ b/src/app/api/noc/[[...slug]]/route.ts @@ -1,7 +1,8 @@ +import { prisma } from "@/module/_global"; import cors from "@elysiajs/cors"; import { swagger } from "@elysiajs/swagger"; import Elysia, { t } from "elysia"; -import { prisma } from "@/module/_global"; +import _ from "lodash"; import moment from "moment"; import "moment/locale/id"; @@ -11,7 +12,7 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) origin: "*", methods: ["GET", "POST", "OPTIONS"], })) - .use(swagger({ + .use(swagger({ path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs documentation: { info: { @@ -231,141 +232,11 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) } ) - // ── GET /api/noc/village-summary ─────────────────────────────────────────── - .get( - "/village-summary", - async ({ query, set }) => { - const { idDesa } = query; - - if (!idDesa) { - set.status = 400; - return { success: false, message: "Parameter idDesa wajib diisi", data: null }; - } - - try { - const counts = await prisma.village.findUnique({ - where: { id: idDesa }, - select: { - name: true, - _count: { - select: { - User: true, - Group: true, - Division: true, - Project: true, - Announcement: true, - Discussion: true, - } - } - } - }); - - if (!counts) { - set.status = 404; - return { success: false, message: "Desa tidak ditemukan", data: null }; - } - - return { - success: true, - message: "Berhasil mendapatkan ringkasan desa", - data: { - idDesa, - namaDesa: counts.name, - summary: { - totalWarga: counts._count.User, - totalGrup: counts._count.Group, - totalDivisi: counts._count.Division, - totalProyek: counts._count.Project, - totalPengumuman: counts._count.Announcement, - totalDiskusi: counts._count.Discussion, - } - } - }; - } catch (error) { - console.error("[NOC] village-summary error:", error); - set.status = 500; - return { success: false, message: "Terjadi kesalahan pada server", data: null }; - } - }, - { - query: t.Object({ idDesa: t.String() }), - detail: { summary: "Village Summary Statistics", tags: ["NOC"] } - } - ) - - // ── GET /api/noc/recent-activity ─────────────────────────────────────────── - .get( - "/recent-activity", - 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 ?? 10), 50); - - try { - const logs = await prisma.userLog.findMany({ - where: { - User: { - idVillage: idDesa - } - }, - select: { - id: true, - action: true, - desc: true, - createdAt: true, - User: { - select: { - name: true, - img: true, - Group: { select: { name: true } } - } - } - }, - orderBy: { createdAt: "desc" }, - take: maxResults - }); - - const mapped = logs.map(l => ({ - id: l.id, - userName: l.User.name, - userGroup: l.User.Group.name, - userImg: l.User.img, - action: l.action, - description: l.desc, - time: moment(l.createdAt).fromNow(), - date: moment(l.createdAt).format("YYYY-MM-DD HH:mm:ss") - })); - - return { - success: true, - message: "Berhasil mendapatkan aktivitas terbaru", - data: { idDesa, total: mapped.length, activities: mapped } - }; - } catch (error) { - console.error("[NOC] recent-activity error:", error); - set.status = 500; - return { success: false, message: "Terjadi kesalahan pada server", data: null }; - } - }, - { - query: t.Object({ - idDesa: t.String(), - limit: t.Optional(t.String()) - }), - detail: { summary: "Recent User Activity Logs", tags: ["NOC"] } - } - ) - // ── GET /api/noc/upcoming-events ─────────────────────────────────────────── .get( "/upcoming-events", async ({ query, set }) => { - const { idDesa, limit } = query; + const { idDesa, limit, filter } = query; if (!idDesa) { set.status = 400; @@ -443,7 +314,8 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) take: maxResults, }); - const mapped = events.map((e) => ({ + const todayMoment = moment().startOf("day"); + const mapper = (e: any) => ({ id: e.id, idCalendar: e.idCalendar, title: e.DivisionCalendar.title, @@ -462,17 +334,29 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) id: e.Division.id, name: e.Division.name, }, - })); + }); + + const todayEvents = events.filter(e => moment(e.dateStart).isSame(todayMoment, 'day')).map(mapper); + const upcomingEvents = events.filter(e => 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 upcoming events", - data: { - idDesa: village.id, - namaDesa: village.name, - total: mapped.length, - events: mapped, - }, + message: "Berhasil mendapatkan events", + data: data, }; } catch (error) { console.error("[NOC] upcoming-events error:", error); @@ -490,14 +374,314 @@ const NocServer = new Elysia({ prefix: "/api/noc" }) 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: "Upcoming Events", - description: "Mendapatkan daftar event yang akan datang untuk semua divisi pada desa tertentu.", + summary: "Events (Today & Upcoming)", + description: "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: "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, { _count }) => 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: "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: "Mendapatkan latest discussion pada desa tertentu.", tags: ["NOC"], }, } ); + export const GET = NocServer.handle; export const POST = NocServer.handle;