PageSourceSearch

https://vetspolszczenia.pl/admin/admin-dashboard.js?v=4

js vetspolszczenia.pl collected 2026-10-09 22:05:21 UTC 12,037 bytes, 288 lines download raw bytes

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    ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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.