1/* ===================================================================== 2 Panel administratora â warstwa PULPITU (kafle, prawa kolumna, nagÅówek). 3 Nie dotyka logiki z admin.js â tylko wypeÅnia kafle statystyk (liczby, 4 wykresiki z 14 dni, przyrost w miesiÄ cu), kartÄ âStatystyki miesiÄczne", 5 panel âProjekty do uratowania", dzwonek w nagÅówku, menu boczne, 6 hamburger i szybkie akcje. Akcje delegujemy do istniejÄ cych przycisków 7 (te same ID). Dane wykresików: /api/admin/pulpit. 8 ===================================================================== */ 9(() => { 10 "use strict"; 11 12 const $ = (s) => document.querySelector(s); 13 const shell = $("#adminShell"); 14 if (!shell) return; 15 16 const fmt = (n) => (Number(n) || 0).toLocaleString("pl-PL"); 17 const esc = (v) => String(v == null ? "" : v).replace(/[&<>"]/g, (c) => 18 ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); 19 20 async function api(sciezka) { 21 const r = await fetch(`/api/${sciezka}`, { credentials: "same-origin", cache: "no-store" }); 22 if (!r.ok) throw new Error(String(r.status)); 23 return r.json(); 24 } 25 26 function set(sel, val) { const e = $(sel); if (e) e.textContent = val; } 27 28 /* ---- wykresik sÅupkowy pod kaflem (ostatnie 14 dni) ---- */ 29 function wykresik(sel, seria) { 30 const box = $(sel); 31 if (!box || !Array.isArray(seria) || !seria.length) return; 32 const max = Math.max(...seria, 1); 33 box.innerHTML = seria 34 .map((v) => `<i style="height:${Math.max(8, Math.round((v / max) * 100))}%"></i>`) 35 .join(""); 36 } 37 38 /* ---- przyrost pod liczbÄ w kaflu ---- */ 39 function przyrost(sel, tekst, plus) { 40 const e = $(sel); 41 if (!e) return; 42 e.textContent = tekst; 43 e.classList.toggle("is-plus", !!plus); 44 } 45 46 /* ---- kafle + panel do uratowania ---- */ 47 async function wypelnijKafle() { 48 try { 49 const projekty = await api("admin/projects"); 50 if (Array.isArray(projekty)) { 51 set("#tileProjekty", fmt(projekty.length)); 52 set("#tilePobrania", fmt(projekty.reduce((s, p) => s + (Number(p.pobrania) || 0), 0))); 53 set("#tileWyswietlenia", fmt(projekty.reduce((s, p) => s + (Number(p.wyswietlenia) || 0), 0))); 54 } 55 } catch {} 56 57 try { 58 const lista = await api("admin/drugie-zycie/projekty"); 59 if (Array.isArray(lista)) { 60 set("#tileRatujemy", fmt(lista.length)); 61 renderRatujemyRail(lista); 62 } 63 } catch {} 64 65 wczytajPulpit(""); 66 } 67 68 const MIESIACE = ["StyczeÅ", "Luty", "Marzec", "KwiecieÅ", "Maj", "Czerwiec", "Lipiec", "SierpieÅ", "WrzesieÅ", "Październik", "Listopad", "GrudzieÅ"]; 69 const nazwaMiesiaca = (ym) => { 70 const [r, m] = String(ym).split("-").map(Number); 71 return m ? `${MIESIACE[m - 1]} ${r}` : ym; 72 }; 73 74 async function wczytajPulpit(miesiac) { 75 let d; 76 try { 77 d = await api(`admin/pulpit${miesiac ? `?miesiac=${encodeURIComponent(miesiac)}` : ""}`); 78 } catch { 79 return; 80 } 81 if (!d) return; 82 const k = d.kafle || {}; 83 84 // Kafle odÅwieżamy tylko przy pierwszym wczytaniu (nie zaleÅ¼Ä od miesiÄ ca). 85 if (!miesiac) { 86 wykresik("#sparkProjekty", k.projekty && k.projekty.seria); 87 wykresik("#sparkPobrania", k.pobrania && k.pobrania.seria); 88 wykresik("#sparkWyswietlenia", k.wyswietlenia && k.wyswietlenia.seria); 89 wykresik("#sparkKomentarze", k.komentarze && k.komentarze.seria); 90 wykresik("#sparkRatujemy", k.ratujemy && k.ratujemy.seria); 91 92 const ile = (o, pole) => Number(o && o[pole]) || 0; 93 const pr = ile(k.projekty, "w_miesiacu"); 94 przyrost("#tileProjektyZm", pr ? `â +${fmt(pr)} w tym miesiÄ cu` : "Bez nowych w tym miesiÄ cu", pr); 95 const pb = ile(k.pobrania, "w_miesiacu"); 96 przyrost("#tilePobraniaZm", pb ? `â +${fmt(pb)} w tym miesiÄ cu` : "Brak pobraÅ w tym miesiÄ cu", pb); 97 const ws = ile(k.wyswietlenia, "w_miesiacu"); 98 przyrost("#tileWyswietleniaZm", ws ? `â +${fmt(ws)} w tym miesiÄ cu` : "Brak wyÅwietleÅ w tym miesiÄ cu", ws); 99 const rt = ile(k.ratujemy, "nowe_7dni"); 100 przyrost("#tileRatujemyZm", rt ? `â +${fmt(rt)} ${rt === 1 ? "nowy" : "nowe"} (7 dni)` : "Bez nowych (7 dni)", rt); 101 102 const sel = $("#admMiesiac"); 103 if (sel && Array.isArray(d.miesiace)) { 104 sel.innerHTML = d.miesiace.map((m) => `<option value="${esc(m)}">${esc(nazwaMiesiaca(m))}</option>`).join(""); 105 sel.value = d.miesiac; 106 } 107 } 108 109 const mies = d.miesieczne || {}; 110 document.querySelectorAll("#admMiesieczne [data-m]").forEach((b) => { 111 const w = mies[b.dataset.m]; 112 b.textContent = w ? fmt(w.liczba) : "â"; 113 }); 114 document.querySelectorAll("#admMiesieczne [data-mz]").forEach((e) => { 115 const w = mies[e.dataset.mz]; 116 const z = w && w.zmiana; 117 e.className = z == null ? "" : z >= 0 ? "is-plus" : "is-minus"; 118 e.textContent = z == null ? "" : `${z >= 0 ? "â +" : "â "}${z}%`; 119 e.title = "Zmiana wzglÄdem poprzedniego miesiÄ ca"; 120 }); 121 } 122 123 const sel = $("#admMiesiac"); 124 if (sel) sel.addEventListener("change", () => wczytajPulpit(sel.value)); 125 126 // Kafel komentarzy: podpis zależny od liczby (liczbÄ kopiuje lustrujBadge). 127 function podpisKomentarzy() {
128 const n = Number(String(($("#tileKomentarze") || {}).textContent || "0").replace(/\D/g, "")) || 0; 129 przyrost("#tileKomentarzeZm", n ? "CzekajÄ na przeczytanie" : "â Brak nowych", 0); 130 } 131 132 function renderRatujemyRail(lista) { 133 const box = $("#admRatujemyLista"); 134 if (!box) return; 135 // Pokazujemy te wymagajÄ ce uwagi (nie uratowane), najnowsze na górze. 136 const doUratowania = lista.filter((x) => x.stan !== "uratowane"); 137 const ile = $("#admRatujemyIle"); 138 if (ile) { 139 ile.textContent = String(doUratowania.length); 140 ile.hidden = !doUratowania.length; 141 } 142 const doPokazania = [...doUratowania] 143 .sort((a, b) => String(b.utworzono || "").localeCompare(String(a.utworzono || ""))) 144 .slice(0, 4); 145 if (!doPokazania.length) { 146 box.innerHTML = '<p class="adm-mini-pusto">Brak projektów do uratowania.</p>'; 147 return; 148 } 149 const data = (s) => { 150 const m = String(s || "").match(/^(\d{4})-(\d{2})-(\d{2})/); 151 return m ? `${m[3]}.${m[2]}.${m[1]}` : ""; 152 }; 153 box.innerHTML = doPokazania.map((p) => ` 154 <div class="adm-mini-item"> 155 ${p.obraz ? `<img class="adm-mini-cov" src="${esc(p.obraz)}" alt="" loading="lazy" onerror="this.style.visibility='hidden'">` : '<span class="adm-mini-cov"></span>'} 156 <span class="adm-mini-txt"><b>${esc(p.tytul)}</b><small>${data(p.utworzono) ? `Dodano: ${data(p.utworzono)}` : `⤠${p.glosy || 0} · ð¬ ${p.komentarze_liczba || 0}`}</small></span> 157 </div>`).join(""); 158 } 159
160 /* ---- lustro odznak: sidebar -> kafel komentarzy + szybkie akcje ---- */ 161 function lustrujBadge(zrodloId, ...celeId) { 162 const zrodlo = document.getElementById(zrodloId); 163 if (!zrodlo) return; 164 const kopiuj = () => { 165 const txt = zrodlo.hidden ? "" : zrodlo.textContent; 166 celeId.forEach((id) => { 167 const c = document.getElementById(id); 168 if (!c) return; 169 if (c.classList.contains("adm-badge") || c.classList.contains("badge-count")) { 170 c.textContent = txt || "0"; 171 c.hidden = zrodlo.hidden; 172 } else { 173 c.textContent = txt || "0"; // kafel liczbowy 174 } 175 }); 176 podpisKomentarzy(); 177 }; 178 kopiuj(); 179 new MutationObserver(kopiuj).observe(zrodlo, { 180 childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ["hidden"], 181 }); 182 } 183 184 /* ---- dzwonek w nagÅówku: suma odznak z menu bocznego ---- */ 185 const ODZNAKI = [ 186 ["komentarzeBadge", "komentarzeButton", "Nowe komentarze"], 187 ["zgloszeniaBadge", "zgloszeniaButton", "ZgÅoszenia"], 188 ["wiadomosciBadge", "wiadomosciButton", "WiadomoÅci"], 189 ["propozycjeBadge", "propozycjeButton", "Propozycje do sprawdzenia"], 190 ["ratujemyBadge", "ratujemyButton", "Ratujemy spolszczenia"], 191 ["czatBadge", "czatButton", "Czat na żywo"], 192 ["forumBadge", "forumButton", "Forum"], 193 ["modyBadge", "modyButton", "Mody"], 194 ["blokadyBadge", "blokadyButton", "Moderacja"], 195 ["ekipaBadge", "ekipaButton", "Ekipa"], 196 ]; 197 const dzwonek = $("#admDzwonek"); 198 const dzwonekMenu = $("#admDzwonekMenu"); 199 const kropka = $("#admDzwonekKropka"); 200 function odswiezDzwonek() { 201 if (!dzwonek || !dzwonekMenu) return; 202 const aktywne = ODZNAKI.map(([b, cel, nazwa]) => { 203 const e = document.getElementById(b); 204 const n = e && !e.hidden ? Number(String(e.textContent).replace(/\D/g, "")) || 0 : 0; 205 return { cel, nazwa, n }; 206 }).filter((x) => x.n > 0); 207 if (kropka) kropka.hidden = !aktywne.length; 208 dzwonekMenu.innerHTML = aktywne.length 209 ? aktywne.map((x) => `<button type="button" data-cel="${esc(x.cel)}"><span>${esc(x.nazwa)}</span><b>${x.n}</b></button>`).join("") 210 : '<p class="adm-mini-pusto">Wszystko przeczytane.</p>'; 211 } 212 if (dzwonek && dzwonekMenu) { 213 dzwonek.addEventListener("click", (e) => { 214 e.stopPropagation(); 215 odswiezDzwonek(); 216 dzwonekMenu.hidden = !dzwonekMenu.hidden; 217 dzwonek.setAttribute("aria-expanded", dzwonekMenu.hidden ? "false" : "true"); 218 }); 219 dzwonekMenu.addEventListener("click", (e) => { 220 const b = e.target.closest("[data-cel]"); 221 if (!b) return; 222 dzwonekMenu.hidden = true; 223 const cel = document.getElementById(b.dataset.cel); 224 if (cel) cel.click(); 225 }); 226 document.addEventListener("click", (e) => { 227 if (!dzwonekMenu.hidden && !e.target.closest(".adm-dzwonek-wrap")) dzwonekMenu.hidden = true; 228 }); 229 ODZNAKI.forEach(([b]) => { 230 const e = document.getElementById(b); 231 if (e) new MutationObserver(odswiezDzwonek).observe(e, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ["hidden"] }); 232 }); 233 } 234 235 /* ---- menu boczne: aktywna pozycja ---- */ 236 function ustawAktywna(btn) { 237 document.querySelectorAll(".adm-nav-item").forEach((b) => b.classList.remove("is-active")); 238 if (btn) btn.classList.add("is-active"); 239 } 240 document.querySelectorAll(".adm-nav-item").forEach((btn) => { 241 btn.addEventListener("click", () => { 242 ustawAktywna(btn); 243 shell.classList.remove("side-open"); // zamknij menu na mobile po wyborze 244 }); 245 }); 246 247 // Statystyki / Projekty = ta sama strona (przewiÅ na górÄ albo do tabeli). 248 const navDash = document.getElementById("navDashboard"); 249 if (navDash) navDash.addEventListener("click", () => window.scrollTo({ top: 0, behavior: "smooth" })); 250 const navProj = document.getElementById("navProjekty"); 251 if (navProj) navProj.addEventListener("click", () => { 252 const cel = document.querySelector(".adm-main-col"); 253 if (cel) cel.scrollIntoView({ behavior: "smooth", block: "start" }); 254 }); 255 256 /* ---- hamburger (mobile) ---- */ 257 const burger = $("#admBurger"); 258 if (burger) burger.addEventListener("click", () => shell.classList.toggle("side-open")); 259 260 /* ---- szybkie akcje -> klik w istniejÄ ce przyciski ---- */ 261 const QUICK = { 262 dodaj: "dodajButton", 263 aktualnosc: "aktualnosciButton", 264 zgloszenia: "zgloszeniaButton", 265 komentarze: "komentarzeButton", 266 ratujemy: "ratujemyButton", 267 statystyki: "statystykiButton", 268 }; 269 document.querySelectorAll("[data-quick]").forEach((b) => { 270 b.addEventListener("click", () => { 271 const cel = document.getElementById(QUICK[b.dataset.quick]); 272 if (cel) cel.click(); 273 }); 274 }); 275 276 /* ---- start: wypeÅnij, gdy panel staje siÄ widoczny po zalogowaniu ---- */ 277 let odpalone = false;
278 function odpal() { 279 if (odpalone || shell.hidden) return; 280 odpalone = true; 281 lustrujBadge("komentarzeBadge", "tileKomentarze", "quickKomentarzeBadge"); 282 lustrujBadge("zgloszeniaBadge", "quickZgloszeniaBadge"); 283 wypelnijKafle(); 284 odswiezDzwonek(); 285 } 286 if (!shell.hidden) odpal(); 287 new MutationObserver(odpal).observe(shell, { attributes: true, attributeFilter: ["hidden"] }); 288})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.