diff --git a/src/admin/AdminApp.jsx b/src/admin/AdminApp.jsx
index 39dd8ac..acfc312 100644
--- a/src/admin/AdminApp.jsx
+++ b/src/admin/AdminApp.jsx
@@ -1,5 +1,5 @@
import { lazy, Suspense } from 'react'
-import { Routes, Route, Navigate } from 'react-router-dom'
+import { Routes, Route } from 'react-router-dom'
import { AuthProvider } from './context/AuthContext'
import { AlertProvider } from './context/AlertContext'
import ErrorBoundary from './components/ErrorBoundary'
@@ -45,6 +45,7 @@ const Invoices = lazy(() => import('./pages/Invoices'))
const InvoiceCreate = lazy(() => import('./pages/InvoiceCreate'))
const InvoiceDetail = lazy(() => import('./pages/InvoiceDetail'))
const Settings = lazy(() => import('./pages/Settings'))
+const NotFound = lazy(() => import('./pages/NotFound'))
export default function AdminApp() {
return (
@@ -86,7 +87,7 @@ export default function AdminApp() {
} />
} />
- } />
+ } />
diff --git a/src/admin/admin.css b/src/admin/admin.css
index aad68d5..12b311a 100644
--- a/src/admin/admin.css
+++ b/src/admin/admin.css
@@ -2419,6 +2419,25 @@ img {
}
}
+/* Error stack (DEV only) */
+.admin-error-stack {
+ max-width: 600px;
+ max-height: 200px;
+ overflow: auto;
+ padding: 0.75rem 1rem;
+ margin: 0;
+ border-radius: var(--border-radius-sm);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ color: var(--danger-color);
+ font-family: var(--font-mono);
+ font-size: 11px;
+ line-height: 1.5;
+ text-align: left;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
/* Keyboard shortcut badge */
.admin-kbd {
display: inline-block;
diff --git a/src/admin/components/ConfirmModal.jsx b/src/admin/components/ConfirmModal.jsx
index a8b8c7d..ebed234 100644
--- a/src/admin/components/ConfirmModal.jsx
+++ b/src/admin/components/ConfirmModal.jsx
@@ -1,6 +1,7 @@
import { useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import useModalLock from '../hooks/useModalLock'
+import useFocusTrap from '../hooks/useFocusTrap'
export default function ConfirmModal({
isOpen,
@@ -14,6 +15,7 @@ export default function ConfirmModal({
loading = false
}) {
useModalLock(isOpen)
+ const trapRef = useFocusTrap(isOpen)
useEffect(() => {
if (!isOpen) return
@@ -70,6 +72,7 @@ export default function ConfirmModal({
>
- Něco se pokazilo při načítání stránky.
-
+
+
+
Něco se pokazilo při načítání stránky.
+ {import.meta.env.DEV && this.state.error && (
+
+ {this.state.error.message}
+ {this.state.error.stack && `\n${this.state.error.stack}`}
+
+ )}
+
+
+ Zpět na Dashboard
+
+
+
)
}
diff --git a/src/admin/hooks/useFocusTrap.js b/src/admin/hooks/useFocusTrap.js
new file mode 100644
index 0000000..9c4e282
--- /dev/null
+++ b/src/admin/hooks/useFocusTrap.js
@@ -0,0 +1,53 @@
+import { useEffect, useRef } from 'react'
+
+const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
+
+/**
+ * Focus trap pro modaly - drzi focus uvnitr prvku.
+ * Vraci ref ktery se pripoji na modal kontejner.
+ */
+export default function useFocusTrap(isOpen) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (!isOpen || !ref.current) { return }
+
+ const container = ref.current
+ const previouslyFocused = document.activeElement
+
+ // Focus prvni focusable prvek v modalu
+ const focusable = container.querySelectorAll(FOCUSABLE)
+ if (focusable.length > 0) {
+ focusable[0].focus()
+ }
+
+ const handleKeyDown = (e) => {
+ if (e.key !== 'Tab') { return }
+
+ const nodes = container.querySelectorAll(FOCUSABLE)
+ if (nodes.length === 0) { return }
+
+ const first = nodes[0]
+ const last = nodes[nodes.length - 1]
+
+ if (e.shiftKey && document.activeElement === first) {
+ e.preventDefault()
+ last.focus()
+ } else if (!e.shiftKey && document.activeElement === last) {
+ e.preventDefault()
+ first.focus()
+ }
+ }
+
+ document.addEventListener('keydown', handleKeyDown)
+
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown)
+ if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
+ previouslyFocused.focus()
+ }
+ }
+ }, [isOpen])
+
+ return ref
+}
diff --git a/src/admin/pages/NotFound.jsx b/src/admin/pages/NotFound.jsx
new file mode 100644
index 0000000..73a599e
--- /dev/null
+++ b/src/admin/pages/NotFound.jsx
@@ -0,0 +1,30 @@
+import { Link } from 'react-router-dom'
+import { motion } from 'framer-motion'
+
+export default function NotFound() {
+ return (
+
+
+
+ 404
+
+ Stránka nebyla nalezena.
+
+ Zpět na Dashboard
+
+
+ )
+}