From ac2a16e7b701f84101d147150e5a311d9de6c986 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Thu, 2 Apr 2026 13:32:32 +0800 Subject: [PATCH] refactor: create monitoring route handler Co-Authored-By: Claude Sonnet 4.6 --- src/server/routes/mileage/monitoring.ts | 119 ++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/server/routes/mileage/monitoring.ts diff --git a/src/server/routes/mileage/monitoring.ts b/src/server/routes/mileage/monitoring.ts new file mode 100644 index 0000000..0d994ca --- /dev/null +++ b/src/server/routes/mileage/monitoring.ts @@ -0,0 +1,119 @@ +import { Hono } from 'hono'; +import { getCache, queryDateMileage, buildDateFilters } from './cache.js'; +import type { CachedVehicle, MonitoringFilters, MonitoringResponse } from './types.js'; + +const app = new Hono(); + +const EMPTY_RESPONSE: MonitoringResponse = { + vehicles: [], + stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, + filters: { departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [], platePrefixes: [], targetNames: [] }, + total: 0, + page: 1, + totalPages: 1, + updatedAt: new Date().toISOString(), +}; + +function applyFilters(vehicles: CachedVehicle[], params: { + search: string; dept: string; customer: string; project: string; + entity: string; rentStatus: string; plate: string; platePrefix: string; + targetName: string; mileageMin: string; mileageMax: string; +}): CachedVehicle[] { + let result = vehicles; + + if (params.search) { + const q = params.search.toLowerCase(); + result = result.filter(v => + v.plate.toLowerCase().includes(q) || + (v.customer || '').toLowerCase().includes(q) || + (v.project || '').toLowerCase().includes(q) + ); + } + if (params.dept) result = result.filter(v => params.dept === '__EMPTY__' ? !v.department : v.department === params.dept); + if (params.customer) result = result.filter(v => params.customer === '__EMPTY__' ? !v.customer : v.customer === params.customer); + if (params.project) result = result.filter(v => v.project === params.project); + if (params.entity) result = result.filter(v => v.entity === params.entity); + if (params.rentStatus) result = result.filter(v => v.rentStatus === params.rentStatus); + if (params.plate) result = result.filter(v => v.plate === params.plate); + if (params.platePrefix) result = result.filter(v => v.plate.startsWith(params.platePrefix)); + if (params.targetName) { + const cache = getCache(); + const tPlates = cache?.targetPlatesMap.get(params.targetName); + result = tPlates ? result.filter(v => tPlates.has(v.plate)) : []; + } + if (params.mileageMin) result = result.filter(v => v.dailyKm >= Number(params.mileageMin)); + if (params.mileageMax) result = result.filter(v => v.dailyKm <= Number(params.mileageMax)); + + return result; +} + +app.get('/', async (c) => { + const sortBy = c.req.query('sortBy') || 'today'; + const sortOrder = c.req.query('sortOrder') || 'desc'; + const limit = Number(c.req.query('limit')) || 50; + const page = Number(c.req.query('page')) || 1; + const date = c.req.query('date') || ''; + + const filterParams = { + search: c.req.query('search') || '', + dept: c.req.query('dept') || '', + customer: c.req.query('customer') || '', + project: c.req.query('project') || '', + entity: c.req.query('entity') || '', + rentStatus: c.req.query('rentStatus') || '', + plate: c.req.query('plate') || '', + platePrefix: c.req.query('platePrefix') || '', + targetName: c.req.query('targetName') || '', + mileageMin: c.req.query('mileageMin') || '', + mileageMax: c.req.query('mileageMax') || '', + }; + + let allVehicles: CachedVehicle[]; + let filters: MonitoringFilters; + + if (date) { + try { + allVehicles = await queryDateMileage(date); + filters = buildDateFilters(allVehicles); + } catch (e: unknown) { + console.error('monitoring date query error:', e); + return c.json(EMPTY_RESPONSE, 500); + } + } else { + const cache = getCache(); + if (!cache) return c.json(EMPTY_RESPONSE); + allVehicles = cache.vehicles; + filters = cache.filters; + } + + const filtered = applyFilters(allVehicles, filterParams); + + const stats = { + totalToday: filtered.reduce((sum, v) => sum + v.dailyKm, 0), + totalAll: filtered.reduce((sum, v) => sum + (v.totalKm || 0), 0), + vehicleCount: filtered.length, + yesterdayTotal: filtered.reduce((sum, v) => sum + v.yesterdayKm, 0), + }; + + const sorted = [...filtered].sort((a, b) => { + const valA = sortBy === 'today' ? a.dailyKm : (a.totalKm || 0); + const valB = sortBy === 'today' ? b.dailyKm : (b.totalKm || 0); + return sortOrder === 'desc' ? valB - valA : valA - valB; + }); + + const offset = (page - 1) * limit; + const paged = sorted.slice(offset, offset + limit); + const total = filtered.length; + + return c.json({ + vehicles: paged, + stats, + filters, + total, + page, + totalPages: Math.ceil(total / limit), + updatedAt: date || getCache()?.updatedAt || new Date().toISOString(), + }); +}); + +export default app;