import { useState, useEffect, useCallback } from 'react' import { useAlert } from '../context/AlertContext' import { useAuth } from '../context/AuthContext' import { useNavigate } from 'react-router-dom' import { motion, AnimatePresence } from 'framer-motion' import ConfirmModal from '../components/ConfirmModal' import FormField from '../components/FormField' import useModalLock from '../hooks/useModalLock' import apiFetch from '../utils/api' const API_BASE = '/api/admin' const MODULE_LABELS = { attendance: 'Docházka', trips: 'Kniha jízd', offers: 'Nabídky', orders: 'Objednávky', projects: 'Projekty', invoices: 'Faktury', users: 'Uživatelé', settings: 'Nastavení' } export default function Settings() { const alert = useAlert() const { hasPermission } = useAuth() const navigate = useNavigate() const [loading, setLoading] = useState(true) const [roles, setRoles] = useState([]) const [, setAllPermissions] = useState([]) const [permissionGroups, setPermissionGroups] = useState({}) // 2FA requirement const [require2FA, setRequire2FA] = useState(false) const [require2FALoading, setRequire2FALoading] = useState(true) const [require2FASaving, setRequire2FASaving] = useState(false) const [showModal, setShowModal] = useState(false) const [editingRole, setEditingRole] = useState(null) const [saving, setSaving] = useState(false) const [form, setForm] = useState({ name: '', display_name: '', description: '', permissions: [] }) const [deleteConfirm, setDeleteConfirm] = useState({ show: false, role: null }) const [deleting, setDeleting] = useState(false) const canRoles = hasPermission('settings.roles') const canSecurity = hasPermission('settings.security') useEffect(() => { if (!canRoles && !canSecurity) { navigate('/') } }, [canRoles, canSecurity, navigate]) useModalLock(showModal) const fetchData = useCallback(async () => { if (!canRoles) { setLoading(false) return } try { const response = await apiFetch(`${API_BASE}/roles.php`) if (response.status === 401) return const result = await response.json() if (result.success) { setRoles(result.data.roles) setAllPermissions(result.data.permissions) setPermissionGroups(result.data.permission_groups) } else { alert.error(result.error || 'Nepodařilo se načíst role') } } catch { alert.error('Chyba připojení') } finally { setLoading(false) } }, [alert, canRoles]) useEffect(() => { fetchData() }, [fetchData]) const fetch2FARequired = useCallback(async () => { if (!canSecurity) { setRequire2FALoading(false) return } try { const response = await apiFetch(`${API_BASE}/totp.php?action=get_required`) const result = await response.json() if (result.success) { setRequire2FA(result.data.require_2fa) } } catch { // ignore } finally { setRequire2FALoading(false) } }, [canSecurity]) useEffect(() => { fetch2FARequired() }, [fetch2FARequired]) const handleToggle2FARequired = async () => { setRequire2FASaving(true) try { const response = await apiFetch(`${API_BASE}/totp.php?action=set_required`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ required: !require2FA }) }) const result = await response.json() if (result.success) { setRequire2FA(result.data.require_2fa) alert.success(result.message) } else { alert.error(result.error || 'Nepodařilo se uložit nastavení') } } catch { alert.error('Chyba připojení') } finally { setRequire2FASaving(false) } } const generateSlug = (text) => { return text .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') } const openCreateModal = () => { setEditingRole(null) setForm({ name: '', display_name: '', description: '', permissions: [] }) setShowModal(true) } const openEditModal = (role) => { setEditingRole(role) setForm({ name: role.name, display_name: role.display_name, description: role.description || '', permissions: role.permissions || [] }) setShowModal(true) } const closeModal = () => { setShowModal(false) setEditingRole(null) } const handleDisplayNameChange = (value) => { const updates = { display_name: value } if (!editingRole) { updates.name = generateSlug(value) } setForm(prev => ({ ...prev, ...updates })) } const togglePermission = (permName) => { setForm(prev => ({ ...prev, permissions: prev.permissions.includes(permName) ? prev.permissions.filter(p => p !== permName) : [...prev.permissions, permName] })) } const toggleModulePermissions = (moduleName) => { const modulePerms = (permissionGroups[moduleName] || []).map(p => p.name) const allChecked = modulePerms.every(p => form.permissions.includes(p)) setForm(prev => ({ ...prev, permissions: allChecked ? prev.permissions.filter(p => !modulePerms.includes(p)) : [...new Set([...prev.permissions, ...modulePerms])] })) } const handleSubmit = async (e) => { e?.preventDefault() if (!form.display_name.trim()) { alert.error('Zobrazovaný název je povinný') return } if (!editingRole && !form.name.trim()) { alert.error('Název role je povinný') return } setSaving(true) try { const url = editingRole ? `${API_BASE}/roles.php?id=${editingRole.id}` : `${API_BASE}/roles.php` const response = await apiFetch(url, { method: editingRole ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form) }) const result = await response.json() if (result.success) { closeModal() await new Promise(resolve => setTimeout(resolve, 300)) alert.success(result.message || (editingRole ? 'Role byla aktualizována' : 'Role byla vytvořena')) fetchData() } else { alert.error(result.error || 'Nepodařilo se uložit roli') } } catch { alert.error('Chyba připojení') } finally { setSaving(false) } } const handleDelete = async () => { if (!deleteConfirm.role) return setDeleting(true) try { const response = await apiFetch(`${API_BASE}/roles.php?id=${deleteConfirm.role.id}`, { method: 'DELETE' }) const result = await response.json() if (result.success) { setDeleteConfirm({ show: false, role: null }) alert.success(result.message || 'Role byla smazána') fetchData() } else { alert.error(result.error || 'Nepodařilo se smazat roli') } } catch { alert.error('Chyba připojení') } finally { setDeleting(false) } } if (loading) { return (
Zabezpečení a správa rolí
| Název | Popis | Oprávnění | Uživatelé | Akce |
|---|---|---|---|---|
|
{role.display_name}
{role.name}
|
{role.description || '—'} | {isAdminRole(role) ? 'Vše' : role.permission_count} | {role.user_count} |
{!isAdminRole(role) && (
|