Porządkowanie kodu, dodanie sekcji wyszukiwania kanałów

This commit is contained in:
dm
2025-12-12 19:48:53 +01:00
parent bf67147cf5
commit 5822237745
47 changed files with 17203 additions and 15686 deletions

View File

@@ -0,0 +1,137 @@
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import "../../styles/channels-search.css";
export default function JamboxChannelsSearch() {
const [q, setQ] = useState("");
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [err, setErr] = useState("");
const abortRef = useRef(null);
useEffect(() => {
const qq = q.trim();
setErr("");
if (qq.length < 2) {
setItems([]);
setLoading(false);
return;
}
const t = setTimeout(async () => {
try {
if (abortRef.current) abortRef.current.abort();
const ac = new AbortController();
abortRef.current = ac;
setLoading(true);
const params = new URLSearchParams();
params.set("q", qq);
params.set("limit", "80");
const res = await fetch(`/api/jambox/channels-search?${params.toString()}`, {
signal: ac.signal,
headers: { Accept: "application/json" },
});
const json = await res.json();
if (!res.ok || !json.ok) throw new Error(json?.error || "API_ERROR");
setItems(Array.isArray(json.data) ? json.data : []);
} catch (e) {
if (e?.name !== "AbortError") {
console.error("❌ channels search:", e);
setErr("Błąd wyszukiwania.");
}
} finally {
setLoading(false);
}
}, 250);
return () => clearTimeout(t);
}, [q]);
const meta = useMemo(() => {
const qq = q.trim();
if (qq.length < 2) return "Wpisz min. 2 znaki";
if (loading) return "Szukam…";
if (err) return err;
return `Znaleziono: ${items.length}`;
}, [q, loading, err, items]);
return (
<div class="fuz-chsearch">
<h1 class="f-section-title">Wyszukiwanie kanałów w pakietach telewizji</h1>
<div class="fuz-chsearch__top">
<input
class="fuz-chsearch__input"
type="search"
value={q}
onInput={(e) => setQ(e.currentTarget.value)}
placeholder="Szukaj kanału po nazwie…"
aria-label="Szukaj kanału po nazwie"
/>
<div class="fuz-chsearch__meta">
{meta}
</div>
</div>
<div class="fuz-chsearch__list" role="list">
{items.map((c) => (
<div class="fuz-chsearch__row" role="listitem" key={`${c.name}-${c.logo_url || ""}`}>
{/* kolumna 1 */}
<div class="fuz-chsearch__left">
{c.logo_url && (
<img
src={c.logo_url}
alt={c.name}
class="fuz-chsearch__logo"
loading="lazy"
/>
)}
<div class="fuz-chsearch__channel-name">
{c.name}
</div>
<div class="fuz-chsearch__channel-number">
kanał {c.min_number || "—"}
</div>
</div>
{/* kolumna 2 */}
<div class="fuz-chsearch__right">
<div
class="fuz-chsearch__desc fuz-chsearch__desc--html"
dangerouslySetInnerHTML={{ __html: c.description || "<em>—</em>" }}
/>
{Array.isArray(c.packages) && c.packages.length > 0 && (
<div class="fuz-chsearch__packages">
Dostępny w:&nbsp;
{c.packages.map((p, i) => (
<span class="fuz-chsearch__pkg" key={p.id}>
{p.name}{" "}
<span class="fuz-chsearch__pkgnum">({p.number})</span>
{i < c.packages.length - 1 ? ", " : ""}
</span>
))}
</div>
)}
</div>
</div>
))}
{q.trim().length >= 2 && !loading && items.length === 0 && (
<div class="fuz-chsearch__empty">
Brak wyników dla: <strong>{q}</strong>
</div>
)}
</div>
</div>
);
}