diff --git a/.gemini/hooks/telegram-notify.ts b/.gemini/hooks/telegram-notify.ts index 063d337..c958201 100755 --- a/.gemini/hooks/telegram-notify.ts +++ b/.gemini/hooks/telegram-notify.ts @@ -2,15 +2,21 @@ import { readFileSync } from "node:fs"; // Fungsi untuk mencari string terpanjang dalam objek (biasanya balasan AI) -function findLongestString(obj: any): string { +function findLongestString(obj: unknown): string { let longest = ""; - const search = (item: any) => { + const search = (item: unknown) => { if (typeof item === "string") { - if (item.length > longest.length) longest = item; + if (item.length > longest.length) { + longest = item; + } } else if (Array.isArray(item)) { - item.forEach(search); - } else if (item && typeof item === "object") { - Object.values(item).forEach(search); + for (const child of item) { + search(child); + } + } else if (item !== null && typeof item === "object") { + for (const value of Object.values(item)) { + search(value); + } } }; search(obj); diff --git a/generated/api.ts b/generated/api.ts index aaa60da..49d875c 100644 --- a/generated/api.ts +++ b/generated/api.ts @@ -383,7 +383,17 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + ok: boolean; + }; + "multipart/form-data": { + ok: boolean; + }; + "text/plain": { + ok: boolean; + }; + }; }; }; }; @@ -400,7 +410,17 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown; + }; + "multipart/form-data": { + data: unknown; + }; + "text/plain": { + data: unknown; + }; + }; }; }; }; @@ -894,7 +914,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -911,7 +957,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -928,7 +1000,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -945,7 +1043,48 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: { + total: number; + baru: number; + proses: number; + selesai: number; + }; + }; + "multipart/form-data": { + data: { + total: number; + baru: number; + proses: number; + selesai: number; + }; + }; + "text/plain": { + data: { + total: number; + baru: number; + proses: number; + selesai: number; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -962,7 +1101,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -979,7 +1144,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -996,7 +1187,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1013,7 +1230,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1030,7 +1273,39 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: { + count: number; + }; + }; + "multipart/form-data": { + data: { + count: number; + }; + }; + "text/plain": { + data: { + count: number; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1047,7 +1322,45 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: { + total: number; + heads: number; + poor: number; + }; + }; + "multipart/form-data": { + data: { + total: number; + heads: number; + poor: number; + }; + }; + "text/plain": { + data: { + total: number; + heads: number; + poor: number; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1064,7 +1377,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1081,7 +1420,48 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: { + religion: unknown[]; + gender: unknown[]; + occupation: unknown[]; + ageGroups: unknown[]; + }; + }; + "multipart/form-data": { + data: { + religion: unknown[]; + gender: unknown[]; + occupation: unknown[]; + ageGroups: unknown[]; + }; + }; + "text/plain": { + data: { + religion: unknown[]; + gender: unknown[]; + occupation: unknown[]; + ageGroups: unknown[]; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1098,7 +1478,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; @@ -1115,7 +1521,33 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": { + data: unknown[]; + }; + "multipart/form-data": { + data: unknown[]; + }; + "text/plain": { + data: unknown[]; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + "multipart/form-data": { + error: string; + }; + "text/plain": { + error: string; + }; + }; }; }; }; diff --git a/generated/schema.json b/generated/schema.json index e001a13..efe6eab 100644 --- a/generated/schema.json +++ b/generated/schema.json @@ -8,18 +8,96 @@ "paths": { "/api/health": { "get": { - "operationId": "getApiHealth", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + }, + "required": [ + "ok" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + }, + "required": [ + "ok" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + }, + "required": [ + "ok" + ] + } + } + } + } + }, + "operationId": "getApiHealth" } }, "/api/session": { "get": { - "operationId": "getApiSession", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": {} + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": {} + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": {} + }, + "required": [ + "data" + ] + } + } + } + } + }, + "operationId": "getApiSession" } }, "/api/apikey/": { @@ -1416,128 +1494,1552 @@ }, "/api/division/": { "get": { - "operationId": "getApiDivision", - "summary": "Get all divisions", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiDivision", + "summary": "Get all divisions" } }, "/api/division/activities": { "get": { - "operationId": "getApiDivisionActivities", - "summary": "Get recent activities", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiDivisionActivities", + "summary": "Get recent activities" } }, "/api/division/metrics": { "get": { - "operationId": "getApiDivisionMetrics", - "summary": "Get division performance metrics", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiDivisionMetrics", + "summary": "Get division performance metrics" } }, "/api/complaint/stats": { "get": { - "operationId": "getApiComplaintStats", - "summary": "Get complaint statistics", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "baru", + "proses", + "selesai" + ], + "properties": { + "total": { + "type": "number" + }, + "baru": { + "type": "number" + }, + "proses": { + "type": "number" + }, + "selesai": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "baru", + "proses", + "selesai" + ], + "properties": { + "total": { + "type": "number" + }, + "baru": { + "type": "number" + }, + "proses": { + "type": "number" + }, + "selesai": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "baru", + "proses", + "selesai" + ], + "properties": { + "total": { + "type": "number" + }, + "baru": { + "type": "number" + }, + "proses": { + "type": "number" + }, + "selesai": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintStats", + "summary": "Get complaint statistics" } }, "/api/complaint/recent": { "get": { - "operationId": "getApiComplaintRecent", - "summary": "Get recent complaints", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintRecent", + "summary": "Get recent complaints" } }, "/api/complaint/service-stats": { "get": { - "operationId": "getApiComplaintService-stats", - "summary": "Get service letter statistics by type", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintService-stats", + "summary": "Get service letter statistics by type" } }, "/api/complaint/innovation-ideas": { "get": { - "operationId": "getApiComplaintInnovation-ideas", - "summary": "Get recent innovation ideas", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintInnovation-ideas", + "summary": "Get recent innovation ideas" } }, "/api/complaint/service-trends": { "get": { - "operationId": "getApiComplaintService-trends", - "summary": "Get service letter trends for last 6 months", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintService-trends", + "summary": "Get service letter trends for last 6 months" } }, "/api/complaint/service-weekly": { "get": { - "operationId": "getApiComplaintService-weekly", - "summary": "Get service letter count for current week", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiComplaintService-weekly", + "summary": "Get service letter count for current week" } }, "/api/resident/stats": { "get": { - "operationId": "getApiResidentStats", - "summary": "Get resident statistics", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "heads", + "poor" + ], + "properties": { + "total": { + "type": "number" + }, + "heads": { + "type": "number" + }, + "poor": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "heads", + "poor" + ], + "properties": { + "total": { + "type": "number" + }, + "heads": { + "type": "number" + }, + "poor": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "total", + "heads", + "poor" + ], + "properties": { + "total": { + "type": "number" + }, + "heads": { + "type": "number" + }, + "poor": { + "type": "number" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiResidentStats", + "summary": "Get resident statistics" } }, "/api/resident/banjar-stats": { "get": { - "operationId": "getApiResidentBanjar-stats", - "summary": "Get population data per banjar", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiResidentBanjar-stats", + "summary": "Get population data per banjar" } }, "/api/resident/demographics": { "get": { - "operationId": "getApiResidentDemographics", - "summary": "Get demographics including religion, gender, occupation and age", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "religion", + "gender", + "occupation", + "ageGroups" + ], + "properties": { + "religion": { + "type": "array", + "items": {} + }, + "gender": { + "type": "array", + "items": {} + }, + "occupation": { + "type": "array", + "items": {} + }, + "ageGroups": { + "type": "array", + "items": {} + } + } + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "religion", + "gender", + "occupation", + "ageGroups" + ], + "properties": { + "religion": { + "type": "array", + "items": {} + }, + "gender": { + "type": "array", + "items": {} + }, + "occupation": { + "type": "array", + "items": {} + }, + "ageGroups": { + "type": "array", + "items": {} + } + } + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "religion", + "gender", + "occupation", + "ageGroups" + ], + "properties": { + "religion": { + "type": "array", + "items": {} + }, + "gender": { + "type": "array", + "items": {} + }, + "occupation": { + "type": "array", + "items": {} + }, + "ageGroups": { + "type": "array", + "items": {} + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiResidentDemographics", + "summary": "Get demographics including religion, gender, occupation and age" } }, "/api/event/": { "get": { - "operationId": "getApiEvent", - "summary": "Get upcoming events", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiEvent", + "summary": "Get upcoming events" } }, "/api/event/today": { "get": { - "operationId": "getApiEventToday", - "summary": "Get events for today", "responses": { - "200": {} - } + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + }, + "required": [ + "data" + ] + } + } + } + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "getApiEventToday", + "summary": "Get events for today" } } }, diff --git a/prisma/seed.ts b/prisma/seed.ts index 6d7ea95..be5bf86 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -165,7 +165,7 @@ async function seedResidents(banjarIds: string[]) { gender: Gender.LAKI_LAKI, religion: Religion.HINDU, occupation: "Wiraswasta", - banjarId: banjarIds[0], + banjarId: banjarIds[0] || "", rt: "001", rw: "000", address: "Jl. Raya Darmasaba No. 1", @@ -180,7 +180,7 @@ async function seedResidents(banjarIds: string[]) { gender: Gender.PEREMPUAN, religion: Religion.HINDU, occupation: "Guru", - banjarId: banjarIds[1], + banjarId: banjarIds[1] || banjarIds[0] || "", rt: "002", rw: "000", address: "Gg. Manesa No. 5", @@ -203,7 +203,7 @@ async function seedActivities(divisionIds: string[]) { { title: "Rapat Koordinasi 2025", description: "Penyusunan rencana kerja tahunan", - divisionId: divisionIds[0], + divisionId: divisionIds[0] || "", progress: 100, status: ActivityStatus.SELESAI, priority: Priority.TINGGI, @@ -211,7 +211,7 @@ async function seedActivities(divisionIds: string[]) { { title: "Pemutakhiran Indeks Desa", description: "Pendataan SDG's Desa 2025", - divisionId: divisionIds[0], + divisionId: divisionIds[0] || "", progress: 65, status: ActivityStatus.BERJALAN, priority: Priority.SEDANG, @@ -219,7 +219,7 @@ async function seedActivities(divisionIds: string[]) { { title: "Pembangunan Jalan Banjar Cabe", description: "Pengaspalan jalan utama", - divisionId: divisionIds[1], + divisionId: divisionIds[1] || divisionIds[0] || "", progress: 40, status: ActivityStatus.BERJALAN, priority: Priority.DARURAT, @@ -296,7 +296,7 @@ async function seedEvents(adminId: string) { } } -async function main() { +export async function runSeed() { console.log("Starting seed..."); const adminId = await seedAdminUser(); @@ -315,11 +315,13 @@ async function main() { console.log("Seed finished successfully!"); } -main() - .catch((e) => { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - }); +if (import.meta.main) { + runSeed() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); +} diff --git a/src/api/complaint.ts b/src/api/complaint.ts index c272982..beea71b 100644 --- a/src/api/complaint.ts +++ b/src/api/complaint.ts @@ -1,4 +1,4 @@ -import Elysia from "elysia"; +import Elysia, { t } from "elysia"; import { prisma } from "../utils/db"; import logger from "../utils/logger"; @@ -23,6 +23,17 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Object({ + total: t.Number(), + baru: t.Number(), + proses: t.Number(), + selesai: t.Number(), + }), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get complaint statistics" }, }, ) @@ -42,6 +53,12 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get recent complaints" }, }, ) @@ -61,6 +78,12 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get service letter statistics by type" }, }, ) @@ -80,6 +103,12 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get recent innovation ideas" }, }, ) @@ -88,7 +117,9 @@ export const complaint = new Elysia({ async ({ set }) => { try { // Get last 6 months trends for service letters - const trends = await prisma.$queryRaw` + const trends = await prisma.$queryRaw< + { month: string; month_num: number; count: number }[] + >` SELECT TO_CHAR("createdAt", 'Mon') as month, EXTRACT(MONTH FROM "createdAt") as month_num, @@ -106,6 +137,12 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get service letter trends for last 6 months" }, }, ) @@ -132,6 +169,14 @@ export const complaint = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Object({ + count: t.Number(), + }), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get service letter count for current week" }, }, ); diff --git a/src/api/division.ts b/src/api/division.ts index ac9d8c9..aa74b9c 100644 --- a/src/api/division.ts +++ b/src/api/division.ts @@ -1,4 +1,4 @@ -import Elysia from "elysia"; +import Elysia, { t } from "elysia"; import { prisma } from "../utils/db"; import logger from "../utils/logger"; @@ -24,6 +24,12 @@ export const division = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get all divisions" }, }, ) @@ -48,6 +54,12 @@ export const division = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get recent activities" }, }, ) @@ -66,6 +78,12 @@ export const division = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get division performance metrics" }, }, ); diff --git a/src/api/event.ts b/src/api/event.ts index 54e57ae..914dd9e 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -1,4 +1,4 @@ -import Elysia from "elysia"; +import Elysia, { t } from "elysia"; import { prisma } from "../utils/db"; import logger from "../utils/logger"; @@ -21,6 +21,12 @@ export const event = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get upcoming events" }, }, ) @@ -49,6 +55,12 @@ export const event = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get events for today" }, }, ); diff --git a/src/api/index.tsx b/src/api/index.tsx index e44bd6d..0fa23c1 100644 --- a/src/api/index.tsx +++ b/src/api/index.tsx @@ -1,6 +1,6 @@ import { cors } from "@elysiajs/cors"; import { swagger } from "@elysiajs/swagger"; -import Elysia from "elysia"; +import Elysia, { t } from "elysia"; import { apiMiddleware } from "../middleware/apiMiddleware"; import { auth } from "../utils/auth"; import { apikey } from "./apikey"; @@ -16,12 +16,24 @@ const api = new Elysia({ prefix: "/api", }) .use(cors()) - .get("/health", () => ({ ok: true })) - .all("/auth/*", ({ request }) => auth.handler(request)) - .get("/session", async ({ request }) => { - const data = await auth.api.getSession({ headers: request.headers }); - return { data }; + .get("/health", () => ({ ok: true }), { + response: { + 200: t.Object({ ok: t.Boolean() }), + }, }) + .all("/auth/*", ({ request }) => auth.handler(request)) + .get( + "/session", + async ({ request }) => { + const data = await auth.api.getSession({ headers: request.headers }); + return { data }; + }, + { + response: { + 200: t.Object({ data: t.Any() }), + }, + }, + ) .use(apiMiddleware) .use(apikey) .use(profile) diff --git a/src/api/profile.ts b/src/api/profile.ts index 9f3dd33..44e0cfd 100644 --- a/src/api/profile.ts +++ b/src/api/profile.ts @@ -1,80 +1,69 @@ import Elysia, { t } from "elysia"; +import { apiMiddleware } from "../middleware/apiMiddleware"; import { prisma } from "../utils/db"; import logger from "../utils/logger"; -interface AuthenticatedUser { - id: string; - email: string; - name?: string | null; -} - export const profile = new Elysia({ prefix: "/profile", -}).post( - "/update", - async ({ - body, - set, - user, - }: { - body: { name?: string; image?: string }; - set: any; - user?: AuthenticatedUser; - }) => { - try { - if (!user) { - set.status = 401; - return { error: "Unauthorized" }; +}) + .use(apiMiddleware) + .post( + "/update", + async ({ body, set, user }) => { + try { + if (!user) { + set.status = 401; + return { error: "Unauthorized" }; + } + + const { name, image } = body; + + const updatedUser = await prisma.user.update({ + where: { id: user.id }, + data: { + name: name || undefined, + image: image || undefined, + }, + select: { + id: true, + name: true, + email: true, + image: true, + role: true, + }, + }); + + logger.info({ userId: user.id }, "Profile updated successfully"); + + return { user: updatedUser }; + } catch (error) { + logger.error({ error, userId: user?.id }, "Failed to update profile"); + set.status = 500; + return { error: "Failed to update profile" }; } - - const { name, image } = body; - - const updatedUser = await prisma.user.update({ - where: { id: user.id }, - data: { - name: name || undefined, - image: image || undefined, - }, - select: { - id: true, - name: true, - email: true, - image: true, - role: true, - }, - }); - - logger.info({ userId: user.id }, "Profile updated successfully"); - - return { user: updatedUser }; - } catch (error) { - logger.error({ error, userId: user?.id }, "Failed to update profile"); - set.status = 500; - return { error: "Failed to update profile" }; - } - }, - { - body: t.Object({ - name: t.Optional(t.String()), - image: t.Optional(t.String()), - }), - response: { - 200: t.Object({ - user: t.Object({ - id: t.String(), - name: t.Any(), - email: t.String(), - image: t.Any(), - role: t.Any(), - }), + }, + { + body: t.Object({ + name: t.Optional(t.String()), + image: t.Optional(t.String()), }), - 401: t.Object({ error: t.String() }), - 500: t.Object({ error: t.String() }), - }, + response: { + 200: t.Object({ + user: t.Object({ + id: t.String(), + name: t.Any(), + email: t.String(), + image: t.Any(), + role: t.Any(), + }), + }), + 401: t.Object({ error: t.String() }), + 500: t.Object({ error: t.String() }), + }, - detail: { - summary: "Update user profile", - description: "Update the authenticated user's name or profile image", + detail: { + summary: "Update user profile", + description: "Update the authenticated user's name or profile image", + }, }, - }, -); + ); diff --git a/src/api/resident.ts b/src/api/resident.ts index 91e59c2..7655a04 100644 --- a/src/api/resident.ts +++ b/src/api/resident.ts @@ -1,4 +1,4 @@ -import Elysia from "elysia"; +import Elysia, { t } from "elysia"; import { prisma } from "../utils/db"; import logger from "../utils/logger"; @@ -22,6 +22,16 @@ export const resident = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Object({ + total: t.Number(), + heads: t.Number(), + poor: t.Number(), + }), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get resident statistics" }, }, ) @@ -46,6 +56,12 @@ export const resident = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Array(t.Any()), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get population data per banjar" }, }, ) @@ -69,7 +85,7 @@ export const resident = new Elysia({ take: 10, }), // Group by age ranges (simplified calculation) - prisma.$queryRaw` + prisma.$queryRaw<{ range: string; count: number }[]>` SELECT CASE WHEN date_part('year', age(now(), "birthDate")) BETWEEN 0 AND 16 THEN '0-16' @@ -94,6 +110,17 @@ export const resident = new Elysia({ } }, { + response: { + 200: t.Object({ + data: t.Object({ + religion: t.Array(t.Any()), + gender: t.Array(t.Any()), + occupation: t.Array(t.Any()), + ageGroups: t.Array(t.Any()), + }), + }), + 500: t.Object({ error: t.String() }), + }, detail: { summary: "Get demographics including religion, gender, occupation and age", diff --git a/src/components/dashboard-content.tsx b/src/components/dashboard-content.tsx index 5299138..64665ae 100644 --- a/src/components/dashboard-content.tsx +++ b/src/components/dashboard-content.tsx @@ -53,18 +53,22 @@ export function DashboardContent() { ); setStats({ - complaints: (complaintRes.data as any)?.data || { + complaints: (complaintRes.data as { data: typeof stats.complaints }) + ?.data || { total: 0, baru: 0, proses: 0, selesai: 0, }, - residents: (residentRes.data as any)?.data || { + residents: (residentRes.data as { data: typeof stats.residents }) + ?.data || { total: 0, heads: 0, poor: 0, }, - weeklyService: (weeklyServiceRes.data as any)?.data?.count || 0, + weeklyService: + (weeklyServiceRes.data as { data: { count: number } })?.data + ?.count || 0, loading: false, }); } catch (error) { diff --git a/src/components/dashboard/activity-list.tsx b/src/components/dashboard/activity-list.tsx index 19ae81c..0d5224e 100644 --- a/src/components/dashboard/activity-list.tsx +++ b/src/components/dashboard/activity-list.tsx @@ -31,10 +31,12 @@ export function ActivityList() { const res = await apiClient.GET("/api/event/"); if (res.data?.data) { setData( - (res.data.data as any[]).map((e) => ({ - date: dayjs(e.startDate).format("D MMMM YYYY"), - title: e.title, - })), + (res.data.data as { startDate: string; title: string }[]).map( + (e) => ({ + date: dayjs(e.startDate).format("D MMMM YYYY"), + title: e.title, + }), + ), ); } } catch (error) { diff --git a/src/components/dashboard/chart-apbdes.tsx b/src/components/dashboard/chart-apbdes.tsx index b2cf50b..929874f 100644 --- a/src/components/dashboard/chart-apbdes.tsx +++ b/src/components/dashboard/chart-apbdes.tsx @@ -55,7 +55,10 @@ export function ChartAPBDes() { [`${value}%`, ""]} + formatter={(value: number | string | undefined) => [ + `${value}%`, + "", + ]} contentStyle={{ backgroundColor: dark ? "#1E293B" : "white", borderColor: dark ? "#334155" : "#e5e7eb", diff --git a/src/components/dashboard/chart-surat.tsx b/src/components/dashboard/chart-surat.tsx index 0d69950..a16ece8 100644 --- a/src/components/dashboard/chart-surat.tsx +++ b/src/components/dashboard/chart-surat.tsx @@ -20,11 +20,16 @@ import { } from "recharts"; import { apiClient } from "@/utils/api-client"; +interface ChartData { + month: string; + value: number; +} + export function ChartSurat() { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; - const [data, setData] = useState([]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -33,7 +38,7 @@ export function ChartSurat() { const res = await apiClient.GET("/api/complaint/service-trends"); if (res.data?.data) { setData( - (res.data.data as any[]).map((d) => ({ + (res.data.data as { month: string; count: number }[]).map((d) => ({ month: d.month, value: Number(d.count), })), @@ -79,7 +84,10 @@ export function ChartSurat() { viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label="Tampilkan Detail" > + Tampilkan Detail ({ + (res.data.data as DivisionApiResponse[]).map((d) => ({ name: d.name, value: d._count?.activities || 0, })), diff --git a/src/components/demografi-pekerjaan.tsx b/src/components/demografi-pekerjaan.tsx index 3f63fec..a1dc941 100644 --- a/src/components/demografi-pekerjaan.tsx +++ b/src/components/demografi-pekerjaan.tsx @@ -1,12 +1,9 @@ import { - Badge, Box, Card, Grid, - GridCol, Group, Loader, - Progress, Stack, Text, ThemeIcon, @@ -45,6 +42,49 @@ const sektorUnggulanData = [ { sektor: "Jasa", value: 52 }, ]; +interface AgeData { + ageRange: string; + total: number; +} + +interface JobData { + job: string; + total: number; +} + +interface ReligionData { + name: string; + value: number; + color: string; +} + +interface BanjarData { + id: string; + name: string; + totalPopulation: number; + totalKK: number; + totalPoor: number; +} + +interface ReligionResponse { + religion: string; + _count: { + _all: number; + }; +} + +interface OccupationResponse { + occupation: string | null; + _count: { + _all: number; + }; +} + +interface AgeGroupResponse { + range: string; + count: number | string; +} + const DemografiPekerjaan = () => { const { colorScheme } = useMantineColorScheme(); const dark = colorScheme === "dark"; @@ -54,10 +94,10 @@ const DemografiPekerjaan = () => { heads: 0, poor: 0, }); - const [ageData, setAgeData] = useState([]); - const [jobData, setJobData] = useState([]); - const [religionData, setReligionData] = useState([]); - const [banjarData, setBanjarData] = useState([]); + const [ageData, setAgeData] = useState([]); + const [jobData, setJobData] = useState([]); + const [religionData, setReligionData] = useState([]); + const [banjarData, setBanjarData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -70,7 +110,8 @@ const DemografiPekerjaan = () => { ]); if (statsRes.data?.data) setStats(statsRes.data.data); - if (banjarRes.data?.data) setBanjarData(banjarRes.data.data); + if (banjarRes.data?.data) + setBanjarData(banjarRes.data.data as BanjarData[]); if (demoRes.data?.data) { const { religion, occupation, ageGroups } = demoRes.data.data; @@ -85,7 +126,7 @@ const DemografiPekerjaan = () => { }; setReligionData( - (religion as any[]).map((r) => ({ + (religion as ReligionResponse[]).map((r) => ({ name: r.religion, value: r._count._all, color: religionColors[r.religion] || "#94A3B8", @@ -93,14 +134,14 @@ const DemografiPekerjaan = () => { ); setJobData( - (occupation as any[]).map((o) => ({ + (occupation as OccupationResponse[]).map((o) => ({ job: o.occupation || "Lainnya", total: o._count._all, })), ); setAgeData( - (ageGroups as any[]).map((a) => ({ + (ageGroups as AgeGroupResponse[]).map((a) => ({ ageRange: a.range, total: Number(a.count), })), @@ -401,8 +442,8 @@ const DemografiPekerjaan = () => { - {dynamicStats.map((stat, index) => ( - + {dynamicStats.map((stat) => ( + { paddingAngle={2} dataKey="value" > - {religionData.map((entry, index) => ( - + {religionData.map((entry) => ( + ))} { {!loading && - religionData.map((item, index) => ( - + religionData.map((item) => ( + { - {banjarData.map((item, index) => ( + {banjarData.map((item) => ( { radius={[0, 8, 8, 0]} maxBarSize={40} > - {sektorUnggulanData.map((entry, index) => ( - + {sektorUnggulanData.map((entry) => ( + ))} diff --git a/src/components/dev-inspector.tsx b/src/components/dev-inspector.tsx index 3c3cdc9..49dadc5 100644 --- a/src/components/dev-inspector.tsx +++ b/src/components/dev-inspector.tsx @@ -129,7 +129,6 @@ export function DevInspector({ children }: { children: React.ReactNode }) { tt.style.left = `${rect.left + window.scrollX}px`; }, []); - // biome-ignore lint/correctness/useExhaustiveDependencies: updateOverlay is stable useEffect(() => { if (!active) return; diff --git a/src/components/figma/ImageWithFallback.tsx b/src/components/figma/ImageWithFallback.tsx index 617f7eb..d7e23d1 100644 --- a/src/components/figma/ImageWithFallback.tsx +++ b/src/components/figma/ImageWithFallback.tsx @@ -23,10 +23,10 @@ export function ImageWithFallback(
Error loading image + />{" "}
) : ( diff --git a/src/components/keuangan-anggaran.tsx b/src/components/keuangan-anggaran.tsx index 6afc78a..98f14c7 100644 --- a/src/components/keuangan-anggaran.tsx +++ b/src/components/keuangan-anggaran.tsx @@ -3,7 +3,6 @@ import { Box, Card, Grid, - GridCol, Group, Stack, Text, @@ -354,8 +353,8 @@ const KeuanganAnggaran = () => { Pendapatan - {apbdReport.income.map((item, index) => ( - + {apbdReport.income.map((item) => ( + {item.category} @@ -390,8 +389,8 @@ const KeuanganAnggaran = () => { Belanja - {apbdReport.expenses.map((item, index) => ( - + {apbdReport.expenses.map((item) => ( + {item.category} @@ -473,9 +472,9 @@ const KeuanganAnggaran = () => { - {assistanceFundData.map((fund, index) => ( + {assistanceFundData.map((fund) => ( { - const [activities, setActivities] = useState([]); - const [todayEvents, setTodayEvents] = useState([]); + const [activities, setActivities] = useState([]); + const [todayEvents, setTodayEvents] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -32,10 +46,10 @@ const KinerjaDivisi = () => { ]); if (activityRes.data?.data) { - setActivities(activityRes.data.data); + setActivities(activityRes.data.data as Activity[]); } if (eventRes.data?.data) { - setTodayEvents(eventRes.data.data); + setTodayEvents(eventRes.data.data as EventData[]); } } catch (error) { console.error("Failed to fetch kinerja divisi data", error); @@ -57,11 +71,8 @@ const KinerjaDivisi = () => { {/* SECTION 1 — PROGRAM KEGIATAN */} - {activities.slice(0, 4).map((kegiatan, index) => ( - + {activities.slice(0, 4).map((kegiatan) => ( + { {/* SECTION 5 — ARSIP DIGITAL PERANGKAT DESA */} - {archiveData.map((item, index) => ( - + {archiveData.map((item) => ( + ))} diff --git a/src/components/kinerja-divisi/discussion-panel.tsx b/src/components/kinerja-divisi/discussion-panel.tsx index 1006923..2876c82 100644 --- a/src/components/kinerja-divisi/discussion-panel.tsx +++ b/src/components/kinerja-divisi/discussion-panel.tsx @@ -1,11 +1,4 @@ -import { - Box, - Card, - Group, - Stack, - Text, - useMantineColorScheme, -} from "@mantine/core"; +import { Card, Group, Stack, Text, useMantineColorScheme } from "@mantine/core"; import { MessageCircle } from "lucide-react"; interface DiscussionItem { @@ -57,9 +50,9 @@ export function DiscussionPanel() { - {discussions.map((discussion, index) => ( + {discussions.map((discussion) => ( ({ - name: div.name, - count: div._count?.activities || 0, - }), - ); + const mapped = ( + data.data as { name: string; _count?: { activities: number } }[] + ).map((div) => ({ + name: div.name, + count: div._count?.activities || 0, + })); setDivisions(mapped); } } catch (error) { @@ -68,9 +68,9 @@ export function DivisionList() { ) : divisions.length > 0 ? ( - divisions.map((division, index) => ( + divisions.map((division) => ( - {documentData.map((entry, index) => ( - + {documentData.map((entry) => ( + ))} diff --git a/src/components/kinerja-divisi/event-card.tsx b/src/components/kinerja-divisi/event-card.tsx index 5dbba56..c908a51 100644 --- a/src/components/kinerja-divisi/event-card.tsx +++ b/src/components/kinerja-divisi/event-card.tsx @@ -43,8 +43,12 @@ export function EventCard({ agendas = [] }: EventCardProps) { {agendas.length > 0 ? ( - {agendas.map((agenda, index) => ( - + {agendas.map((agenda) => ( + {agenda.time} diff --git a/src/components/kinerja-divisi/progress-chart.tsx b/src/components/kinerja-divisi/progress-chart.tsx index cc1f51a..572fffb 100644 --- a/src/components/kinerja-divisi/progress-chart.tsx +++ b/src/components/kinerja-divisi/progress-chart.tsx @@ -47,8 +47,8 @@ export function ProgressChart() { paddingAngle={2} dataKey="value" > - {progressData.map((entry, index) => ( - + {progressData.map((entry) => ( + ))} - {progressData.map((item, index) => ( - + {progressData.map((item) => ( + { switch (status.toLowerCase()) { case "baru": @@ -81,8 +101,8 @@ const PengaduanLayananPublik = () => { proses: 0, selesai: 0, }); - const [recentComplaints, setRecentComplaints] = useState([]); - const [serviceStats, setServiceStats] = useState([]); + const [recentComplaints, setRecentComplaints] = useState([]); + const [serviceStats, setServiceStats] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -95,9 +115,12 @@ const PengaduanLayananPublik = () => { ]); if (statsRes.data?.data) setStats(statsRes.data.data); - if (recentRes.data?.data) setRecentComplaints(recentRes.data.data); + if (recentRes.data?.data) + setRecentComplaints(recentRes.data.data as Complaint[]); if (serviceRes.data?.data) { - const mappedService = serviceRes.data.data.map((item: any) => ({ + const mappedService = ( + serviceRes.data.data as ServiceApiResponse[] + ).map((item) => ({ jenis: item.letterType, jumlah: item._count?._all || 0, })); @@ -148,8 +171,8 @@ const PengaduanLayananPublik = () => { {/* TOP SECTION - 4 STAT CARDS */} - {summaryData.map((item, index) => ( - + {summaryData.map((item) => ( + { ) : recentComplaints.length > 0 ? ( - recentComplaints.map((item, index) => ( + recentComplaints.map((item) => ( { Ajuan Ide Inovatif - {ideInovatif.map((item, index) => ( + {ideInovatif.map((item) => ( { const { colorScheme } = useMantineColorScheme(); - const dark = colorScheme === "dark"; + const _dark = colorScheme === "dark"; return ( diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 6e6ef49..dfdd80f 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -1,13 +1,10 @@ import { - Badge, Box, Collapse, - Group, Image, Input, NavLink as MantineNavLink, Stack, - Text, useMantineColorScheme, } from "@mantine/core"; import { useLocation, useNavigate } from "@tanstack/react-router"; @@ -80,11 +77,11 @@ export function Sidebar({ className }: SidebarProps) { {/* Menu Items */} - {menuItems.map((item, index) => { + {menuItems.map((item) => { const isActive = location.pathname === item.path; return ( navigate({ to: item.path })} label={item.name} active={isActive} @@ -146,11 +143,11 @@ export function Sidebar({ className }: SidebarProps) { ml="lg" style={{ overflowY: "auto", maxHeight: "200px" }} > - {settingsItems.map((item, index) => { + {settingsItems.map((item) => { const isActive = location.pathname === item.path; return ( navigate({ to: item.path })} label={item.name} active={isActive} diff --git a/src/components/sosial/beasiswa.tsx b/src/components/sosial/beasiswa.tsx index e39269d..1bc4e46 100644 --- a/src/components/sosial/beasiswa.tsx +++ b/src/components/sosial/beasiswa.tsx @@ -4,7 +4,6 @@ import { Stack, Text, ThemeIcon, - Title, useMantineColorScheme, } from "@mantine/core"; import { IconAward } from "@tabler/icons-react"; diff --git a/src/components/sosial/health-stats.tsx b/src/components/sosial/health-stats.tsx index 3552576..70fc7c1 100644 --- a/src/components/sosial/health-stats.tsx +++ b/src/components/sosial/health-stats.tsx @@ -49,8 +49,8 @@ export const HealthStats = ({ data }: HealthStatsProps) => { Statistik Kesehatan - {displayData.map((item, index) => ( -
+ {displayData.map((item) => ( +
{item.label} diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx index 930347d..bfe9795 100644 --- a/src/components/ui/breadcrumb.tsx +++ b/src/components/ui/breadcrumb.tsx @@ -53,8 +53,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { return ( ( mantineVariant = "transparent"; mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now break; - case "default": default: mantineVariant = "filled"; mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx index db3ddee..c6b0cde 100644 --- a/src/components/ui/carousel.tsx +++ b/src/components/ui/carousel.tsx @@ -117,16 +117,16 @@ function Carousel({ canScrollNext, }} > -
{children} -
+ {" "} ); } @@ -156,6 +156,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { const { orientation } = useCarousel(); return ( + // biome-ignore lint/a11y/useSemanticElements: role='group' and aria-roledescription='slide' is standard for carousel slides.
{ return (