diff --git a/.env.example b/.env.example index 0c0e5bd..edc94f4 100644 --- a/.env.example +++ b/.env.example @@ -17,3 +17,4 @@ LOG_LEVEL=info # Public URL VITE_PUBLIC_URL="http://localhost:3000" +NOC_API_URL="https://darmasaba.muku.id/api/noc/docs/json" diff --git a/__tests__/api/noc.test.ts b/__tests__/api/noc.test.ts new file mode 100644 index 0000000..0291e35 --- /dev/null +++ b/__tests__/api/noc.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "bun:test"; +import api from "@/api"; + +describe("NOC API Module", () => { + const idDesa = "darmasaba"; + + it("should return active divisions", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/active-divisions?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return latest projects", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/latest-projects?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return upcoming events", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/upcoming-events?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return diagram jumlah document", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/diagram-jumlah-document?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return diagram progres kegiatan", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/diagram-progres-kegiatan?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return latest discussion", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/latest-discussion?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(Array.isArray(data.data)).toBe(true); + }); + + it("should return 400 for missing idDesa in active-divisions", async () => { + const response = await api.handle( + new Request("http://localhost/api/noc/active-divisions"), + ); + // Elysia returns 400 or 422 for validation errors + expect([400, 422]).toContain(response.status); + }); +}); diff --git a/generated/api.ts b/generated/api.ts index e81c7e2..3909eed 100644 --- a/generated/api.ts +++ b/generated/api.ts @@ -457,6 +457,102 @@ export interface paths { patch?: never; trace?: never; }; + "/api/noc/active-divisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocActive-divisions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/noc/latest-projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocLatest-projects"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/noc/upcoming-events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocUpcoming-events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/noc/diagram-jumlah-document": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocDiagram-jumlah-document"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/noc/diagram-progres-kegiatan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocDiagram-progres-kegiatan"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/noc/latest-discussion": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getApiNocLatest-discussion"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { @@ -1974,4 +2070,279 @@ export interface operations { }; }; }; + "getApiNocActive-divisions": { + parameters: { + query: { + idDesa: string; + limit?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + name: string; + activityCount: number; + color: string; + }[]; + }; + "multipart/form-data": { + data: { + id: string; + name: string; + activityCount: number; + color: string; + }[]; + }; + "text/plain": { + data: { + id: string; + name: string; + activityCount: number; + color: string; + }[]; + }; + }; + }; + }; + }; + "getApiNocLatest-projects": { + parameters: { + query: { + idDesa: string; + limit?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + title: string; + status: string; + progress: number; + divisionName: string; + createdAt: string; + }[]; + }; + "multipart/form-data": { + data: { + id: string; + title: string; + status: string; + progress: number; + divisionName: string; + createdAt: string; + }[]; + }; + "text/plain": { + data: { + id: string; + title: string; + status: string; + progress: number; + divisionName: string; + createdAt: string; + }[]; + }; + }; + }; + }; + }; + "getApiNocUpcoming-events": { + parameters: { + query: { + idDesa: string; + limit?: string; + filter?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + title: string; + startDate: string; + location: (string | null) | null; + eventType: string; + }[]; + }; + "multipart/form-data": { + data: { + id: string; + title: string; + startDate: string; + location: (string | null) | null; + eventType: string; + }[]; + }; + "text/plain": { + data: { + id: string; + title: string; + startDate: string; + location: (string | null) | null; + eventType: string; + }[]; + }; + }; + }; + }; + }; + "getApiNocDiagram-jumlah-document": { + parameters: { + query: { + idDesa: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + category: string; + count: number; + }[]; + }; + "multipart/form-data": { + data: { + category: string; + count: number; + }[]; + }; + "text/plain": { + data: { + category: string; + count: number; + }[]; + }; + }; + }; + }; + }; + "getApiNocDiagram-progres-kegiatan": { + parameters: { + query: { + idDesa: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + status: string; + avgProgress: number; + count: number; + }[]; + }; + "multipart/form-data": { + data: { + status: string; + avgProgress: number; + count: number; + }[]; + }; + "text/plain": { + data: { + status: string; + avgProgress: number; + count: number; + }[]; + }; + }; + }; + }; + }; + "getApiNocLatest-discussion": { + parameters: { + query: { + idDesa: string; + limit?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + message: string; + senderName: string; + senderImage: (string | null) | null; + divisionName: string; + createdAt: string; + }[]; + }; + "multipart/form-data": { + data: { + id: string; + message: string; + senderName: string; + senderImage: (string | null) | null; + divisionName: string; + createdAt: string; + }[]; + }; + "text/plain": { + data: { + id: string; + message: string; + senderName: string; + senderImage: (string | null) | null; + divisionName: string; + createdAt: string; + }[]; + }; + }; + }; + }; + }; } diff --git a/generated/schema.json b/generated/schema.json index 1c28df3..4b24031 100644 --- a/generated/schema.json +++ b/generated/schema.json @@ -4022,6 +4022,892 @@ }, "operationId": "getApiDashboardSatisfaction" } + }, + "/api/noc/active-divisions": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "name", + "activityCount", + "color" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "activityCount": { + "type": "number" + }, + "color": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "name", + "activityCount", + "color" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "activityCount": { + "type": "number" + }, + "color": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "name", + "activityCount", + "color" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "activityCount": { + "type": "number" + }, + "color": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocActive-divisions" + } + }, + "/api/noc/latest-projects": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "status", + "progress", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "string" + }, + "progress": { + "type": "number" + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "status", + "progress", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "string" + }, + "progress": { + "type": "number" + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "status", + "progress", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "string" + }, + "progress": { + "type": "number" + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocLatest-projects" + } + }, + "/api/noc/upcoming-events": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "filter", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "startDate", + "location", + "eventType" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "location": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "eventType": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "startDate", + "location", + "eventType" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "location": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "eventType": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "title", + "startDate", + "location", + "eventType" + ], + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "location": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "eventType": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocUpcoming-events" + } + }, + "/api/noc/diagram-jumlah-document": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "category", + "count" + ], + "properties": { + "category": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "category", + "count" + ], + "properties": { + "category": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "category", + "count" + ], + "properties": { + "category": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocDiagram-jumlah-document" + } + }, + "/api/noc/diagram-progres-kegiatan": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "status", + "avgProgress", + "count" + ], + "properties": { + "status": { + "type": "string" + }, + "avgProgress": { + "type": "number" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "status", + "avgProgress", + "count" + ], + "properties": { + "status": { + "type": "string" + }, + "avgProgress": { + "type": "number" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "status", + "avgProgress", + "count" + ], + "properties": { + "status": { + "type": "string" + }, + "avgProgress": { + "type": "number" + }, + "count": { + "type": "number" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocDiagram-progres-kegiatan" + } + }, + "/api/noc/latest-discussion": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "idDesa", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "message", + "senderName", + "senderImage", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "senderName": { + "type": "string" + }, + "senderImage": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "message", + "senderName", + "senderImage", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "senderName": { + "type": "string" + }, + "senderImage": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "message", + "senderName", + "senderImage", + "divisionName", + "createdAt" + ], + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "senderName": { + "type": "string" + }, + "senderImage": { + "nullable": true, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "divisionName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiNocLatest-discussion" + } } }, "components": { diff --git a/src/api/index.tsx b/src/api/index.tsx index c5ee92f..c5d999b 100644 --- a/src/api/index.tsx +++ b/src/api/index.tsx @@ -10,6 +10,7 @@ import { division } from "./division"; import { event } from "./event"; import { profile } from "./profile"; import { resident } from "./resident"; +import { noc } from "./noc"; const isProduction = process.env.NODE_ENV === "production"; @@ -35,6 +36,7 @@ const api = new Elysia({ }, }, ) + .use(noc) .use(apiMiddleware) .use(apikey) .use(profile) diff --git a/src/api/noc.ts b/src/api/noc.ts new file mode 100644 index 0000000..0c258c9 --- /dev/null +++ b/src/api/noc.ts @@ -0,0 +1,276 @@ +import { Elysia, t } from "elysia"; +import { prisma } from "../utils/db"; + +export const noc = new Elysia({ prefix: "/noc" }) + .get( + "/active-divisions", + async ({ query }) => { + const { idDesa, limit } = query; + // TODO: Filter by idDesa once schema supports it + const data = await prisma.division.findMany({ + include: { + _count: { + select: { activities: true }, + }, + }, + orderBy: { + activities: { + _count: "desc", + }, + }, + take: limit ? Number.parseInt(limit) : 5, + }); + + return { + data: data.map((d) => ({ + id: d.id, + name: d.name, + activityCount: d._count.activities, + color: d.color, + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + limit: t.Optional(t.String()), + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + id: t.String(), + name: t.String(), + activityCount: t.Number(), + color: t.String(), + }), + ), + }), + }, + }, + ) + .get( + "/latest-projects", + async ({ query }) => { + const { idDesa, limit } = query; + // TODO: Filter by idDesa once schema supports it + const data = await prisma.activity.findMany({ + orderBy: { createdAt: "desc" }, + take: limit ? Number.parseInt(limit) : 5, + include: { division: true }, + }); + + return { + data: data.map((a) => ({ + id: a.id, + title: a.title, + status: a.status, + progress: a.progress, + divisionName: a.division.name, + createdAt: a.createdAt.toISOString(), + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + limit: t.Optional(t.String()), + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + id: t.String(), + title: t.String(), + status: t.String(), + progress: t.Number(), + divisionName: t.String(), + createdAt: t.String(), + }), + ), + }), + }, + }, + ) + .get( + "/upcoming-events", + async ({ query }) => { + const { idDesa, limit, filter } = query; + // TODO: Filter by idDesa once schema supports it + const now = new Date(); + const where: any = {}; + + if (filter === "today") { + const startOfDay = new Date(now.setHours(0, 0, 0, 0)); + const endOfDay = new Date(now.setHours(23, 59, 59, 999)); + where.startDate = { + gte: startOfDay, + lte: endOfDay, + }; + } else { + where.startDate = { + gte: now, + }; + } + + const data = await prisma.event.findMany({ + where, + orderBy: { startDate: "asc" }, + take: limit ? Number.parseInt(limit) : 5, + }); + + return { + data: data.map((e) => ({ + id: e.id, + title: e.title, + startDate: e.startDate.toISOString(), + location: e.location, + eventType: e.eventType, + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + limit: t.Optional(t.String()), + filter: t.Optional(t.String()), // today/upcoming + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + id: t.String(), + title: t.String(), + startDate: t.String(), + location: t.Nullable(t.String()), + eventType: t.String(), + }), + ), + }), + }, + }, + ) + .get( + "/diagram-jumlah-document", + async ({ query }) => { + const { idDesa } = query; + // TODO: Filter by idDesa once schema supports it + const data = await prisma.document.groupBy({ + by: ["category"], + _count: { + _all: true, + }, + }); + + return { + data: data.map((d) => ({ + category: d.category, + count: d._count._all, + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + category: t.String(), + count: t.Number(), + }), + ), + }), + }, + }, + ) + .get( + "/diagram-progres-kegiatan", + async ({ query }) => { + const { idDesa } = query; + // TODO: Filter by idDesa once schema supports it + const data = await prisma.activity.groupBy({ + by: ["status"], + _avg: { + progress: true, + }, + _count: { + _all: true, + }, + }); + + return { + data: data.map((d) => ({ + status: d.status, + avgProgress: d._avg.progress || 0, + count: d._count._all, + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + status: t.String(), + avgProgress: t.Number(), + count: t.Number(), + }), + ), + }), + }, + }, + ) + .get( + "/latest-discussion", + async ({ query }) => { + const { idDesa, limit } = query; + // TODO: Filter by idDesa once schema supports it + const data = await prisma.discussion.findMany({ + orderBy: { createdAt: "desc" }, + take: limit ? Number.parseInt(limit) : 5, + include: { + sender: { + select: { name: true, image: true }, + }, + division: { + select: { name: true }, + }, + }, + }); + + return { + data: data.map((d) => ({ + id: d.id, + message: d.message, + senderName: d.sender.name || "Anonymous", + senderImage: d.sender.image, + divisionName: d.division?.name || "General", + createdAt: d.createdAt.toISOString(), + })), + }; + }, + { + query: t.Object({ + idDesa: t.String(), + limit: t.Optional(t.String()), + }), + response: { + 200: t.Object({ + data: t.Array( + t.Object({ + id: t.String(), + message: t.String(), + senderName: t.String(), + senderImage: t.Nullable(t.String()), + divisionName: t.String(), + createdAt: t.String(), + }), + ), + }), + }, + }); diff --git a/src/components/kinerja-divisi/activity-card.tsx b/src/components/kinerja-divisi/activity-card.tsx index f3bcac2..c661ab6 100644 --- a/src/components/kinerja-divisi/activity-card.tsx +++ b/src/components/kinerja-divisi/activity-card.tsx @@ -45,6 +45,7 @@ export function ActivityCard({ backgroundColor: dark ? "#334155" : "white", overflow: "hidden", }} + h={"100%"} > {/* 🔵 HEADER */}