Files
mek-tech-consulting/routes/spa/index.js
T

123 lines
6.1 KiB
JavaScript
Raw Normal View History

const express = require('express');
const router = express.Router();
const { getDb, getSettings } = require('../../db');
const { getServicesStatus } = require('../../lib/services');
// ---- Auth is publiek (login), rest vereist sessie ----
router.use('/auth', require('./auth'));
// Sessie-guard voor alle andere /api/spa routes: 401 JSON (nooit redirect)
router.use((req, res, next) => {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Niet ingelogd' });
}
next();
});
router.use(require('./crm'));
router.use(require('./finance'));
for (const mod of ['./infra', './misc']) {
try { router.use(require(mod)); } catch (e) { console.warn(`SPA module ${mod} niet geladen:`, e.message); }
}
// ============ DASHBOARD (geaggregeerde data voor SPA-home) ============
router.get('/dashboard', async (req, res) => {
try {
const db = getDb();
const clients = db.prepare(`
SELECT c.*,
(SELECT COUNT(*) FROM engagements e WHERE e.client_id = c.id) as engagement_count,
(SELECT COUNT(*) FROM engagements e WHERE e.client_id = c.id AND e.status = 'in_progress') as active_engagements,
(SELECT MAX(a.created_at) FROM assessments a WHERE a.client_id = c.id) as last_assessment
FROM clients c ORDER BY c.updated_at DESC LIMIT 12
`).all();
const stats = {
totalClients: db.prepare('SELECT COUNT(*) as cnt FROM clients').get().cnt,
activeClients: db.prepare("SELECT COUNT(*) as cnt FROM clients WHERE status='active'").get().cnt,
leads: db.prepare("SELECT COUNT(*) as cnt FROM clients WHERE status='lead'").get().cnt,
activeEngagements: db.prepare("SELECT COUNT(*) as cnt FROM engagements WHERE status='in_progress'").get().cnt,
totalTasks: db.prepare('SELECT COUNT(*) as cnt FROM tasks').get().cnt,
completedTasks: db.prepare("SELECT COUNT(*) as cnt FROM tasks WHERE status='done'").get().cnt,
overdueTasks: db.prepare("SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('todo','in_progress') AND due_date < date('now')").get().cnt,
totalRacks: db.prepare('SELECT COUNT(*) as cnt FROM racks').get().cnt,
totalDiagrams: db.prepare('SELECT COUNT(*) as cnt FROM data_diagrams').get().cnt,
totalNetworkDevices: db.prepare('SELECT COUNT(*) as cnt FROM network_devices').get().cnt,
totalHours: db.prepare('SELECT COALESCE(SUM(hours),0) as h FROM time_entries').get().h,
billableHours: db.prepare('SELECT COALESCE(SUM(hours),0) as h FROM time_entries WHERE billable=1').get().h,
totalInvoiced: db.prepare("SELECT COALESCE(SUM(total),0) as t FROM invoices WHERE status != 'draft'").get().t,
totalPaid: db.prepare("SELECT COALESCE(SUM(total),0) as t FROM invoices WHERE status='paid'").get().t,
totalOutstanding: db.prepare("SELECT COALESCE(SUM(total),0) as t FROM invoices WHERE status IN ('sent','overdue')").get().t
};
try { stats.datacenters = db.prepare('SELECT COUNT(*) as cnt FROM datacenters').get().cnt; } catch (e) { stats.datacenters = 0; }
try { stats.marketCompanies = db.prepare('SELECT COUNT(*) as cnt FROM tech_companies').get().cnt; } catch (e) { stats.marketCompanies = 0; }
const recentEngagements = db.prepare(`
SELECT e.*, c.name as client_name FROM engagements e JOIN clients c ON c.id = e.client_id
ORDER BY e.updated_at DESC LIMIT 8
`).all();
const upcomingTasks = db.prepare(`
SELECT t.*, e.title as engagement_title, c.name as client_name
FROM tasks t JOIN engagements e ON e.id = t.engagement_id JOIN clients c ON c.id = e.client_id
WHERE t.status IN ('todo','in_progress')
ORDER BY t.due_date IS NULL, t.due_date, CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END
LIMIT 10
`).all();
const recentInvoices = db.prepare(`
SELECT i.*, c.name as client_name FROM invoices i JOIN clients c ON c.id = i.client_id
ORDER BY i.created_at DESC LIMIT 6
`).all();
const engagementByType = db.prepare('SELECT type, COUNT(*) as cnt FROM engagements GROUP BY type ORDER BY cnt DESC').all();
const engagementByStatus = db.prepare('SELECT status, COUNT(*) as cnt FROM engagements GROUP BY status ORDER BY cnt DESC').all();
const clientsByStatus = db.prepare('SELECT status, COUNT(*) as cnt FROM clients GROUP BY status').all();
const invoicesByStatus = db.prepare('SELECT status, COUNT(*) as cnt, COALESCE(SUM(total),0) as total FROM invoices GROUP BY status').all();
// Uren per maand (afgelopen 6 maanden) voor grafiek
const hoursByMonth = db.prepare(`
SELECT strftime('%Y-%m', date) as month, SUM(hours) as hours,
SUM(CASE WHEN billable=1 THEN hours ELSE 0 END) as billable
FROM time_entries
WHERE date >= date('now', '-6 months', 'start of month')
GROUP BY month ORDER BY month
`).all();
// Omzet per maand (afgelopen 6 maanden)
const revenueByMonth = db.prepare(`
SELECT strftime('%Y-%m', date) as month,
SUM(CASE WHEN status='paid' THEN total ELSE 0 END) as paid,
SUM(CASE WHEN status IN ('sent','overdue') THEN total ELSE 0 END) as outstanding
FROM invoices
WHERE date >= date('now', '-6 months', 'start of month')
GROUP BY month ORDER BY month
`).all();
const activeAdvice = db.prepare(`
SELECT a.*, c.name as client_name
FROM advice_log a JOIN clients c ON c.id = a.client_id
WHERE a.dismissed = 0
ORDER BY CASE a.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, a.created_at DESC
LIMIT 8
`).all();
const recentAudit = db.prepare('SELECT * FROM audit_log ORDER BY created_at DESC LIMIT 8').all();
let servicesStatus = {};
try { servicesStatus = await getServicesStatus(); } catch (e) { servicesStatus = {}; }
res.json({
stats, clients, recentEngagements, upcomingTasks, recentInvoices,
engagementByType, engagementByStatus, clientsByStatus, invoicesByStatus,
hoursByMonth, revenueByMonth, activeAdvice, recentAudit, servicesStatus,
settings: { brand_name: getSettings().brand_name, showcase_url: getSettings().showcase_url }
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
module.exports = router;