import { useState, useEffect, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useAuth } from "../context/AuthContext"; import { useAlert } from "../context/AlertContext"; import ConfirmModal from "../components/ConfirmModal"; import FormField from "../components/FormField"; import Forbidden from "../components/Forbidden"; import useModalLock from "../hooks/useModalLock"; import apiFetch from "../utils/api"; const API_BASE = "/api/admin"; interface User { id: number; username: string; email: string; first_name: string; last_name: string; role_id: number; roles?: { id: number; name: string; display_name: string } | null; is_active: boolean; } interface Role { id: number; name: string; display_name: string; } interface FormData { username: string; email: string; password: string; first_name: string; last_name: string; role_id: number | string; is_active: boolean; } export default function Users() { const { user: currentUser, updateUser, hasPermission } = useAuth(); const alert = useAlert(); const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [editingUser, setEditingUser] = useState(null); const [deleteModal, setDeleteModal] = useState<{ isOpen: boolean; user: User | null; }>({ isOpen: false, user: null }); const [deleting, setDeleting] = useState(false); const [formData, setFormData] = useState({ username: "", email: "", password: "", first_name: "", last_name: "", role_id: "", is_active: true, }); useModalLock(showModal); const fetchUsers = useCallback(async () => { try { const usersRes = await apiFetch(`${API_BASE}/users`); const usersData = await usersRes.json(); if (usersData.success) { setUsers(Array.isArray(usersData.data) ? usersData.data : []); } else { alert.error(usersData.error || "Nepodařilo se načíst uživatele"); } // Roles fetch — gracefully handle 403 if user lacks settings.roles permission try { const rolesRes = await apiFetch(`${API_BASE}/roles`); const rolesData = await rolesRes.json(); if (rolesData.success) { setRoles(Array.isArray(rolesData.data) ? rolesData.data : []); } } catch { /* roles not accessible */ } } catch { alert.error("Chyba připojení"); } finally { setLoading(false); } }, []); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { fetchUsers(); }, [fetchUsers]); if (!hasPermission("users.view")) return ; const openCreateModal = () => { setEditingUser(null); setFormData({ username: "", email: "", password: "", first_name: "", last_name: "", role_id: roles[0]?.id || "", is_active: true, }); setShowModal(true); }; const openEditModal = (user: User) => { setEditingUser(user); setFormData({ username: user.username, email: user.email, password: "", first_name: user.first_name, last_name: user.last_name, role_id: user.role_id, is_active: user.is_active, }); setShowModal(true); }; const closeModal = () => { setShowModal(false); setEditingUser(null); }; const handleSubmit = async (e?: React.FormEvent) => { e?.preventDefault(); const dataToSave = { ...formData }; const wasEditing = editingUser; const editingId = editingUser?.id; try { const url = wasEditing ? `${API_BASE}/users/${editingId}` : `${API_BASE}/users`; const method = wasEditing ? "PUT" : "POST"; const response = await apiFetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(dataToSave), }); const data = await response.json(); if (data.success) { if ( wasEditing && currentUser && Number(editingId) === Number(currentUser.id) ) { updateUser({ username: dataToSave.username, email: dataToSave.email, fullName: `${dataToSave.first_name} ${dataToSave.last_name}`.trim(), }); } closeModal(); await new Promise((resolve) => setTimeout(resolve, 300)); alert.success( wasEditing ? "Uživatel byl upraven" : "Uživatel byl vytvořen", ); fetchUsers(); } else { alert.error(data.error || "Nepodařilo se uložit uživatele"); } } catch { alert.error("Chyba připojení"); } }; const openDeleteModal = (user: User) => { setDeleteModal({ isOpen: true, user }); }; const closeDeleteModal = () => { setDeleteModal({ isOpen: false, user: null }); }; const handleDelete = async () => { if (!deleteModal.user) return; setDeleting(true); try { const response = await apiFetch( `${API_BASE}/users/${deleteModal.user.id}`, { method: "DELETE", }, ); const data = await response.json(); if (data.success) { closeDeleteModal(); fetchUsers(); alert.success("Uživatel byl smazán"); } else { alert.error(data.error || "Nepodařilo se smazat uživatele"); } } catch { alert.error("Chyba připojení"); } finally { setDeleting(false); } }; const toggleActive = async (user: User) => { try { const response = await apiFetch(`${API_BASE}/users/${user.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ is_active: !user.is_active, }), }); const data = await response.json(); if (data.success) { fetchUsers(); alert.success( user.is_active ? "Uživatel byl deaktivován" : "Uživatel byl aktivován", ); } else { alert.error(data.error || "Nepodařilo se změnit stav uživatele"); } } catch { alert.error("Chyba připojení"); } }; const getRoleBadgeClass = (roleName: string): string => { switch (roleName) { case "admin": return "admin-badge admin-badge-admin"; default: return "admin-badge admin-badge-viewer"; } }; if (loading) { return (
{[0, 1, 2, 3, 4].map((i) => (
))}
); } return (

Uživatelé

Správa uživatelských účtů a oprávnění

{users.map((user) => ( ))}
Uživatel E-mail Role Stav Akce
{(user.first_name || user.username) .charAt(0) .toUpperCase()}
{user.first_name} {user.last_name}
@{user.username}
{user.email} {user.roles?.display_name || user.roles?.name || "—"}
{user.id !== currentUser?.id && ( )}
{showModal && (

{editingUser ? "Upravit uživatele" : "Přidat nového uživatele"}

setFormData({ ...formData, first_name: e.target.value, }) } required className="admin-form-input" /> setFormData({ ...formData, last_name: e.target.value, }) } required className="admin-form-input" />
setFormData({ ...formData, username: e.target.value }) } required className="admin-form-input" /> setFormData({ ...formData, email: e.target.value }) } required className="admin-form-input" /> setFormData({ ...formData, password: e.target.value }) } required={!editingUser} className="admin-form-input" />
)}
); }