/* 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 (
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")}.
) : (
)}
);
}
return (
{/* Header */}
Admin Panel · OMT Centar
{!isMobile &&
Upravljanje kursevima i blogom
}
{/* 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 }) => (
))}
Kursevi
{!isMobile && }
{(isMobile ? ["Naziv kursa","Cena","Status","Akcije"] : ["Naziv kursa","Region","Nivo","Trajanje","Cena","Datum","Status","Akcije"]).map(h => (
| {h} |
))}
{courses.map((c, i) => (
| {c.title} |
{!isMobile && {c.region} | }
{!isMobile && {c.level} | }
{!isMobile && {c.duration} · {c.hours} | }
{c.price} |
{!isMobile && {c.startDate || "—"} | }
|
|
))}
{courses.length === 0 && (
)}
{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 }) => (
))}
Blog postovi
{(isMobile ? ["Naslov","Kat.","Status","Akcije"] : ["Naslov","Kategorija","Datum","Trajanje","Istaknuto","Status","Akcije"]).map(h => (
| {h} |
))}
{blogs.map((b, i) => (
| {b.title} |
{b.cat} |
{!isMobile && {b.date || "—"} | }
{!isMobile && {b.read} | }
{!isMobile &&
{b.featured ? Da : —}
| }
|
|
))}
{blogs.length === 0 && (
)}
>)}
{/* 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
)}
{!isNew && (
)}
);
}
function AdminBlogEditor({ blog, isNew, onSave, onCancel, isMobile }) {
const [f, setF] = useStateAdm({ ...blog });
const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
const setCheck = (k) => (e) => setF({ ...f, [k]: e.target.checked });
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 colSpan = isMobile ? "span 1" : "span 2";
return (
e.stopPropagation()} style={{ background:"#fff", borderRadius:isMobile?20:24, padding:isMobile?"24px 20px":36, maxWidth:680, width:"100%", maxHeight:"92vh", overflow:"auto", boxShadow:"var(--shadow-hover)" }}>
{isNew ? "Novi post" : "Uredi post"}
{isNew ? "Dodaj blog post" : f.title}
{f.imageUrl ? (
) : (
)}
{!isNew && (
)}
);
}
window.AdminPage = AdminPage;