feat: tambahkan village-report endpoint dengan perbandingan periode sebelumnya
- Endpoint /village-report kini menghitung activity_count periode saat ini dan prev_activity_count periode sebelumnya dalam satu query (doubleRange) - Tambahkan kalkulasi trend persentase perubahan antar periode - Sertakan data perbekel, active_users, inactive_users, lastActivity, dan daysSince - Tambahkan endpoint /export-logs dan /export-users untuk ekspor CSV
This commit is contained in:
@@ -1871,6 +1871,80 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
detail: { summary: "Export Users CSV", tags: ["user"] },
|
||||
})
|
||||
|
||||
.get("/village-report", async ({ query, set }) => {
|
||||
const VALID_RANGES = [7, 30, 90];
|
||||
const range = VALID_RANGES.includes(Number(query.range)) ? Number(query.range) : 7;
|
||||
const doubleRange = range * 2;
|
||||
|
||||
try {
|
||||
const data = await prisma.$queryRaw`
|
||||
SELECT
|
||||
v."id",
|
||||
v."name",
|
||||
v."isActive",
|
||||
COUNT(CASE WHEN ul."createdAt" >= NOW() - (${range} * INTERVAL '1 day') THEN 1 END)::int AS activity_count,
|
||||
COUNT(CASE WHEN ul."createdAt" < NOW() - (${range} * INTERVAL '1 day') THEN 1 END)::int AS prev_activity_count,
|
||||
MAX(ul."createdAt") AS last_activity,
|
||||
(
|
||||
SELECT u2."name" FROM "User" u2
|
||||
WHERE u2."idVillage" = v."id" AND u2."idUserRole" = 'supadmin'
|
||||
LIMIT 1
|
||||
) AS perbekel,
|
||||
(
|
||||
SELECT COUNT(*)::int FROM "User" u3
|
||||
WHERE u3."idVillage" = v."id" AND u3."isActive" = true AND u3."idUserRole" != 'developer'
|
||||
) AS active_users,
|
||||
(
|
||||
SELECT COUNT(*)::int FROM "User" u4
|
||||
WHERE u4."idVillage" = v."id" AND u4."isActive" = false AND u4."idUserRole" != 'developer'
|
||||
) AS inactive_users
|
||||
FROM "Village" v
|
||||
LEFT JOIN "User" u ON u."idVillage" = v."id" AND u."idUserRole" != 'developer'
|
||||
LEFT JOIN "UserLog" ul ON ul."idUser" = u."id"
|
||||
AND ul."createdAt" >= NOW() - (${doubleRange} * INTERVAL '1 day')
|
||||
WHERE v."isDummy" = false
|
||||
GROUP BY v."id", v."name", v."isActive"
|
||||
ORDER BY activity_count DESC, v."name" ASC
|
||||
` as any[];
|
||||
|
||||
const result = data.map((v: any) => {
|
||||
const curr = Number(v.activity_count);
|
||||
const prev = Number(v.prev_activity_count);
|
||||
const trend = prev > 0 ? Math.round(((curr - prev) / prev) * 100) : curr > 0 ? 100 : 0;
|
||||
return {
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
isActive: v.isActive,
|
||||
perbekel: v.perbekel ?? '-',
|
||||
activeUsers: Number(v.active_users),
|
||||
inactiveUsers: Number(v.inactive_users),
|
||||
activityCount: curr,
|
||||
prevActivityCount: prev,
|
||||
trend,
|
||||
lastActivity: v.last_activity ? moment(v.last_activity).format('DD MMM YYYY HH:mm') : null,
|
||||
daysSince: v.last_activity
|
||||
? Math.floor((Date.now() - new Date(v.last_activity).getTime()) / (1000 * 60 * 60 * 24))
|
||||
: null,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mendapatkan data",
|
||||
data: { villages: result, range, generatedAt: moment().format('DD MMM YYYY HH:mm') },
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[village-report] error:", error);
|
||||
set.status = 500;
|
||||
return { success: false, message: "Terjadi kesalahan pada server", data: null };
|
||||
}
|
||||
}, {
|
||||
query: t.Object({
|
||||
range: t.Optional(t.String({ description: "Rentang hari: 7, 30, atau 90 (default: 7)" })),
|
||||
}),
|
||||
detail: { summary: "Village Report", description: "Data semua desa untuk keperluan laporan PDF.", tags: ["villages"] },
|
||||
})
|
||||
|
||||
// ─── API KEY MANAGEMENT ──────────────────────────────────────────────────
|
||||
|
||||
.get("/api-keys", async ({ set }) => {
|
||||
|
||||
Reference in New Issue
Block a user