Phase 3-4: Goals AI planner, Team tasks, Push notifications, i18n (CZ/HE/RU/UA)

- Goals CRUD API + AI study plan generator + progress reports
- Goals frontend page with progress bars
- Team tasks: assign, transfer, collaborate endpoints
- Push notifications: web-push, VAPID, subscribe/send
- i18n: 4 languages (cs, he, ru, ua) translation files
- notifications.js + goals.js routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude CLI Agent
2026-03-29 13:12:19 +00:00
parent eac9e72404
commit fea4d38ce8
19 changed files with 1176 additions and 112 deletions

View File

@@ -3,12 +3,14 @@
import { useState, useEffect, useCallback } from "react";
import { useAuth } from "@/lib/auth";
import { useTheme } from "./ThemeProvider";
import { useTranslation } from "@/lib/i18n";
import Link from "next/link";
import { useRouter } from "next/navigation";
export default function Header() {
const { user, logout, token } = useAuth();
const { theme, toggleTheme } = useTheme();
const { t } = useTranslation();
const router = useRouter();
const [drawerOpen, setDrawerOpen] = useState(false);
@@ -54,7 +56,7 @@ export default function Header() {
<button
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"
aria-label="Přepnout téma"
aria-label={t("common.toggleTheme")}
>
{theme === "dark" ? (
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -82,7 +84,7 @@ export default function Header() {
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"
>
Odhlásit
{t("auth.logout")}
</button>
</div>
) : (
@@ -90,7 +92,7 @@ export default function Header() {
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"
>
Přihlásit
{t("auth.submit")}
</Link>
)}
</div>
@@ -101,7 +103,7 @@ export default function Header() {
<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"
aria-label={t("common.menu")}
>
{(user.name || user.email || "?").charAt(0).toUpperCase()}
</button>
@@ -109,7 +111,7 @@ export default function Header() {
<button
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"
aria-label="Menu"
aria-label={t("common.menu")}
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
@@ -132,11 +134,11 @@ export default function Header() {
<div className="absolute right-0 top-0 bottom-0 w-72 bg-white dark:bg-gray-900 shadow-2xl animate-slideInRight">
{/* Close button */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
<span className="font-semibold text-lg">Menu</span>
<span className="font-semibold text-lg">{t("common.menu")}</span>
<button
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"
aria-label="Zavřít menu"
aria-label={t("common.closeMenu")}
>
<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" />
@@ -152,7 +154,7 @@ export default function Header() {
{(user.name || user.email || "?").charAt(0).toUpperCase()}
</div>
<div className="min-w-0">
<p className="font-medium text-sm truncate">{user.name || "Uživatel"}</p>
<p className="font-medium text-sm truncate">{user.name || t("settings.user")}</p>
<p className="text-xs text-muted truncate">{user.email}</p>
</div>
</div>
@@ -169,7 +171,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}>
<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>
<span className="text-sm font-medium">Úkoly</span>
<span className="text-sm font-medium">{t("nav.tasks")}</span>
</Link>
<Link
@@ -180,7 +182,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}>
<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>
<span className="text-sm font-medium">Kalendář</span>
<span className="text-sm font-medium">{t("nav.calendar")}</span>
</Link>
<Link
@@ -191,7 +193,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}>
<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>
<span className="text-sm font-medium">{t("nav.chat")}</span>
</Link>
<Link
@@ -203,7 +205,7 @@ export default function Header() {
<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>
<span className="text-sm font-medium">{t("nav.settings")}</span>
</Link>
{/* Theme toggle */}
@@ -221,7 +223,7 @@ export default function Header() {
</svg>
)}
<span className="text-sm font-medium">
{theme === "dark" ? "Světlý režim" : "Tmavý režim"}
{theme === "dark" ? t("settings.light") : t("settings.dark")}
</span>
</button>
</div>
@@ -236,7 +238,7 @@ export default function Header() {
<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" />
</svg>
<span className="text-sm font-medium">Odhlásit se</span>
<span className="text-sm font-medium">{t("auth.logout")}</span>
</button>
</div>
)}