/* global React */ const { useState: useStateAdm, useEffect: useEffectAdm, useRef: useRefAdm } = React; async function authFetch(action, extra = {}) { const fd = new FormData(); fd.append("action", action); Object.entries(extra).forEach(([k, v]) => fd.append(k, v)); const res = await fetch("/admin-auth.php", { method: "POST", body: fd }); return res.json(); } function AdminPage({ onNav }) { const { isMobile, isTablet, isDesktop } = useBreakpoint(); const bp = (d, t, m) => isMobile ? m : isTablet ? t : d; const [auth, setAuth] = useStateAdm(false); const [authChecked, setAuthChecked] = useStateAdm(false); const [pw, setPw] = useStateAdm(""); const [err, setErr] = useStateAdm(""); const [loading, setLoading] = useStateAdm(false); const [lockSecs, setLockSecs] = useStateAdm(0); const [courses, setCourses] = useCourses(); const [blogs, setBlogs] = useBlogs(); const [tab, setTab] = useStateAdm("kursevi"); const [editing, setEditing] = useStateAdm(null); const [editingBlog, setEditingBlog] = useStateAdm(null); // Proveri PHP session pri učitavanju useEffectAdm(() => { authFetch("check").then(d => { setAuth(!!d.ok); setAuthChecked(true); }).catch(() => setAuthChecked(true)); }, []); const login = async (e) => { e.preventDefault(); if (loading || lockSecs > 0) return; setLoading(true); setErr(""); try { const d = await authFetch("login", { password: pw }); if (d.ok) { setAuth(true); setErr(""); } else if (d.locked) { setLockSecs(d.remaining || 600); const t = setInterval(() => { setLockSecs(s => { if (s <= 1) { clearInterval(t); return 0; } return s - 1; }); }, 1000); setErr("Previše pokušaja — pristup privremeno blokiran."); } else { const left = d.attempts_left ?? ""; setErr(`Pogrešna lozinka.${left ? ` Još ${left} pokušaj${left === 1 ? "" : "a"}.` : ""}`); } } catch { setErr("Greška veze sa serverom."); } finally { setLoading(false); } }; const logout = async () => { await authFetch("logout"); setAuth(false); setPw(""); }; const save = (data) => { if (editing === "new") { setCourses([...courses, { ...data, id: "c-" + Date.now(), active: true }]); } else { setCourses(courses.map(c => c.id === editing ? { ...c, ...data } : c)); } setEditing(null); }; const remove = (id) => { if (confirm("Obrisati kurs? Ova akcija je nepovratna.")) setCourses(courses.filter(c => c.id !== id)); }; const toggle = (id) => setCourses(courses.map(c => c.id === id ? { ...c, active: !c.active } : c)); const resetDefaults = () => { if (confirm("Vratiti podrazumevane kurseve? Sve izmene će biti izgubljene.")) setCourses(DEFAULT_COURSES); }; const saveBlog = (data) => { if (editingBlog === "new") { setBlogs([{ ...data, id: "b-" + Date.now(), active: true }, ...blogs]); } else { setBlogs(blogs.map(b => b.id === editingBlog ? { ...b, ...data } : b)); } setEditingBlog(null); }; const removeBlog = (id) => { if (confirm("Obrisati post? Ova akcija je nepovratna.")) setBlogs(blogs.filter(b => b.id !== id)); }; const toggleBlog = (id) => setBlogs(blogs.map(b => b.id === id ? { ...b, active: !b.active } : b)); const editingBlogData = editingBlog === "new" ? { title:"", cat:"Tehnike", read:"5 min", date:"", excerpt:"", imageUrl:"", featured:false, active:true } : blogs.find(b => b.id === editingBlog); const editingCourse = editing === "new" ? { title: "", region: "Vratna kičma", level: "Srednji", duration: "2 dana", hours: "16h", price: "€600", startDate: "", deadline: "", location: "OMT Centar · Beograd, Centar", spots: "1 polaznik", techniques: [], description: "", curriculum: [], badge: "", imageUrl: "" } : courses.find(c => c.id === editing); const inp = { width: "100%", padding: "11px 14px", fontSize: 14, fontFamily: "inherit", border: "1.5px solid var(--border-default)", borderRadius: 8, outline: 0, background: "#fff", color: "var(--text-primary)" }; const lbl = { display: "block", fontSize: 11, fontWeight: 700, color: "var(--text-secondary)", marginBottom: 6, letterSpacing: "0.04em", textTransform: "uppercase" }; const adminBtn = { display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11, fontFamily: "inherit", fontWeight: 600, cursor: "pointer", background: "var(--surface-2)", color: "var(--text-secondary)", border: 0, borderRadius: 6, padding: "5px 10px", letterSpacing: "0.04em", textTransform: "uppercase" }; if (!authChecked) { return
Učitavanje…
; } if (!auth) { return (
OMT Centar

Admin panel

OMT Centar — upravljanje kursevima

{lockSecs > 0 ? (
Pristup privremeno blokiran
Pokušajte ponovo za {Math.floor(lockSecs/60)}:{String(lockSecs%60).padStart(2,"0")}.
) : (
{ setPw(e.target.value); setErr(""); }} placeholder="Unesite lozinku" autoFocus autoComplete="current-password"/> {err && (
{err}
)}
)}
onNav("home")} style={{ fontSize: 13, color: "var(--text-tertiary)", cursor: "pointer" }}>Nazad na sajt
); } return (
{/* Header */}
Admin Panel · OMT Centar
{!isMobile &&
Upravljanje kursevima i blogom
}
{!isMobile && ( onNav("home")} style={{ fontSize: 13, color: "rgba(255,255,255,0.65)", cursor: "pointer" }}> Pogledaj sajt )}
{/* Tab switcher */}
{[["kursevi","book","Kursevi"],["blog","edit","Blog"]].map(([key,icon,label]) => ( ))}
{/* ── KURSEVI TAB ── */} {tab === "kursevi" && (<> {/* Stats */}
{[ { label: "Ukupno kurseva", val: courses.length, icon: "book" }, { label: "Aktivnih", val: courses.filter(c=>c.active).length, icon: "checkCircle" }, { label: "Neaktivnih", val: courses.filter(c=>!c.active).length, icon: "minus" }, { label: "Kapacitet", val: courses.filter(c=>c.active).length + " mesta", icon: "users" }, ].map(({ label, val, icon }) => (
{val}
{label}
))}

Kursevi

{!isMobile && }
{(isMobile ? ["Naziv kursa","Cena","Status","Akcije"] : ["Naziv kursa","Region","Nivo","Trajanje","Cena","Datum","Status","Akcije"]).map(h => ( ))} {courses.map((c, i) => ( {!isMobile && } {!isMobile && } {!isMobile && } {!isMobile && } ))}
{h}
{c.title}{c.region}{c.level}{c.duration} · {c.hours}{c.price}{c.startDate || "—"}
{courses.length === 0 && (
Nema kurseva. setEditing("new")} style={{ color: "var(--omt-green-ink)", cursor: "pointer", fontWeight: 600 }}>Dodajte prvi.
)}
{isMobile &&
} )} {/* ── BLOG TAB ── */} {tab === "blog" && (<> {/* Blog Stats */}
{[ { label: "Ukupno postova", val: blogs.length, icon: "book" }, { label: "Objavljenih", val: blogs.filter(b=>b.active).length, icon: "checkCircle" }, { label: "U nacrtu", val: blogs.filter(b=>!b.active).length, icon: "minus" }, ].map(({ label, val, icon }) => (
{val}
{label}
))}

Blog postovi

{(isMobile ? ["Naslov","Kat.","Status","Akcije"] : ["Naslov","Kategorija","Datum","Trajanje","Istaknuto","Status","Akcije"]).map(h => ( ))} {blogs.map((b, i) => ( {!isMobile && } {!isMobile && } {!isMobile && } ))}
{h}
{b.title} {b.cat}{b.date || "—"}{b.read} {b.featured ? Da : }
{blogs.length === 0 && (
Nema postova. setEditingBlog("new")} style={{ color: "var(--omt-green-ink)", cursor: "pointer", fontWeight: 600 }}>Dodajte prvi.
)}
)}
{/* Course Editor Modal */} {editing && editingCourse && ( setEditing(null)} isMobile={isMobile} /> )} {/* Blog Editor Modal */} {editingBlog && editingBlogData && ( setEditingBlog(null)} isMobile={isMobile} /> )}
); } function AdminCourseEditor({ course, isNew, onSave, onCancel, isMobile }) { const [f, setF] = useStateAdm({ ...course, imageUrl: course.imageUrl || "", active: course.active !== false, techniques: (course.techniques || []).join(", "), curriculum: (course.curriculum || []).join("\n"), }); const set = (k) => (e) => setF({ ...f, [k]: e.target.value }); const handleImage = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => setF(prev => ({ ...prev, imageUrl: ev.target.result })); reader.readAsDataURL(file); }; const inp = { width: "100%", padding: "11px 14px", fontSize: 14, fontFamily: "inherit", border: "1.5px solid var(--border-default)", borderRadius: 8, outline: 0, background: "#fff", color: "var(--text-primary)" }; const lbl = { display: "block", fontSize: 11, fontWeight: 700, color: "var(--text-secondary)", marginBottom: 6, letterSpacing: "0.04em", textTransform: "uppercase" }; const submit = () => { const data = { ...f, active: f.active !== false, techniques: f.techniques.split(",").map(t => t.trim()).filter(Boolean), curriculum: f.curriculum.split("\n").map(t => t.trim()).filter(Boolean), }; onSave(data); }; const colSpan = isMobile ? "span 1" : "span 2"; return (
e.stopPropagation()} style={{ background: "#fff", borderRadius: isMobile ? 20 : 24, padding: isMobile ? "24px 20px" : 36, maxWidth: 760, width: "100%", maxHeight: "92vh", overflow: "auto", boxShadow: "var(--shadow-hover)" }}>
{isNew ? "Novi kurs" : "Uredi kurs"}

{isNew ? "Dodaj novi kurs" : f.title}

{f.imageUrl ? (
) : (
Izaberite fotografiju kursa
)}