import { useState, useEffect, useCallback } from "react"; import { Link } from "react-router-dom"; import { motion } from "framer-motion"; import { useAuth } from "../context/AuthContext"; import { useAlert } from "../context/AlertContext"; import useModalLock from "../hooks/useModalLock"; import apiFetch from "../utils/api"; import { getCzechDate } from "../utils/dashboardHelpers"; import DashKpiCards from "../components/dashboard/DashKpiCards"; import DashQuickActions from "../components/dashboard/DashQuickActions"; import DashActivityFeed from "../components/dashboard/DashActivityFeed"; import DashAttendanceToday from "../components/dashboard/DashAttendanceToday"; import DashProfile from "../components/dashboard/DashProfile"; import DashSessions from "../components/dashboard/DashSessions"; const API_BASE = "/api/admin"; interface DashData { my_shift?: { has_ongoing: boolean }; attendance?: { present_today: number; total_active: number; on_leave: number; users: Array<{ user_id: number | string; name: string; initials?: string; status: string; leave_type?: string; arrived_at?: string; }>; }; offers?: { open_count: number; converted_count: number; expired_count: number; created_this_month: number; }; projects?: { active_projects: Array<{ id: number; name: string; customer_name: string | null; }>; }; invoices?: { revenue_this_month: Array<{ amount: number; currency: string }>; unpaid_count: number; revenue_czk: number | null; }; leave_pending?: { count: number }; recent_activity?: Array<{ id: number | string; action: string; entity_type: string; description: string; username?: string; created_at: string; }>; users_count?: number; active_projects?: number; pending_orders?: number; unpaid_invoices?: number; pending_leave_requests?: number; [key: string]: unknown; } export default function Dashboard() { const { user, updateUser, hasPermission } = useAuth(); const alert = useAlert(); const [dashData, setDashData] = useState(null); const [dashLoading, setDashLoading] = useState(true); const [punching, setPunching] = useState(false); // 2FA state - sdileny mezi profilem a bannerem const [totpEnabled, setTotpEnabled] = useState(false); const [totpLoading, setTotpLoading] = useState(true); const [show2FASetup, setShow2FASetup] = useState(false); const [show2FADisable, setShow2FADisable] = useState(false); const [totpSecret, setTotpSecret] = useState(null); const [totpQrUri, setTotpQrUri] = useState(null); const [totpCode, setTotpCode] = useState(""); const [totpSubmitting, setTotpSubmitting] = useState(false); const [backupCodes, setBackupCodes] = useState(null); const [disableCode, setDisableCode] = useState(""); useModalLock(show2FASetup); useModalLock(show2FADisable); const fetchDashboard = useCallback(async () => { try { const response = await apiFetch(`${API_BASE}/dashboard`); const data = await response.json(); if (data.success !== false) { setDashData(data.data || data); } } catch (err) { if (import.meta.env.DEV) { console.error("Dashboard fetch error:", err); } } finally { setDashLoading(false); } }, []); useEffect(() => { fetchDashboard(); }, [fetchDashboard]); // 2FA status fetch const fetch2FAStatus = useCallback(async () => { try { const response = await apiFetch(`${API_BASE}/totp/setup`); const data = await response.json(); if (data.success) { setTotpEnabled(!!user?.totpEnabled); } } catch { // 2FA status fetch failed silently setTotpEnabled(!!user?.totpEnabled); } finally { setTotpLoading(false); } }, [user?.totpEnabled]); useEffect(() => { fetch2FAStatus(); }, [fetch2FAStatus]); // Punch (prichod/odchod) primo z dashboardu const handleQuickPunch = () => { const action = dashData?.my_shift?.has_ongoing ? "departure" : "arrival"; setPunching(true); const submitPunch = async (gpsData: Record = {}) => { try { const response = await apiFetch(`${API_BASE}/attendance`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ punch_action: action, ...gpsData }), }); const result = await response.json(); if (result.success) { alert.success(result.data?.message || "Docházka zaznamenána"); fetchDashboard(); } else { alert.error(result.error || "Chyba při záznamu docházky"); } } catch { alert.error("Chyba pripojeni"); } finally { setPunching(false); } }; if (!navigator.geolocation) { submitPunch({}); return; } navigator.geolocation.getCurrentPosition( (pos) => { const { latitude, longitude, accuracy } = pos.coords; submitPunch({ latitude, longitude, accuracy, address: "" }); }, () => submitPunch({}), { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 }, ); }; // 2FA handlery const handleStart2FASetup = async () => { setTotpSubmitting(true); try { const response = await apiFetch(`${API_BASE}/totp/setup`); const data = await response.json(); if (data.success) { setTotpSecret(data.data.secret); setTotpQrUri(data.data.uri || data.data.qr_uri); setTotpCode(""); setBackupCodes(null); setShow2FASetup(true); } else { alert.error(data.error || "Nepodařilo se vygenerovat 2FA klíč"); } } catch { alert.error("Chyba připojení"); } finally { setTotpSubmitting(false); } }; const handleConfirm2FA = async () => { if (!totpCode.trim()) return; setTotpSubmitting(true); try { const response = await apiFetch(`${API_BASE}/totp/enable`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ secret: totpSecret, code: totpCode.trim() }), }); const data = await response.json(); if (data.success) { setTotpEnabled(true); setBackupCodes(data.data?.backup_codes || null); setTotpSecret(null); setTotpQrUri(null); updateUser({ totpEnabled: true }); alert.success("2FA bylo aktivováno"); } else { alert.error(data.error || "Neplatný kód"); setTotpCode(""); } } catch { alert.error("Chyba připojení"); } finally { setTotpSubmitting(false); } }; const handleDisable2FA = async () => { if (!disableCode.trim()) return; setTotpSubmitting(true); try { const response = await apiFetch(`${API_BASE}/totp/disable`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code: disableCode.trim() }), }); const data = await response.json(); if (data.success) { setTotpEnabled(false); setShow2FADisable(false); setDisableCode(""); updateUser({ totpEnabled: false }); alert.success("2FA bylo deaktivováno"); } else { alert.error(data.error || "Neplatný kód"); setDisableCode(""); } } catch { alert.error("Chyba připojení"); } finally { setTotpSubmitting(false); } }; return (
{/* Header */}

Vítejte zpět, {user?.fullName || user?.username}

{getCzechDate()}

{/* 2FA Required Banner */} {user?.require2FA && !user?.totpEnabled && (
Dvoufaktorové ověření je povinné
Administrátor vyžaduje aktivaci 2FA. Dokud ji neaktivujete, nemáte přístup k ostatním sekcím systému.
)} {/* Skeleton loading */} {dashLoading && (
{[0, 1, 2, 3].map((i) => (
))}
{[0, 1, 2, 3].map((i) => (
))}
)} {/* KPI cards — only show if user has any admin-level permissions */} {!dashLoading && (hasPermission("offers.view") || hasPermission("invoices.view") || hasPermission("projects.view") || hasPermission("orders.view")) && } {/* Quick actions */} {!dashLoading && ( )} {/* Main content grid */} {!dashLoading && ( {hasPermission("settings.audit") && ( )} {hasPermission("attendance.admin") && ( )} {/* Pravy sloupec: projekty + nabidky */}
{dashData?.projects && (

Aktivní projekty

Vše →
{dashData.projects.active_projects.length === 0 && (
Žádné aktivní projekty
)} {dashData.projects.active_projects.map( (p: { id: number; name: string; customer_name: string | null; }) => (
{p.name}
{p.customer_name && (
{p.customer_name}
)} ), )}
)} {dashData?.offers && (

Nabídky

Zobrazit →
Otevřené {dashData.offers.open_count}
Převedené na objednávku {dashData.offers.converted_count}
Zneplatněné {dashData.offers.expired_count}
)}
)} {/* Profile + Sessions */} {!dashLoading && (
)}
); }