diff --git a/src/islands/jambox/JamboxChannelsSearch.jsx b/src/islands/jambox/JamboxChannelsSearch.jsx index bf068bc..a3a7df2 100644 --- a/src/islands/jambox/JamboxChannelsSearch.jsx +++ b/src/islands/jambox/JamboxChannelsSearch.jsx @@ -7,6 +7,9 @@ export default function JamboxChannelsSearch() { const [loading, setLoading] = useState(false); const [err, setErr] = useState(""); + // ✅ koszyk kanałów + const [wanted, setWanted] = useState([]); // [{ name, logo_url, packages:[{id,name}] }] + const abortRef = useRef(null); useEffect(() => { @@ -31,13 +34,10 @@ export default function JamboxChannelsSearch() { params.set("q", qq); params.set("limit", "80"); - const res = await fetch( - `/api/jambox/jambox-channels-search?${params.toString()}`, - { - signal: ac.signal, - headers: { Accept: "application/json" }, - } - ); + const res = await fetch(`/api/jambox/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"); @@ -59,31 +59,182 @@ export default function JamboxChannelsSearch() { const meta = useMemo(() => { const qq = q.trim(); if (qq.length === 0) return ""; - // "Zacznij pisać, aby wyszukać" if (loading) return "Szukam…"; if (err) return err; return `Znaleziono: ${items.length}`; }, [q, loading, err, items]); -function scrollToPackage(packageName) { - const key = String(packageName || "").trim(); - if (!key) return; + function scrollToPackage(packageName) { + const key = String(packageName || "").trim(); + if (!key) return; - const el = document.getElementById(`pkg-${key}`); - if (!el) { - console.warn("❌ Nie znaleziono pakietu w DOM:", `pkg-${key}`); - return; + const el = document.getElementById(`pkg-${key}`); + if (!el) { + console.warn("❌ Nie znaleziono pakietu w DOM:", `pkg-${key}`); + return; + } + + el.scrollIntoView({ behavior: "smooth", block: "start" }); + el.classList.add("is-target"); + window.setTimeout(() => el.classList.remove("is-target"), 5400); } - el.scrollIntoView({ behavior: "smooth", block: "start" }); - el.classList.add("is-target"); - window.setTimeout(() => el.classList.remove("is-target"), 5400); -} + // ========================== + // ✅ koszyk: dodaj/usuń kanał + // ========================== + const isWanted = (c) => + wanted.some((w) => String(w.name || "").toLowerCase() === String(c.name || "").toLowerCase()); + + function addWanted(c) { + setWanted((prev) => { + const exists = prev.some( + (w) => String(w.name || "").toLowerCase() === String(c.name || "").toLowerCase() + ); + if (exists) return prev; + + return [ + ...prev, + { + name: c.name, + logo_url: c.logo_url || "", + packages: Array.isArray(c.packages) ? c.packages : [], + }, + ]; + }); + } + + function removeWantedByName(name) { + setWanted((prev) => + prev.filter((w) => String(w.name || "").toLowerCase() !== String(name || "").toLowerCase()) + ); + } + + function clearWanted() { + setWanted([]); + } + + // ========================================= + // ✅ pakiety, które zawierają WSZYSTKIE kanały + // ========================================= + const packageSuggestions = useMemo(() => { + if (!wanted.length) return { exact: [], ranked: [] }; + + // mapa pakietu -> { id,name,count } + const counts = new Map(); // key = packageName + for (const ch of wanted) { + const pkgs = Array.isArray(ch.packages) ? ch.packages : []; + for (const p of pkgs) { + const name = String(p?.name ?? "").trim(); + if (!name) continue; + const cur = counts.get(name) || { id: p?.id ?? name, name, count: 0 }; + cur.count += 1; + counts.set(name, cur); + } + } + + const all = Array.from(counts.values()); + + // ✅ exact: pakiety z count == wanted.length (czyli zawierają wszystkie) + const exact = all + .filter((p) => p.count === wanted.length) + .sort((a, b) => a.name.localeCompare(b.name, "pl")); + + // ✅ ranked: najlepsze dopasowania gdy exact puste (malejąco count) + const ranked = all + .filter((p) => p.count < wanted.length) + .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name, "pl")) + .slice(0, 12); + + return { exact, ranked }; + }, [wanted]); return (