123 lines
6.1 KiB
JavaScript
123 lines
6.1 KiB
JavaScript
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;
|