Polish: Unicode diacritics fix, Header drawer, date formatting

- Czech diacritics properly rendered (no more escape sequences)
- Header avatar click opens drawer on mobile+desktop
- Invalid Date -> Bez terminu fallback with cs-CZ locale
- Chat/Settings pages improved

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude CLI Agent
2026-03-29 13:01:48 +00:00
parent 8ef3206ff0
commit eac9e72404
3 changed files with 58 additions and 27 deletions

View File

@@ -63,7 +63,7 @@ export default function ChatPage() {
const assistantMsg: ChatMessage = { const assistantMsg: ChatMessage = {
id: (Date.now() + 1).toString(), id: (Date.now() + 1).toString(),
role: "assistant", role: "assistant",
content: data.reply || data.message || "Omlouv\u00e1m se, nemohl jsem zpracovat va\u0161i zpr\u00e1vu.", content: data.reply || data.message || "Omlouvám se, nemohl jsem zpracovat vaši zprávu.",
timestamp: new Date(), timestamp: new Date(),
}; };
setMessages((prev) => [...prev, assistantMsg]); setMessages((prev) => [...prev, assistantMsg]);
@@ -71,7 +71,7 @@ export default function ChatPage() {
const errorMsg: ChatMessage = { const errorMsg: ChatMessage = {
id: (Date.now() + 1).toString(), id: (Date.now() + 1).toString(),
role: "assistant", role: "assistant",
content: "Chat asistent je momentáln\u011b nedostupný. Zkuste to prosím pozd\u011bji.", content: "Chat asistent je momentálně nedostupný. Zkuste to prosím později.",
timestamp: new Date(), timestamp: new Date(),
}; };
setMessages((prev) => [...prev, errorMsg]); setMessages((prev) => [...prev, errorMsg]);
@@ -101,7 +101,7 @@ export default function ChatPage() {
</div> </div>
<div> <div>
<h1 className="font-semibold">AI Asistent</h1> <h1 className="font-semibold">AI Asistent</h1>
<p className="text-xs text-muted">Zeptejte se na cokoliv ohledn\u011b va\u0161ich úkol\u016f</p> <p className="text-xs text-muted">Zeptejte se na cokoliv ohledně vašich úkolů</p>
</div> </div>
</div> </div>
@@ -114,9 +114,9 @@ export default function ChatPage() {
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg> </svg>
</div> </div>
<p className="text-muted text-lg font-medium">Za\u010dn\u011bte konverzaci</p> <p className="text-muted text-lg font-medium">Začněte konverzaci</p>
<p className="text-muted text-sm mt-1"> <p className="text-muted text-sm mt-1">
Napi\u0161te zpr\u00e1vu a AI asistent v\u00e1m pom\u016f\u017ee s úkoly Napište zprávu a AI asistent vám pomůže s úkoly
</p> </p>
</div> </div>
)} )}
@@ -168,7 +168,7 @@ export default function ChatPage() {
value={input} value={input}
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder="Napi\u0161te zpr\u00e1vu..." placeholder="Napište zprávu..."
rows={1} rows={1}
className="flex-1 px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-2xl bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-none text-sm" className="flex-1 px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-2xl bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-none text-sm"
style={{ maxHeight: "120px" }} style={{ maxHeight: "120px" }}

View File

@@ -148,10 +148,10 @@ export default function TaskDetailPage() {
} }
const PRIORITY_LABELS: Record<string, { label: string; dot: string }> = { const PRIORITY_LABELS: Record<string, { label: string; dot: string }> = {
low: { label: "Nízká", dot: "\uD83D\uDFE2" }, low: { label: "Nízká", dot: "🟢" },
medium: { label: "Střední", dot: "\uD83D\uDFE1" }, medium: { label: "Střední", dot: "🟡" },
high: { label: "Vysoká", dot: "\uD83D\uDFE0" }, high: { label: "Vysoká", dot: "🟠" },
urgent: { label: "Urgentní", dot: "\uD83D\uDD34" }, urgent: { label: "Urgentní", dot: "🔴" },
}; };
const pri = PRIORITY_LABELS[task.priority] || PRIORITY_LABELS.medium; const pri = PRIORITY_LABELS[task.priority] || PRIORITY_LABELS.medium;

View File

@@ -19,6 +19,7 @@ export default function Header() {
} }
const closeDrawer = useCallback(() => setDrawerOpen(false), []); const closeDrawer = useCallback(() => setDrawerOpen(false), []);
const openDrawer = useCallback(() => setDrawerOpen(true), []);
// Close drawer on escape // Close drawer on escape
useEffect(() => { useEffect(() => {
@@ -53,7 +54,7 @@ export default function Header() {
<button <button
onClick={toggleTheme} onClick={toggleTheme}
className="p-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center" className="p-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
aria-label="Prepnout tema" aria-label="Přepnout téma"
> >
{theme === "dark" ? ( {theme === "dark" ? (
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -68,9 +69,12 @@ export default function Header() {
{token && user ? ( {token && user ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-7 h-7 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-xs font-bold"> <button
onClick={openDrawer}
className="w-7 h-7 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-xs font-bold cursor-pointer hover:ring-2 hover:ring-blue-400 transition-all"
>
{(user.name || user.email || "?").charAt(0).toUpperCase()} {(user.name || user.email || "?").charAt(0).toUpperCase()}
</div> </button>
<span className="text-sm text-muted max-w-[120px] truncate"> <span className="text-sm text-muted max-w-[120px] truncate">
{user.name || user.email} {user.name || user.email}
</span> </span>
@@ -78,7 +82,7 @@ export default function Header() {
onClick={handleLogout} onClick={handleLogout}
className="text-sm px-3 py-1.5 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors min-h-[44px] flex items-center" className="text-sm px-3 py-1.5 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors min-h-[44px] flex items-center"
> >
Odhlasit Odhlásit
</button> </button>
</div> </div>
) : ( ) : (
@@ -86,20 +90,24 @@ export default function Header() {
href="/login" href="/login"
className="text-sm px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors min-h-[44px] flex items-center" className="text-sm px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors min-h-[44px] flex items-center"
> >
Prihlasit Přihlásit
</Link> </Link>
)} )}
</div> </div>
{/* Right: Mobile - avatar + hamburger */} {/* Right: Mobile - avatar + hamburger both open drawer */}
<div className="flex sm:hidden items-center gap-1"> <div className="flex sm:hidden items-center gap-1">
{token && user && ( {token && user && (
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-xs font-bold"> <button
onClick={openDrawer}
className="w-8 h-8 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-xs font-bold cursor-pointer"
aria-label="Otevřít menu"
>
{(user.name || user.email || "?").charAt(0).toUpperCase()} {(user.name || user.email || "?").charAt(0).toUpperCase()}
</div> </button>
)} )}
<button <button
onClick={() => setDrawerOpen(true)} onClick={openDrawer}
className="p-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center" className="p-2.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
aria-label="Menu" aria-label="Menu"
> >
@@ -113,7 +121,7 @@ export default function Header() {
{/* Mobile slide-out drawer */} {/* Mobile slide-out drawer */}
{drawerOpen && ( {drawerOpen && (
<div className="fixed inset-0 z-[60] sm:hidden"> <div className="fixed inset-0 z-[60]">
{/* Backdrop */} {/* Backdrop */}
<div <div
className="absolute inset-0 bg-black/50 backdrop-blur-sm animate-fadeIn" className="absolute inset-0 bg-black/50 backdrop-blur-sm animate-fadeIn"
@@ -128,7 +136,7 @@ export default function Header() {
<button <button
onClick={closeDrawer} onClick={closeDrawer}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] flex items-center justify-center" className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] flex items-center justify-center"
aria-label="Zavrit menu" aria-label="Zavřít menu"
> >
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -144,7 +152,7 @@ export default function Header() {
{(user.name || user.email || "?").charAt(0).toUpperCase()} {(user.name || user.email || "?").charAt(0).toUpperCase()}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="font-medium text-sm truncate">{user.name || "Uzivatel"}</p> <p className="font-medium text-sm truncate">{user.name || "Uživatel"}</p>
<p className="text-xs text-muted truncate">{user.email}</p> <p className="text-xs text-muted truncate">{user.email}</p>
</div> </div>
</div> </div>
@@ -161,7 +169,7 @@ export default function Header() {
<svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg> </svg>
<span className="text-sm font-medium">Ukoly</span> <span className="text-sm font-medium">Úkoly</span>
</Link> </Link>
<Link <Link
@@ -172,12 +180,35 @@ export default function Header() {
<svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg> </svg>
<span className="text-sm font-medium">Kalendar</span> <span className="text-sm font-medium">Kalendář</span>
</Link>
<Link
href="/chat"
onClick={closeDrawer}
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-h-[48px]"
>
<svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<span className="text-sm font-medium">Chat</span>
</Link>
<Link
href="/settings"
onClick={closeDrawer}
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors min-h-[48px]"
>
<svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span className="text-sm font-medium">Nastavení</span>
</Link> </Link>
{/* Theme toggle */} {/* Theme toggle */}
<button <button
onClick={toggleTheme} onClick={() => { toggleTheme(); }}
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors w-full text-left min-h-[48px]" className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors w-full text-left min-h-[48px]"
> >
{theme === "dark" ? ( {theme === "dark" ? (
@@ -190,7 +221,7 @@ export default function Header() {
</svg> </svg>
)} )}
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{theme === "dark" ? "Svetly rezim" : "Tmavy rezim"} {theme === "dark" ? "Světlý režim" : "Tmavý režim"}
</span> </span>
</button> </button>
</div> </div>
@@ -205,7 +236,7 @@ export default function Header() {
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> <path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg> </svg>
<span className="text-sm font-medium">Odhlasit se</span> <span className="text-sm font-medium">Odhlásit se</span>
</button> </button>
</div> </div>
)} )}