Merge pull request 'amalia/22-mei-26' (#52) from amalia/22-mei-26 into join

Reviewed-on: #52
This commit is contained in:
2026-05-22 17:41:21 +08:00
3 changed files with 80 additions and 185 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "sistem-desa-mandiri",
"version": "0.1.16",
"version": "0.1.17",
"private": true,
"scripts": {
"dev": "next dev --experimental-https",

View File

@@ -0,0 +1 @@
-- This is an empty migration.

View File

@@ -117,50 +117,32 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
)
.get("/daily-activity", async ({ query, set }) => {
try {
// const data = await prisma.userLog.findMany({
// where: {
// User: {
// Village: {
// isDummy: false
// }
// },
// createdAt: {
// gte: moment().subtract(7, 'days').toDate(),
// lte: moment().toDate(),
// }
// },
// select: {
// createdAt: true,
// }
// })
const VALID_RANGES = [7, 30, 90];
const range = VALID_RANGES.includes(Number(query.range)) ? Number(query.range) : 7;
const data = await prisma.$queryRaw`
SELECT
SELECT
DATE(ul."createdAt") AS tanggal,
COUNT(*) AS total
FROM "UserLog" ul
JOIN "User" u ON ul."idUser" = u."id"
JOIN "Village" v ON u."idVillage" = v."id"
WHERE v."isDummy" = false
AND ul."createdAt" >= NOW() - INTERVAL '7 days'
AND ul."createdAt" >= NOW() - (${range} * INTERVAL '1 day')
GROUP BY tanggal
ORDER BY tanggal;` as any[];
const result = [];
// ubah data ke map biar gampang lookup
const map = data.reduce((acc: any, item: any) => {
const key = moment(item.tanggal).format('YYYY-MM-DD');
acc[key] = Number(item.total);
return acc;
}, {});
// generate 7 hari terakhir
for (let i = 6; i >= 0; i--) {
for (let i = range - 1; i >= 0; i--) {
const date = moment().subtract(i, 'days');
const key = date.format('YYYY-MM-DD');
result.push({
date: date.format('DD MMM'),
logs: map[key] || 0
@@ -183,45 +165,39 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
}
},
{
query: t.Object({
range: t.Optional(t.String({ description: "Rentang hari: 7, 30, atau 90 (default: 7)" })),
}),
detail: {
summary: "Daily Activity",
description: "Menu Overview - Mendapatkan data grafik aktivitas harian semua desa.",
description: "Menu Overview - Mendapatkan data grafik aktivitas harian semua desa. Gunakan ?range=30 atau ?range=90 untuk rentang lebih panjang.",
tags: ["overview"],
},
}
)
.get("/comparison-activity", async ({ query, set }) => {
try {
const villages = await prisma.village.findMany({
where: { isDummy: false },
select: { name: true },
});
const VALID_RANGES = [7, 30, 90];
const range = VALID_RANGES.includes(Number(query.range)) ? Number(query.range) : 7;
const data = await prisma.$queryRaw`
SELECT
SELECT
v."name",
COUNT(ul."id") AS total_logs
FROM "UserLog" ul
JOIN "User" u ON ul."idUser" = u."id"
JOIN "Village" v ON u."idVillage" = v."id"
WHERE v."isDummy" = false
AND ul."createdAt" >= NOW() - INTERVAL '7 days'
AND ul."createdAt" >= NOW() - (${range} * INTERVAL '1 day')
GROUP BY v."id", v."name"
ORDER BY total_logs DESC;
` as any[];
const logMap: Record<string, number> = {};
data.forEach((item) => {
logMap[item.name] = Number(item.total_logs);
});
const result = villages.map((v) => ({
village: v.name,
activity: logMap[v.name] || 0,
const result = data.map((item: any) => ({
village: item.name,
activity: Number(item.total_logs),
}));
return {
success: true,
message: "Berhasil mendapatkan data",
@@ -238,9 +214,12 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
}
},
{
query: t.Object({
range: t.Optional(t.String({ description: "Rentang hari: 7, 30, atau 90 (default: 7)" })),
}),
detail: {
summary: "Comparison Activity",
description: "Menu Overview - Mendapatkan data grafik perbandingan aktivitas desa selama 7 hari terakhir.",
description: "Menu Overview - Mendapatkan data grafik perbandingan aktivitas desa. Gunakan ?range=30 atau ?range=90 untuk rentang lebih panjang.",
tags: ["overview"],
},
}
@@ -1061,37 +1040,31 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
}
)
.get("/log-all-villages", async ({ query, set }) => {
const { page = 1, search } = query;
const { page = 1, search, action, idVillage, dateFrom, dateTo } = query;
const pageNum = Number(page) || 1;
const take = 15;
const skip = (pageNum - 1) * take;
const whereClause = {
...(action && { action: action.toUpperCase() }),
...(idVillage && { User: { idVillage } }),
...(dateFrom || dateTo) && {
createdAt: {
...(dateFrom && { gte: new Date(dateFrom) }),
...(dateTo && { lte: new Date(new Date(dateTo).setHours(23, 59, 59, 999)) }),
},
},
...(search && {
OR: [
{ User: { name: { contains: search, mode: "insensitive" as const } } },
{ User: { Village: { name: { contains: search, mode: "insensitive" as const } } } },
],
}),
};
try {
const dataLog = await prisma.userLog.findMany({
where: {
...(search && {
OR: [
{
User: {
name: {
contains: search,
mode: "insensitive",
},
},
},
{
User: {
Village: {
name: {
contains: search,
mode: "insensitive",
},
},
},
},
],
}),
},
where: whereClause,
select: {
id: true,
createdAt: true,
@@ -1115,32 +1088,7 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
take,
});
const total = await prisma.userLog.count({
where: {
...(search && {
OR: [
{
User: {
name: {
contains: search,
mode: "insensitive",
},
},
},
{
User: {
Village: {
name: {
contains: search,
mode: "insensitive",
},
},
},
},
],
}),
},
});
const total = await prisma.userLog.count({ where: whereClause });
const result = dataLog.map((item) => ({
id: item.id,
@@ -1175,65 +1123,49 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
{
query: t.Object({
page: t.Optional(t.String({ description: "Halaman" })),
search: t.Optional(t.String({ description: "Pencarian" })),
search: t.Optional(t.String({ description: "Pencarian nama user atau desa" })),
action: t.Optional(t.String({ description: "Filter jenis aksi: LOGIN | LOGOUT | CREATE | UPDATE | DELETE" })),
idVillage: t.Optional(t.String({ description: "Filter berdasarkan ID desa" })),
dateFrom: t.Optional(t.String({ description: "Tanggal mulai (ISO 8601, e.g. 2026-05-01)" })),
dateTo: t.Optional(t.String({ description: "Tanggal akhir (ISO 8601, e.g. 2026-05-31)" })),
}),
detail: {
summary: "Log Villages",
description:
"Mendapatkan data log aktivitas desa berdasarkan halaman dan pencarian",
description: "Mendapatkan data log aktivitas desa dengan filter aksi, desa, rentang tanggal, pencarian, dan paginasi",
tags: ["log-activity"],
},
}
)
.get("/user", async ({ query, set }) => {
const { page = 1, search } = query;
const { page = 1, search, isActive, idUserRole, idVillage, orderBy = 'createdAt', orderDir = 'desc' } = query;
const pageNum = Number(page) || 1;
const take = 15;
const skip = (pageNum - 1) * take;
const SORTABLE_FIELDS = ['name', 'email', 'isActive', 'idUserRole', 'createdAt'] as const;
type SortableField = typeof SORTABLE_FIELDS[number];
const safeOrderBy: SortableField = SORTABLE_FIELDS.includes(orderBy as SortableField) ? (orderBy as SortableField) : 'createdAt';
const safeOrderDir = orderDir === 'asc' ? 'asc' : 'desc';
const whereClause = {
...(isActive !== undefined && { isActive: isActive === 'true' }),
...(idUserRole && { idUserRole }),
...(idVillage && { idVillage }),
...(search && {
OR: [
{ name: { contains: search, mode: "insensitive" as const } },
{ phone: { contains: search, mode: "insensitive" as const } },
{ email: { contains: search, mode: "insensitive" as const } },
{ nik: { contains: search, mode: "insensitive" as const } },
{ Village: { name: { contains: search, mode: "insensitive" as const } } },
{ idUserRole: search },
],
}),
};
try {
const data = await prisma.user.findMany({
where: {
...(search && {
OR: [
{
name: {
contains: search,
mode: "insensitive",
},
},
{
phone: {
contains: search,
mode: "insensitive",
},
},
{
email: {
contains: search,
mode: "insensitive",
},
},
{
nik: {
contains: search,
mode: "insensitive",
},
},
{
Village: {
name: {
contains: search,
mode: "insensitive",
},
},
},
{
idUserRole: search,
},
],
}),
},
where: whereClause,
select: {
id: true,
name: true,
@@ -1270,55 +1202,13 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
},
},
orderBy: {
createdAt: "desc",
[safeOrderBy]: safeOrderDir,
},
skip,
take,
});
const total = await prisma.user.count({
where: {
...(search && {
OR: [
{
name: {
contains: search,
mode: "insensitive",
},
},
{
phone: {
contains: search,
mode: "insensitive",
},
},
{
email: {
contains: search,
mode: "insensitive",
},
},
{
nik: {
contains: search,
mode: "insensitive",
},
},
{
Village: {
name: {
contains: search,
mode: "insensitive",
},
},
},
{
idUserRole: search,
},
],
}),
},
});
const total = await prisma.user.count({ where: whereClause });
const result = data.map((item) => ({
id: item.id,
@@ -1363,12 +1253,16 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
{
query: t.Object({
page: t.Optional(t.String({ description: "Halaman" })),
search: t.Optional(t.String({ description: "Pencarian" })),
search: t.Optional(t.String({ description: "Pencarian nama/NIK/email/telepon" })),
isActive: t.Optional(t.String({ description: "Filter status: 'true' atau 'false'" })),
idUserRole: t.Optional(t.String({ description: "Filter berdasarkan ID role" })),
idVillage: t.Optional(t.String({ description: "Filter berdasarkan ID desa" })),
orderBy: t.Optional(t.String({ description: "Kolom urutan: name | email | isActive | idUserRole | createdAt (default: createdAt)" })),
orderDir: t.Optional(t.String({ description: "Arah urutan: asc | desc (default: desc)" })),
}),
detail: {
summary: "User",
description:
"Mendapatkan data user berdasarkan halaman dan pencarian",
description: "Mendapatkan data user dengan filter status, role, desa, pencarian, dan pengurutan",
tags: ["user"],
},
}