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" />
)}
) }