Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-28 21:36:45 +00:00
Commit ab1e5be9a9
146 geänderte Dateien mit 65525 neuen und 0 gelöschten Zeilen

310
backend/routes/stats.js Normale Datei
Datei anzeigen

@ -0,0 +1,310 @@
/**
* TASKMATE - Stats Routes
* =======================
* Dashboard-Statistiken
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const logger = require('../utils/logger');
/**
* GET /api/stats/dashboard
* Haupt-Dashboard Statistiken
*/
router.get('/dashboard', (req, res) => {
try {
const db = getDb();
const { projectId } = req.query;
let projectFilter = '';
const params = [];
if (projectId) {
projectFilter = ' AND t.project_id = ?';
params.push(projectId);
}
// Gesamtzahlen
const total = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
WHERE t.archived = 0 ${projectFilter}
`).get(...params).count;
// Offene Aufgaben (erste Spalte jedes Projekts)
const open = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0 AND c.position = 0 ${projectFilter}
`).get(...params).count;
// In Arbeit (mittlere Spalten)
const inProgress = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0 AND c.position > 0
AND c.position < (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
${projectFilter}
`).get(...params).count;
// Erledigt (letzte Spalte)
const completed = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0
AND c.position = (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
${projectFilter}
`).get(...params).count;
// Überfällig
const overdue = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0
AND t.due_date < date('now')
AND c.position < (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
${projectFilter}
`).get(...params).count;
// Heute fällig
const dueToday = db.prepare(`
SELECT t.id, t.title, t.priority, t.assigned_to,
u.display_name as assigned_name, u.color as assigned_color
FROM tasks t
LEFT JOIN users u ON t.assigned_to = u.id
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0
AND t.due_date = date('now')
AND c.position < (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
${projectFilter}
ORDER BY t.priority DESC
LIMIT 10
`).all(...params);
// Bald fällig (nächste 7 Tage)
const dueSoon = db.prepare(`
SELECT COUNT(*) as count FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0
AND t.due_date BETWEEN date('now', '+1 day') AND date('now', '+7 days')
AND c.position < (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
${projectFilter}
`).get(...params).count;
res.json({
total,
open,
inProgress,
completed,
overdue,
dueSoon,
dueToday: dueToday.map(t => ({
id: t.id,
title: t.title,
priority: t.priority,
assignedTo: t.assigned_to,
assignedName: t.assigned_name,
assignedColor: t.assigned_color
}))
});
} catch (error) {
logger.error('Fehler bei Dashboard-Stats:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/stats/completed-per-week
* Erledigte Aufgaben pro Woche
*/
router.get('/completed-per-week', (req, res) => {
try {
const db = getDb();
const { projectId, weeks = 8 } = req.query;
let projectFilter = '';
const params = [parseInt(weeks)];
if (projectId) {
projectFilter = ' AND h.task_id IN (SELECT id FROM tasks WHERE project_id = ?)';
params.push(projectId);
}
// Erledigte Aufgaben pro Kalenderwoche
const stats = db.prepare(`
SELECT
strftime('%Y-%W', h.timestamp) as week,
COUNT(DISTINCT h.task_id) as count
FROM history h
WHERE h.action = 'moved'
AND h.new_value IN (
SELECT name FROM columns c
WHERE c.position = (SELECT MAX(position) FROM columns WHERE project_id = c.project_id)
)
AND h.timestamp >= date('now', '-' || ? || ' weeks')
${projectFilter}
GROUP BY week
ORDER BY week DESC
`).all(...params);
// Letzten X Wochen mit 0 auffüllen
const result = [];
const now = new Date();
for (let i = 0; i < parseInt(weeks); i++) {
const date = new Date(now);
date.setDate(date.getDate() - (i * 7));
const year = date.getFullYear();
const week = getWeekNumber(date);
const weekKey = `${year}-${week.toString().padStart(2, '0')}`;
const found = stats.find(s => s.week === weekKey);
result.unshift({
week: weekKey,
label: `KW${week}`,
count: found ? found.count : 0
});
}
res.json(result);
} catch (error) {
logger.error('Fehler bei Completed-per-Week Stats:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/stats/time-per-project
* Geschätzte Zeit pro Projekt
*/
router.get('/time-per-project', (req, res) => {
try {
const db = getDb();
const stats = db.prepare(`
SELECT
p.id,
p.name,
COALESCE(SUM(t.time_estimate_min), 0) as total_minutes,
COUNT(t.id) as task_count
FROM projects p
LEFT JOIN tasks t ON p.id = t.project_id AND t.archived = 0
WHERE p.archived = 0
GROUP BY p.id
ORDER BY total_minutes DESC
`).all();
res.json(stats.map(s => ({
id: s.id,
name: s.name,
totalMinutes: s.total_minutes,
totalHours: Math.round(s.total_minutes / 60 * 10) / 10,
taskCount: s.task_count
})));
} catch (error) {
logger.error('Fehler bei Time-per-Project Stats:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/stats/user-activity
* Aktivität pro Benutzer
*/
router.get('/user-activity', (req, res) => {
try {
const db = getDb();
const { days = 30 } = req.query;
const stats = db.prepare(`
SELECT
u.id,
u.display_name,
u.color,
COUNT(DISTINCT CASE WHEN h.action = 'created' THEN h.task_id END) as tasks_created,
COUNT(DISTINCT CASE WHEN h.action = 'moved' THEN h.task_id END) as tasks_moved,
COUNT(DISTINCT CASE WHEN h.action = 'commented' THEN h.id END) as comments,
COUNT(h.id) as total_actions
FROM users u
LEFT JOIN history h ON u.id = h.user_id AND h.timestamp >= date('now', '-' || ? || ' days')
GROUP BY u.id
ORDER BY total_actions DESC
`).all(parseInt(days));
res.json(stats.map(s => ({
id: s.id,
displayName: s.display_name,
color: s.color,
tasksCreated: s.tasks_created,
tasksMoved: s.tasks_moved,
comments: s.comments,
totalActions: s.total_actions
})));
} catch (error) {
logger.error('Fehler bei User-Activity Stats:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/stats/calendar
* Aufgaben nach Datum (für Kalender)
*/
router.get('/calendar', (req, res) => {
try {
const db = getDb();
const { projectId, month, year } = req.query;
const currentYear = year || new Date().getFullYear();
const currentMonth = month || (new Date().getMonth() + 1);
// Start und Ende des Monats
const startDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`;
const endDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-31`;
let query = `
SELECT
t.due_date,
COUNT(*) as count,
SUM(CASE WHEN t.due_date < date('now') AND c.position < (SELECT MAX(position) FROM columns WHERE project_id = c.project_id) THEN 1 ELSE 0 END) as overdue_count
FROM tasks t
JOIN columns c ON t.column_id = c.id
WHERE t.archived = 0
AND t.due_date BETWEEN ? AND ?
`;
const params = [startDate, endDate];
if (projectId) {
query += ' AND t.project_id = ?';
params.push(projectId);
}
query += ' GROUP BY t.due_date ORDER BY t.due_date';
const stats = db.prepare(query).all(...params);
res.json(stats.map(s => ({
date: s.due_date,
count: s.count,
overdueCount: s.overdue_count
})));
} catch (error) {
logger.error('Fehler bei Calendar Stats:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* Hilfsfunktion: Kalenderwoche berechnen
*/
function getWeekNumber(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
module.exports = router;