import { FastifyInstance } from "fastify"; import prisma from "../../config/database"; import { requireAuth } from "../../middleware/auth"; import { success } from "../../utils/response"; import { localTimeStr } from "../../utils/date"; export default async function dashboardRoutes( fastify: FastifyInstance, ): Promise { fastify.get("/", { preHandler: requireAuth }, async (request, reply) => { const now = new Date(); const todayStart = new Date( now.getFullYear(), now.getMonth(), now.getDate(), ); const todayEnd = new Date( now.getFullYear(), now.getMonth(), now.getDate() + 1, ); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 1); const authData = request.authData!; const userId = authData.userId; const perms = authData.permissions; const has = (p: string) => perms.includes(p); const result: Record = {}; // My shift — always available for authenticated users with attendance.record if (has("attendance.record")) { const myShift = await prisma.attendance.findFirst({ where: { user_id: userId, arrival_time: { not: null }, departure_time: null, }, orderBy: { created_at: "desc" }, }); result.my_shift = { has_ongoing: myShift !== null }; } // Attendance admin — only for attendance.admin if (has("attendance.admin")) { const [todayAttendance, onLeaveToday, usersCount] = await Promise.all([ prisma.attendance.findMany({ where: { shift_date: { gte: todayStart, lt: todayEnd }, OR: [{ leave_type: null }, { leave_type: "work" }], }, include: { users: { select: { id: true, first_name: true, last_name: true } }, }, orderBy: { arrival_time: "asc" }, }), prisma.attendance.findMany({ where: { shift_date: { gte: todayStart, lt: todayEnd }, leave_type: { in: ["vacation", "sick", "holiday", "unpaid"] }, }, include: { users: { select: { id: true, first_name: true, last_name: true } }, }, }), prisma.users.count({ where: { is_active: true } }), ]); const userAttendanceMap = new Map(); for (const a of todayAttendance) { const existing = userAttendanceMap.get(a.users.id); if ( !existing || (a.arrival_time && existing.arrival_time && a.arrival_time > existing.arrival_time) ) { userAttendanceMap.set(a.users.id, a); } } let presentCount = 0; const attendanceUsers: Array<{ user_id: number; name: string; initials: string; status: string; arrived_at: string | null; leave_type?: string; }> = []; for (const a of userAttendanceMap.values()) { const user = a.users; const firstInitial = user.first_name?.charAt(0) ?? ""; const lastInitial = user.last_name?.charAt(0) ?? ""; let status = "out"; if (a.arrival_time) { if (a.departure_time) status = "out"; else if (a.break_start && !a.break_end) status = "away"; else { status = "in"; presentCount++; } } attendanceUsers.push({ user_id: user.id, name: `${user.first_name} ${user.last_name}`, initials: `${firstInitial}${lastInitial}`.toUpperCase(), status, arrived_at: a.arrival_time ? localTimeStr(a.arrival_time) : null, }); } const leaveUserIds = new Set(); for (const a of onLeaveToday) { if (leaveUserIds.has(a.users.id)) continue; leaveUserIds.add(a.users.id); const user = a.users; attendanceUsers.push({ user_id: user.id, name: `${user.first_name} ${user.last_name}`, initials: `${user.first_name?.charAt(0) ?? ""}${user.last_name?.charAt(0) ?? ""}`.toUpperCase(), status: "leave", arrived_at: null, leave_type: (a.leave_type as string) || "vacation", }); } result.attendance = { present_today: presentCount, total_active: usersCount, on_leave: leaveUserIds.size, users: attendanceUsers, }; result.users_count = usersCount; } // Offers — only for offers.view if (has("offers.view")) { const [openCount, convertedCount, expiredCount, createdThisMonth] = await Promise.all([ prisma.quotations.count({ where: { status: "active" } }), prisma.quotations.count({ where: { status: "converted" } }), prisma.quotations.count({ where: { status: "expired" } }), prisma.quotations.count({ where: { created_at: { gte: monthStart, lt: monthEnd } }, }), ]); result.offers = { open_count: openCount, converted_count: convertedCount, expired_count: expiredCount, created_this_month: createdThisMonth, }; } // Projects — only for projects.view if (has("projects.view")) { const [activeCount, activeList] = await Promise.all([ prisma.projects.count({ where: { status: "aktivni" } }), prisma.projects.findMany({ where: { status: "aktivni" }, include: { customers: { select: { name: true } } }, orderBy: { created_at: "desc" }, take: 5, }), ]); result.active_projects = activeCount; result.projects = { active_projects: activeList.map((p) => ({ id: p.id, name: p.name ?? "", customer_name: p.customers?.name ?? null, })), }; } // Invoices — only for invoices.view if (has("invoices.view")) { const [unpaidCount, issuedThisMonth] = await Promise.all([ prisma.invoices.count({ where: { status: "issued" } }), prisma.invoices.findMany({ where: { issue_date: { gte: monthStart, lt: monthEnd } }, include: { invoice_items: true }, }), ]); const revenueByCurrency: Record = {}; for (const inv of issuedThisMonth) { const currency = inv.currency ?? "CZK"; let total = 0; for (const item of inv.invoice_items) { total += (Number(item.quantity) || 0) * (Number(item.unit_price) || 0); } revenueByCurrency[currency] = (revenueByCurrency[currency] ?? 0) + total; } result.invoices = { revenue_this_month: Object.entries(revenueByCurrency).map( ([currency, amount]) => ({ amount: Math.round(amount * 100) / 100, currency, }), ), unpaid_count: unpaidCount, revenue_czk: revenueByCurrency["CZK"] != null ? Math.round(revenueByCurrency["CZK"] * 100) / 100 : null, }; result.unpaid_invoices = unpaidCount; } // Orders — only for orders.view if (has("orders.view")) { result.pending_orders = await prisma.orders.count({ where: { status: "prijata" }, }); } // Leave pending — only for attendance.approve if (has("attendance.approve")) { const count = await prisma.leave_requests.count({ where: { status: "pending" }, }); result.leave_pending = { count }; result.pending_leave_requests = count; } // Recent activity — only for settings.audit (admin) if (has("settings.audit")) { const logs = await prisma.audit_logs.findMany({ orderBy: { created_at: "desc" }, take: 8, where: { action: { in: ["create", "update", "delete", "login"] } }, select: { id: true, action: true, entity_type: true, description: true, username: true, created_at: true, }, }); result.recent_activity = logs.map((log) => ({ id: log.id, action: log.action, entity_type: log.entity_type ?? "", description: log.description ?? "", username: log.username ?? null, created_at: log.created_at ?? "", })); } return success(reply, result); }); }