Files
app/api/admin/dashboard.php
2026-03-12 12:43:56 +01:00

282 lines
8.9 KiB
PHP

<?php
/**
* Dashboard API - agregovaná data pro dashboard
*
* GET /api/admin/dashboard.php
* Vrací sekce dle oprávnění přihlášeného uživatele.
*/
declare(strict_types=1);
require_once dirname(__DIR__) . '/config.php';
require_once dirname(__DIR__) . '/includes/JWTAuth.php';
require_once dirname(__DIR__) . '/includes/AuditLog.php';
require_once dirname(__DIR__) . '/includes/CnbRates.php';
setCorsHeaders();
setSecurityHeaders();
setNoCacheHeaders();
header('Content-Type: application/json; charset=utf-8');
$authData = JWTAuth::requireAuth();
AuditLog::setUser($authData['user_id'], $authData['user']['username'] ?? 'unknown');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
errorResponse('Method not allowed', 405);
}
$pdo = db();
$result = [];
// --- Stav smeny aktualniho uzivatele (attendance.record) ---
$userId = $authData['user_id'];
if (hasPermission($authData, 'attendance.record')) {
$stmt = $pdo->prepare("
SELECT id FROM attendance
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
ORDER BY created_at DESC LIMIT 1
");
$stmt->execute([$userId]);
$result['my_shift'] = [
'has_ongoing' => (bool) $stmt->fetch(),
];
}
// --- Docházka dnes (attendance.admin) ---
if (hasPermission($authData, 'attendance.admin')) {
// Poslední pracovní záznam per uživatel (vyloučit ty co mají leave dnes)
$stmt = $pdo->query("
SELECT u.id, CONCAT(u.first_name, ' ', u.last_name) as name,
CONCAT(LEFT(u.first_name, 1), LEFT(u.last_name, 1)) as initials,
a.arrival_time, a.departure_time, a.break_start, a.break_end
FROM users u
LEFT JOIN (
SELECT a1.*
FROM attendance a1
INNER JOIN (
SELECT user_id, MAX(id) as max_id
FROM attendance
WHERE shift_date = CURDATE()
AND (leave_type IS NULL OR leave_type = 'work')
GROUP BY user_id
) a2 ON a1.id = a2.max_id
) a ON u.id = a.user_id
WHERE u.is_active = 1
AND u.id NOT IN (
SELECT user_id FROM attendance
WHERE shift_date = CURDATE() AND leave_type IN ('vacation', 'sick', 'holiday', 'unpaid')
)
ORDER BY a.arrival_time IS NULL, a.arrival_time ASC
");
$users = $stmt->fetchAll();
$present = 0;
$away = 0;
$attendanceUsers = [];
foreach ($users as $u) {
$status = 'out';
$arrivedAt = null;
if ($u['arrival_time'] !== null) {
if ($u['departure_time'] !== null) {
$status = 'out';
} elseif ($u['break_start'] !== null && $u['break_end'] === null) {
$status = 'away';
$away++;
} else {
$status = 'in';
$present++;
}
$arrivedAt = date('H:i', strtotime($u['arrival_time']));
}
$attendanceUsers[] = [
'name' => $u['name'],
'initials' => $u['initials'],
'status' => $status,
'arrived_at' => $arrivedAt,
];
}
// Dnes na dovolene/nemocenske
$stmtLeave = $pdo->query("
SELECT CONCAT(u.first_name, ' ', u.last_name) as name,
CONCAT(LEFT(u.first_name, 1), LEFT(u.last_name, 1)) as initials,
a.leave_type
FROM attendance a
JOIN users u ON a.user_id = u.id
WHERE a.shift_date = CURDATE() AND a.leave_type IN ('vacation', 'sick', 'holiday', 'unpaid')
");
$onLeave = $stmtLeave->fetchAll();
foreach ($onLeave as $leave) {
$attendanceUsers[] = [
'name' => $leave['name'],
'initials' => $leave['initials'],
'status' => 'leave',
'arrived_at' => null,
'leave_type' => $leave['leave_type'],
];
}
$result['attendance'] = [
'present_today' => $present,
'away_today' => $away,
'total_active' => count($users),
'on_leave' => count($onLeave),
'users' => $attendanceUsers,
];
}
// --- Nabídky (offers.view) ---
if (hasPermission($authData, 'offers.view')) {
$stmt = $pdo->query("
SELECT
COUNT(*) as total,
SUM(CASE WHEN q.order_id IS NULL
AND (q.valid_until IS NULL OR q.valid_until >= CURDATE())
THEN 1 ELSE 0 END) as open_count,
SUM(CASE WHEN q.order_id IS NULL
AND q.valid_until < CURDATE()
THEN 1 ELSE 0 END) as expired_count,
SUM(CASE WHEN q.order_id IS NOT NULL
THEN 1 ELSE 0 END) as converted_count
FROM quotations q
");
$counts = $stmt->fetch();
$stmtMonth = $pdo->query("
SELECT COUNT(*) as count FROM quotations
WHERE YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())
");
$monthData = $stmtMonth->fetch();
$result['offers'] = [
'total' => (int) $counts['total'],
'open_count' => (int) $counts['open_count'],
'expired_count' => (int) $counts['expired_count'],
'converted_count' => (int) $counts['converted_count'],
'created_this_month' => (int) $monthData['count'],
];
}
// --- Projekty (projects.view) ---
if (hasPermission($authData, 'projects.view')) {
$stmt = $pdo->query("
SELECT p.id, p.name, p.status, c.name as customer_name
FROM projects p
LEFT JOIN customers c ON p.customer_id = c.id
WHERE p.status = 'aktivni'
ORDER BY p.modified_at DESC
LIMIT 5
");
$activeProjects = $stmt->fetchAll();
$stmtCounts = $pdo->query("
SELECT
SUM(CASE WHEN status = 'aktivni' THEN 1 ELSE 0 END) as active_count,
SUM(CASE WHEN status = 'dokonceny' THEN 1 ELSE 0 END) as completed_count
FROM projects WHERE status != 'deleted'
");
$projectCounts = $stmtCounts->fetch();
$result['projects'] = [
'active_count' => (int) ($projectCounts['active_count'] ?? 0),
'completed_count' => (int) ($projectCounts['completed_count'] ?? 0),
'active_projects' => $activeProjects,
];
}
// --- Faktury (invoices.view) ---
if (hasPermission($authData, 'invoices.view')) {
$stmt = $pdo->query("
SELECT
COUNT(*) as total,
SUM(CASE WHEN i.status = 'paid'
AND YEAR(i.paid_date) = YEAR(CURDATE())
AND MONTH(i.paid_date) = MONTH(CURDATE())
THEN 1 ELSE 0 END) as paid_this_month,
SUM(CASE WHEN i.status IN ('issued', 'overdue')
THEN 1 ELSE 0 END) as unpaid_count
FROM invoices i
");
$invCounts = $stmt->fetch();
// Tržby tento měsíc per faktura (pro kurz k datu vystaveni)
$stmtRevenue = $pdo->query("
SELECT i.id, i.currency, i.issue_date,
COALESCE(SUM(ii.quantity * ii.unit_price), 0) as revenue
FROM invoices i
JOIN invoice_items ii ON i.id = ii.invoice_id
WHERE i.status = 'paid'
AND YEAR(i.paid_date) = YEAR(CURDATE())
AND MONTH(i.paid_date) = MONTH(CURDATE())
GROUP BY i.id, i.currency, i.issue_date
ORDER BY revenue DESC
");
$revByCurrency = [];
$revCzkItems = [];
foreach ($stmtRevenue->fetchAll() as $row) {
$cur = $row['currency'];
$amt = (float) $row['revenue'];
$revByCurrency[$cur] = ($revByCurrency[$cur] ?? 0) + $amt;
$revCzkItems[] = [
'amount' => $amt,
'currency' => $cur,
'date' => $row['issue_date'],
];
}
$revenueByCurrency = [];
foreach ($revByCurrency as $cur => $total) {
$revenueByCurrency[] = [
'currency' => $cur,
'amount' => round($total, 2),
];
}
$cnb = CnbRates::getInstance();
$result['invoices'] = [
'total' => (int) $invCounts['total'],
'paid_this_month' => (int) $invCounts['paid_this_month'],
'unpaid_count' => (int) $invCounts['unpaid_count'],
'revenue_this_month' => $revenueByCurrency,
'revenue_czk' => $cnb->sumToCzk($revCzkItems),
];
}
// --- Čekající žádosti (attendance.approve) ---
if (hasPermission($authData, 'attendance.approve')) {
$stmt = $pdo->query("
SELECT COUNT(*) as count FROM leave_requests WHERE status = 'pending'
");
$pending = $stmt->fetch();
$result['leave_pending'] = [
'count' => (int) $pending['count'],
];
}
// --- Poslední aktivita (settings.roles = admin přehled) ---
if (hasPermission($authData, 'settings.roles')) {
$stmt = $pdo->query("
SELECT username, action, entity_type, description, created_at
FROM audit_logs
WHERE action IN ('create', 'update', 'delete', 'login')
ORDER BY created_at DESC
LIMIT 8
");
$result['recent_activity'] = $stmt->fetchAll();
}
jsonResponse($result);