- Dashboard (/dashboard): configurable widget system with 6 widget types (current_tasks, category_time, today_progress, next_task, motivace, calendar_mini) stored in localStorage widget_config - Lock screen (/lockscreen): fullscreen with clock, active group badge, up to 4 current tasks, gradient from group color, swipe/tap to unlock - InactivityMonitor: auto-redirect to lockscreen after configurable timeout (only in PWA standalone/fullscreen mode) - Settings: widget toggle switches, inactivity timeout slider, lockscreen preview - manifest.json: added display_override ["fullscreen","standalone"] + orientation portrait Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
542 lines
24 KiB
TypeScript
542 lines
24 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import { useAuth } from "@/lib/auth";
|
||
import { useTheme } from "@/components/ThemeProvider";
|
||
import { useTranslation, LOCALES } from "@/lib/i18n";
|
||
import type { Locale } from "@/lib/i18n";
|
||
import type { Group } from "@/lib/api";
|
||
import Link from "next/link";
|
||
|
||
type WidgetType = "current_tasks" | "category_time" | "today_progress" | "next_task" | "motivace" | "calendar_mini";
|
||
|
||
const ALL_WIDGETS: { key: WidgetType; label: string }[] = [
|
||
{ key: "current_tasks", label: "Aktualni ukoly" },
|
||
{ key: "category_time", label: "Aktivni kategorie" },
|
||
{ key: "today_progress", label: "Dnesni pokrok" },
|
||
{ key: "next_task", label: "Pristi ukol" },
|
||
{ key: "motivace", label: "Motivace" },
|
||
{ key: "calendar_mini", label: "Mini kalendar" },
|
||
];
|
||
|
||
const DEFAULT_WIDGETS: WidgetType[] = ["current_tasks", "category_time", "today_progress"];
|
||
|
||
interface GroupSetting {
|
||
from: string;
|
||
to: string;
|
||
days: number[];
|
||
locationName: string;
|
||
gps: string;
|
||
radius: number;
|
||
}
|
||
|
||
export default function SettingsPage() {
|
||
const { token, user, logout } = useAuth();
|
||
const { theme, toggleTheme } = useTheme();
|
||
const { t, locale, setLocale } = useTranslation();
|
||
const router = useRouter();
|
||
const [notifications, setNotifications] = useState({
|
||
push: true,
|
||
email: false,
|
||
taskReminders: true,
|
||
dailySummary: false,
|
||
});
|
||
const [saved, setSaved] = useState(false);
|
||
const [groups, setGroups] = useState<Group[]>([]);
|
||
const [groupSettings, setGroupSettings] = useState<Record<string, GroupSetting>>({});
|
||
const [expandedGroup, setExpandedGroup] = useState<string | null>(null);
|
||
const [savedGroup, setSavedGroup] = useState<string | null>(null);
|
||
const [widgetEnabled, setWidgetEnabled] = useState<Record<WidgetType, boolean>>(() => {
|
||
const defaults: Record<WidgetType, boolean> = { current_tasks: true, category_time: true, today_progress: true, next_task: false, motivace: false, calendar_mini: false };
|
||
return defaults;
|
||
});
|
||
const [inactivityTimeout, setInactivityTimeout] = useState(5);
|
||
const [widgetSaved, setWidgetSaved] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (!token) {
|
||
router.replace("/login");
|
||
}
|
||
// Load saved preferences
|
||
if (typeof window !== "undefined") {
|
||
const savedNotifs = localStorage.getItem("taskteam_notifications");
|
||
if (savedNotifs) {
|
||
try {
|
||
setNotifications(JSON.parse(savedNotifs));
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
const savedWidgets = localStorage.getItem("widget_config");
|
||
if (savedWidgets) {
|
||
try {
|
||
const cfg = JSON.parse(savedWidgets);
|
||
if (Array.isArray(cfg.enabled)) {
|
||
const map: Record<WidgetType, boolean> = { current_tasks: false, category_time: false, today_progress: false, next_task: false, motivace: false, calendar_mini: false };
|
||
for (const w of cfg.enabled) map[w as WidgetType] = true;
|
||
setWidgetEnabled(map);
|
||
}
|
||
if (cfg.inactivityTimeout > 0) setInactivityTimeout(cfg.inactivityTimeout);
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
}
|
||
}, [token, router]);
|
||
|
||
useEffect(() => {
|
||
if (!token) return;
|
||
fetch("/api/v1/groups", { headers: { Authorization: `Bearer ${token}` } })
|
||
.then(r => r.json())
|
||
.then(res => {
|
||
const data: Group[] = res.data || [];
|
||
setGroups(data);
|
||
const settings: Record<string, GroupSetting> = {};
|
||
for (const g of data) {
|
||
const tz = g.time_zones?.[0];
|
||
const loc = g.locations?.[0];
|
||
settings[g.id] = {
|
||
from: tz?.from || "",
|
||
to: tz?.to || "",
|
||
days: tz?.days || [],
|
||
locationName: loc?.name || "",
|
||
gps: (loc?.lat != null && loc?.lng != null) ? `${loc.lat}, ${loc.lng}` : "",
|
||
radius: loc?.radius_m || 200,
|
||
};
|
||
}
|
||
setGroupSettings(settings);
|
||
})
|
||
.catch(() => {});
|
||
}, [token]);
|
||
|
||
function toggleGroup(id: string) {
|
||
setExpandedGroup(prev => prev === id ? null : id);
|
||
}
|
||
|
||
function updateGroupSetting(groupId: string, key: keyof GroupSetting, value: string | number | number[]) {
|
||
setGroupSettings(prev => ({
|
||
...prev,
|
||
[groupId]: { ...prev[groupId], [key]: value },
|
||
}));
|
||
}
|
||
|
||
function toggleDay(groupId: string, day: number) {
|
||
const current = groupSettings[groupId]?.days || [];
|
||
const next = current.includes(day) ? current.filter(d => d !== day) : [...current, day];
|
||
updateGroupSetting(groupId, "days", next);
|
||
}
|
||
|
||
function getCurrentLocation(groupId: string) {
|
||
if (!navigator.geolocation) return;
|
||
navigator.geolocation.getCurrentPosition(pos => {
|
||
const gps = `${pos.coords.latitude.toFixed(6)}, ${pos.coords.longitude.toFixed(6)}`;
|
||
updateGroupSetting(groupId, "gps", gps);
|
||
});
|
||
}
|
||
|
||
async function saveGroupSettings(groupId: string) {
|
||
const s = groupSettings[groupId] || {} as GroupSetting;
|
||
const timeZones = (s.from && s.to) ? [{
|
||
days: s.days?.length ? s.days : [0, 1, 2, 3, 4, 5, 6],
|
||
from: s.from,
|
||
to: s.to,
|
||
}] : [];
|
||
|
||
const gpsParts = (s.gps || "").split(",").map(x => parseFloat(x.trim()));
|
||
const lat = gpsParts[0] || null;
|
||
const lng = gpsParts[1] || null;
|
||
const locations = s.locationName ? [{
|
||
name: s.locationName,
|
||
lat: isNaN(lat as number) ? null : lat,
|
||
lng: isNaN(lng as number) ? null : lng,
|
||
radius_m: Number(s.radius) || 200,
|
||
}] : [];
|
||
|
||
await fetch(`/api/v1/groups/${groupId}`, {
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
||
body: JSON.stringify({ time_zones: timeZones, locations }),
|
||
});
|
||
|
||
setSavedGroup(groupId);
|
||
setTimeout(() => setSavedGroup(null), 2000);
|
||
}
|
||
|
||
function toggleWidget(key: WidgetType) {
|
||
setWidgetEnabled(prev => ({ ...prev, [key]: !prev[key] }));
|
||
}
|
||
|
||
function saveWidgetConfig() {
|
||
const enabled = ALL_WIDGETS.filter(w => widgetEnabled[w.key]).map(w => w.key);
|
||
const config = { enabled, inactivityTimeout };
|
||
localStorage.setItem("widget_config", JSON.stringify(config));
|
||
setWidgetSaved(true);
|
||
setTimeout(() => setWidgetSaved(false), 2000);
|
||
}
|
||
|
||
function handleSave() {
|
||
if (typeof window !== "undefined") {
|
||
localStorage.setItem("taskteam_notifications", JSON.stringify(notifications));
|
||
}
|
||
setSaved(true);
|
||
setTimeout(() => setSaved(false), 2000);
|
||
}
|
||
|
||
function handleLogout() {
|
||
logout();
|
||
router.push("/login");
|
||
}
|
||
|
||
if (!token) return null;
|
||
|
||
return (
|
||
<div className="max-w-lg mx-auto space-y-6 px-4 pb-24 sm:pb-8">
|
||
<h1 className="text-xl font-bold">{t("settings.title")}</h1>
|
||
|
||
{/* Profile section */}
|
||
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-5">
|
||
<h2 className="text-sm font-semibold text-muted uppercase tracking-wide mb-4">{t("settings.profile")}</h2>
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-14 h-14 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full flex items-center justify-center text-xl font-bold">
|
||
{(user?.name || user?.email || "?").charAt(0).toUpperCase()}
|
||
</div>
|
||
<div className="min-w-0">
|
||
<p className="font-semibold text-lg">{user?.name || t("settings.user")}</p>
|
||
<p className="text-sm text-muted truncate">{user?.email}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Install section */}
|
||
<section id="install" className="bg-white dark:bg-gray-900 rounded-xl p-4 border border-gray-200 dark:border-gray-700">
|
||
<h2 className="font-semibold mb-3">{t("settings.install") || "Instalace"}</h2>
|
||
<div className="space-y-2">
|
||
<a href="https://expo.dev/accounts/it-enterprise/projects/task-team/builds/b31c63a8-0e4f-44d6-80e9-98cb24174037"
|
||
target="_blank" rel="noopener"
|
||
className="flex items-center gap-3 px-4 py-3 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition-colors">
|
||
<span className="text-xl">🤖</span>
|
||
<span>Android APK</span>
|
||
<span className="ml-auto text-sm opacity-75">Stáhnout</span>
|
||
</a>
|
||
<a href="https://tasks.hasdo.info"
|
||
target="_blank" rel="noopener"
|
||
className="flex items-center gap-3 px-4 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors">
|
||
<span className="text-xl">🌐</span>
|
||
<span>PWA Web App</span>
|
||
<span className="ml-auto text-sm opacity-75">Otevřít</span>
|
||
</a>
|
||
<div className="flex items-center gap-3 px-4 py-3 bg-gray-200 dark:bg-gray-700 text-gray-500 rounded-lg">
|
||
<span className="text-xl">🍎</span>
|
||
<span>iOS (App Store)</span>
|
||
<span className="ml-auto text-sm">Připravujeme</span>
|
||
</div>
|
||
<div className="flex items-center gap-3 px-4 py-3 bg-gray-200 dark:bg-gray-700 text-gray-500 rounded-lg">
|
||
<span className="text-xl">📦</span>
|
||
<span>F-Droid</span>
|
||
<span className="ml-auto text-sm">Připravujeme</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Appearance */}
|
||
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-5">
|
||
<h2 className="text-sm font-semibold text-muted uppercase tracking-wide mb-4">{t("settings.appearance")}</h2>
|
||
|
||
{/* Theme toggle */}
|
||
<div className="flex items-center justify-between py-3">
|
||
<div className="flex items-center gap-3">
|
||
{theme === "dark" ? (
|
||
<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="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||
</svg>
|
||
) : (
|
||
<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="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||
</svg>
|
||
)}
|
||
<span className="text-sm font-medium">
|
||
{theme === "dark" ? t("settings.dark") : t("settings.light")}
|
||
</span>
|
||
</div>
|
||
<button
|
||
onClick={toggleTheme}
|
||
className={`relative w-12 h-7 rounded-full transition-colors ${
|
||
theme === "dark" ? "bg-blue-600" : "bg-gray-300"
|
||
}`}
|
||
aria-label={t("common.toggleTheme")}
|
||
>
|
||
<div
|
||
className={`absolute top-0.5 w-6 h-6 bg-white rounded-full shadow transition-transform ${
|
||
theme === "dark" ? "translate-x-5" : "translate-x-0.5"
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Language */}
|
||
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-5">
|
||
<h2 className="text-sm font-semibold text-muted uppercase tracking-wide mb-4">{t("settings.language")}</h2>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{LOCALES.map((lang) => (
|
||
<button
|
||
key={lang.code}
|
||
onClick={() => setLocale(lang.code as Locale)}
|
||
className={`flex items-center gap-2 px-4 py-3 rounded-xl text-sm font-medium transition-all ${
|
||
locale === lang.code
|
||
? "bg-blue-600 text-white shadow-md"
|
||
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||
}`}
|
||
>
|
||
<span className="text-lg">{lang.flag}</span>
|
||
<span>{lang.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Notifications */}
|
||
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-5">
|
||
<h2 className="text-sm font-semibold text-muted uppercase tracking-wide mb-4">{t("settings.notifications")}</h2>
|
||
<div className="space-y-1">
|
||
{[
|
||
{ key: "push" as const, label: t("settings.push") },
|
||
{ key: "email" as const, label: t("settings.email") },
|
||
{ key: "taskReminders" as const, label: t("settings.taskReminders") },
|
||
{ key: "dailySummary" as const, label: t("settings.dailySummary") },
|
||
].map((item) => (
|
||
<div key={item.key} className="flex items-center justify-between py-3">
|
||
<span className="text-sm font-medium">{item.label}</span>
|
||
<button
|
||
onClick={() =>
|
||
setNotifications((prev) => ({
|
||
...prev,
|
||
[item.key]: !prev[item.key],
|
||
}))
|
||
}
|
||
className={`relative w-12 h-7 rounded-full transition-colors ${
|
||
notifications[item.key] ? "bg-blue-600" : "bg-gray-300 dark:bg-gray-600"
|
||
}`}
|
||
aria-label={item.label}
|
||
>
|
||
<div
|
||
className={`absolute top-0.5 w-6 h-6 bg-white rounded-full shadow transition-transform ${
|
||
notifications[item.key] ? "translate-x-5" : "translate-x-0.5"
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Widget & Lock Screen settings */}
|
||
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-5">
|
||
<h2 className="text-sm font-semibold text-muted uppercase tracking-wide mb-4">Widgety & Zamykaci obrazovka</h2>
|
||
<div className="space-y-1">
|
||
{ALL_WIDGETS.map(w => (
|
||
<div key={w.key} className="flex items-center justify-between py-3">
|
||
<span className="text-sm font-medium">{w.label}</span>
|
||
<button
|
||
onClick={() => toggleWidget(w.key)}
|
||
className={`relative w-12 h-7 rounded-full transition-colors ${
|
||
widgetEnabled[w.key] ? "bg-blue-600" : "bg-gray-300 dark:bg-gray-600"
|
||
}`}
|
||
aria-label={w.label}
|
||
>
|
||
<div className={`absolute top-0.5 w-6 h-6 bg-white rounded-full shadow transition-transform ${
|
||
widgetEnabled[w.key] ? "translate-x-5" : "translate-x-0.5"
|
||
}`} />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Inactivity timeout */}
|
||
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-sm font-medium">Neaktivita (min)</span>
|
||
<span className="text-sm text-muted">{inactivityTimeout} min</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min={1}
|
||
max={30}
|
||
step={1}
|
||
value={inactivityTimeout}
|
||
onChange={e => setInactivityTimeout(Number(e.target.value))}
|
||
className="w-full"
|
||
/>
|
||
<p className="text-xs text-muted mt-1">Po teto dobe neaktivity se zobrazi zamykaci obrazovka (pouze v PWA rezimu).</p>
|
||
</div>
|
||
|
||
{/* Preview lockscreen button */}
|
||
<Link
|
||
href="/lockscreen"
|
||
className="mt-4 w-full py-2.5 rounded-xl bg-gray-100 dark:bg-gray-800 text-sm font-medium text-center block hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||
>
|
||
Nahled zamykaci obrazovky
|
||
</Link>
|
||
|
||
{/* Save widget config */}
|
||
<button
|
||
onClick={saveWidgetConfig}
|
||
className={`mt-3 w-full py-2.5 rounded-xl font-medium transition-all text-sm ${
|
||
widgetSaved ? "bg-green-600 text-white" : "bg-blue-600 hover:bg-blue-700 text-white"
|
||
}`}
|
||
>
|
||
{widgetSaved ? "Ulozeno \u2713" : "Ulozit nastaveni widgetu"}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Groups settings */}
|
||
{groups.length > 0 && (
|
||
<div style={{ marginTop: 8 }}>
|
||
<div style={{ fontSize: 11, color: "#6B6B85", marginBottom: 12, textTransform: "uppercase", letterSpacing: 1, fontWeight: 600 }}>
|
||
Skupiny
|
||
</div>
|
||
{groups.map(group => (
|
||
<div key={group.id} style={{
|
||
background: "#13131A", border: `1px solid #2A2A3A`,
|
||
borderLeft: `3px solid ${group.color || "#4F46E5"}`,
|
||
borderRadius: 12, marginBottom: 8, overflow: "hidden",
|
||
}}>
|
||
<div onClick={() => toggleGroup(group.id)}
|
||
style={{ padding: "12px 16px", display: "flex", alignItems: "center", gap: 10, cursor: "pointer" }}>
|
||
<span style={{ fontSize: 18 }}>{group.icon || "📁"}</span>
|
||
<span style={{ flex: 1, fontWeight: 500, fontSize: 14, color: "#F0F0F5" }}>
|
||
{group.display_name || group.name}
|
||
</span>
|
||
<span style={{ color: "#6B6B85", fontSize: 12 }}>
|
||
{group.time_zones?.[0] ? `${group.time_zones[0].from}–${group.time_zones[0].to}` : ""}
|
||
</span>
|
||
<span style={{ color: "#6B6B85", fontSize: 12 }}>{expandedGroup === group.id ? "▲" : "▼"}</span>
|
||
</div>
|
||
|
||
{expandedGroup === group.id && (
|
||
<div style={{ padding: "0 16px 16px", borderTop: "1px solid #2A2A3A" }}>
|
||
{/* CAS AKTIVITY */}
|
||
<div style={{ marginTop: 12 }}>
|
||
<div style={{ fontSize: 11, color: "#6B6B85", marginBottom: 6, textTransform: "uppercase", letterSpacing: 0.5 }}>
|
||
Čas aktivity (volitelné)
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||
<input type="time" value={groupSettings[group.id]?.from || ""}
|
||
onChange={e => updateGroupSetting(group.id, "from", e.target.value)}
|
||
style={{ flex: 1, padding: "8px", borderRadius: 8, border: "1px solid #2A2A3A", background: "#0A0A0F", color: "#F0F0F5", fontSize: 14 }}
|
||
/>
|
||
<span style={{ color: "#6B6B85" }}>–</span>
|
||
<input type="time" value={groupSettings[group.id]?.to || ""}
|
||
onChange={e => updateGroupSetting(group.id, "to", e.target.value)}
|
||
style={{ flex: 1, padding: "8px", borderRadius: 8, border: "1px solid #2A2A3A", background: "#0A0A0F", color: "#F0F0F5", fontSize: 14 }}
|
||
/>
|
||
</div>
|
||
{/* DNY V TYDNU */}
|
||
<div style={{ display: "flex", gap: 4, marginTop: 8 }}>
|
||
{["Ne", "Po", "Út", "St", "Čt", "Pá", "So"].map((d, i) => {
|
||
const active = (groupSettings[group.id]?.days || []).includes(i);
|
||
return (
|
||
<button key={i} onClick={() => toggleDay(group.id, i)} style={{
|
||
flex: 1, padding: "6px 0", borderRadius: 6, fontSize: 11,
|
||
border: `1px solid ${active ? (group.color || "#4F46E5") : "#2A2A3A"}`,
|
||
background: active ? `${group.color || "#4F46E5"}20` : "transparent",
|
||
color: active ? (group.color || "#4F46E5") : "#6B6B85",
|
||
cursor: "pointer",
|
||
}}>{d}</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* GPS MISTO */}
|
||
<div style={{ marginTop: 12 }}>
|
||
<div style={{ fontSize: 11, color: "#6B6B85", marginBottom: 6, textTransform: "uppercase", letterSpacing: 0.5 }}>
|
||
Místo výkonu (volitelné)
|
||
</div>
|
||
<input
|
||
placeholder="Název místa (např. Synagoga, Kancelář...)"
|
||
value={groupSettings[group.id]?.locationName || ""}
|
||
onChange={e => updateGroupSetting(group.id, "locationName", e.target.value)}
|
||
style={{ width: "100%", padding: "8px", borderRadius: 8, border: "1px solid #2A2A3A", background: "#0A0A0F", color: "#F0F0F5", fontSize: 14, marginBottom: 8, boxSizing: "border-box" }}
|
||
/>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<input
|
||
placeholder="GPS souřadnice (lat, lng)"
|
||
value={groupSettings[group.id]?.gps || ""}
|
||
onChange={e => updateGroupSetting(group.id, "gps", e.target.value)}
|
||
style={{ flex: 1, padding: "8px", borderRadius: 8, border: "1px solid #2A2A3A", background: "#0A0A0F", color: "#F0F0F5", fontSize: 14 }}
|
||
/>
|
||
<button onClick={() => getCurrentLocation(group.id)}
|
||
style={{ padding: "8px 12px", borderRadius: 8, border: "1px solid #1D4ED8", background: "#1D4ED820", color: "#60A5FA", cursor: "pointer", fontSize: 12, whiteSpace: "nowrap" }}>
|
||
Moje GPS
|
||
</button>
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 8 }}>
|
||
<span style={{ fontSize: 12, color: "#6B6B85" }}>Polomer:</span>
|
||
<input type="range" min="50" max="1000" step="50"
|
||
value={groupSettings[group.id]?.radius || 200}
|
||
onChange={e => updateGroupSetting(group.id, "radius", Number(e.target.value))}
|
||
style={{ flex: 1 }}
|
||
/>
|
||
<span style={{ fontSize: 12, color: "#9999AA", minWidth: 50 }}>
|
||
{groupSettings[group.id]?.radius || 200}m
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ULOZIT */}
|
||
<button onClick={() => saveGroupSettings(group.id)}
|
||
style={{ marginTop: 12, width: "100%", padding: "10px", borderRadius: 10, background: savedGroup === group.id ? "#16A34A" : "#1D4ED8", color: "white", border: "none", cursor: "pointer", fontSize: 14, fontWeight: 500, transition: "background 0.2s" }}>
|
||
{savedGroup === group.id ? "Uloženo ✓" : "Uložit nastavení skupiny"}
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Save button */}
|
||
<button
|
||
onClick={handleSave}
|
||
className={`w-full py-3 rounded-xl font-medium transition-all ${
|
||
saved
|
||
? "bg-green-600 text-white"
|
||
: "bg-blue-600 hover:bg-blue-700 text-white"
|
||
}`}
|
||
>
|
||
{saved ? t("settings.saved") : t("settings.save")}
|
||
</button>
|
||
|
||
{/* Logout */}
|
||
<button
|
||
onClick={handleLogout}
|
||
className="w-full py-3 rounded-xl font-medium border border-red-300 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||
>
|
||
{t("auth.logout")}
|
||
</button>
|
||
|
||
{/* App info */}
|
||
<div className="text-center text-xs text-muted py-4">
|
||
<p>{t("common.appName")} {t("common.appVersion")}</p>
|
||
</div>
|
||
|
||
<section className="bg-red-50 dark:bg-red-900/20 rounded-xl p-4 border border-red-200 dark:border-red-800 mt-6">
|
||
<h2 className="font-semibold text-red-600 dark:text-red-400 mb-2">Smazat ucet</h2>
|
||
<p className="text-sm text-red-500 mb-3">Trvale smazat ucet a vsechna data. Tuto akci nelze vratit.</p>
|
||
<button onClick={() => {
|
||
if (confirm("Opravdu chcete trvale smazat svuj ucet a vsechna data?")) {
|
||
const token = localStorage.getItem("taskteam_token");
|
||
fetch("/api/v1/auth/delete-account", { method: "DELETE", headers: { Authorization: "Bearer " + token } })
|
||
.then(r => r.json())
|
||
.then(() => { localStorage.clear(); window.location.href = "/login"; });
|
||
}
|
||
}} className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm font-medium">
|
||
Smazat ucet
|
||
</button>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|