167 lines
4.6 KiB
JavaScript
167 lines
4.6 KiB
JavaScript
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]);
|
|
|
|
function scrollToPackage(packageId) {
|
|
const el = document.getElementById(`pkg-${packageId}`);
|
|
if (!el) return;
|
|
|
|
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
|
el.classList.add("is-target");
|
|
window.setTimeout(() => el.classList.remove("is-target"), 1200);
|
|
}
|
|
|
|
return (
|
|
<div class="f-chsearch">
|
|
<h1 class="f-section-title">Wyszukiwanie kanałów w pakietach telewizji</h1>
|
|
|
|
<div class="f-chsearch__top">
|
|
<div class="f-chsearch__inputwrap">
|
|
<input
|
|
class="f-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"
|
|
/>
|
|
|
|
{q && (
|
|
<button
|
|
type="button"
|
|
class="f-chsearch__clear"
|
|
aria-label="Wyczyść wyszukiwanie"
|
|
onClick={() => setQ("")}
|
|
>
|
|
✕
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div class="f-chsearch__meta">{meta}</div>
|
|
</div>
|
|
|
|
<div class="f-chsearch__list" role="list">
|
|
{items.map((c) => (
|
|
<div
|
|
class="f-chsearch__row"
|
|
role="listitem"
|
|
key={`${c.name}-${c.logo_url || ""}`}
|
|
>
|
|
{/* kolumna 1 */}
|
|
<div class="f-chsearch__left">
|
|
{c.logo_url && (
|
|
<img
|
|
src={c.logo_url}
|
|
alt={c.name}
|
|
class="f-chsearch__logo"
|
|
loading="lazy"
|
|
/>
|
|
)}
|
|
|
|
<div class="f-chsearch__channel-name">{c.name}</div>
|
|
</div>
|
|
|
|
{/* kolumna 2 */}
|
|
<div class="f-chsearch__right">
|
|
<div
|
|
class="f-chsearch__desc f-chsearch__desc--html"
|
|
dangerouslySetInnerHTML={{
|
|
__html: c.description || "<em>—</em>",
|
|
}}
|
|
/>
|
|
|
|
{Array.isArray(c.packages) && c.packages.length > 0 && (
|
|
<div class="f-chsearch__packages">
|
|
Dostępny w:
|
|
{c.packages.map((p, i) => (
|
|
<button
|
|
type="button"
|
|
class="f-chsearch__pkg"
|
|
key={p.id}
|
|
onClick={() => scrollToPackage(p.id)}
|
|
>
|
|
{p.name}{" "}
|
|
<span class="f-chsearch__pkgnum">(kanał {p.number})</span>
|
|
{i < c.packages.length - 1 ? ", " : ""}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{q.trim().length >= 2 && !loading && items.length === 0 && (
|
|
<div class="f-chsearch__empty">
|
|
Brak wyników dla: <strong>{q}</strong>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|