import { useEffect, useMemo, useState } from "preact/hooks";
import "../../styles/modal.css";
import "../../styles/offers-table.css";
function formatFeatureValue(val) {
if (val === true || val === "true") return "✓";
if (val === false || val === "false" || val == null) return "✕";
return val;
}
function money(amount) {
const n = Number(amount || 0);
return n.toFixed(2).replace(".", ",");
}
/**
* Mapuje YAML telefonu (cards.yaml) na format używany w modalu:
* { id, name, price_monthly, features: [{label, value}] }
*/
function mapPhoneYamlToPlans(phoneCards) {
const list = Array.isArray(phoneCards) ? phoneCards : [];
return list
.filter((c) => c?.widoczny !== false)
.map((c, idx) => ({
id: String(c?.id ?? c?.nazwa ?? idx),
name: c?.nazwa ?? "—",
price_monthly: Number(c?.cena?.wartosc ?? 0),
features: (Array.isArray(c?.parametry) ? c.parametry : []).map((p) => ({
label: p.label,
value: p.value,
})),
}));
}
/**
* Dodatki z YAML:
* { id, nazwa, typ, ilosc, min, max, krok, opis, cena }
*/
function normalizeAddons(addons) {
const list = Array.isArray(addons) ? addons : [];
return list
.filter((a) => a?.id && a?.nazwa)
.map((a) => ({
id: String(a.id),
nazwa: String(a.nazwa),
typ: String(a.typ ?? a.type ?? "checkbox"),
ilosc: !!a.ilosc,
min: a.min != null ? Number(a.min) : 0,
max: a.max != null ? Number(a.max) : 10,
krok: a.krok != null ? Number(a.krok) : 1,
opis: a.opis ? String(a.opis) : "",
cena: Number(a.cena ?? 0),
}));
}
/** ✅ Sekcja-akordeon (jak w internet modal) */
function SectionAccordion({ title, right, open, onToggle, children }) {
return (
);
}
export default function InternetAddonsModal({
isOpen,
onClose,
plan,
phoneCards = [],
addons = [],
cenaOpis = "zł/mies.",
}) {
const phonePlans = useMemo(() => mapPhoneYamlToPlans(phoneCards), [phoneCards]);
const addonsList = useMemo(() => normalizeAddons(addons), [addons]);
const [error, setError] = useState("");
const [selectedPhoneId, setSelectedPhoneId] = useState(null);
const [selectedQty, setSelectedQty] = useState({});
// ✅ sekcje jako akordeony (jedna otwarta naraz)
const [openSections, setOpenSections] = useState({
internet: true,
phone: false,
addons: false,
summary: false,
});
const toggleSection = (key) => {
setOpenSections((prev) => {
const nextOpen = !prev[key];
return {
internet: false,
phone: false,
addons: false,
summary: false,
[key]: nextOpen,
};
});
};
// reset wyborów po otwarciu / zmianie planu
useEffect(() => {
if (!isOpen) return;
setError("");
setSelectedPhoneId(null);
setSelectedQty({});
setOpenSections({ internet: true, phone: false, addons: false, summary: false });
}, [isOpen, plan?.id]);
if (!isOpen || !plan) return null;
const basePrice = Number(plan.price_monthly || 0);
const phonePrice = useMemo(() => {
if (!selectedPhoneId) return 0;
const p = phonePlans.find((x) => String(x.id) === String(selectedPhoneId));
return Number(p?.price_monthly || 0);
}, [selectedPhoneId, phonePlans]);
const addonsPrice = useMemo(() => {
return addonsList.reduce((sum, a) => {
const qty = Number(selectedQty[a.id] || 0);
return sum + qty * Number(a.cena || 0);
}, 0);
}, [selectedQty, addonsList]);
const totalMonthly = basePrice + phonePrice + addonsPrice;
const handlePhoneSelect = (id) => {
if (id === null) {
setSelectedPhoneId(null);
return;
}
setSelectedPhoneId(id);
};
const toggleCheckboxAddon = (id) => {
setSelectedQty((prev) => {
const next = { ...prev };
next[id] = (next[id] || 0) > 0 ? 0 : 1;
return next;
});
};
const setQtyAddon = (id, qty, min, max) => {
const safe = Math.max(min, Math.min(max, qty));
setSelectedQty((prev) => ({ ...prev, [id]: safe }));
};
// ======================
// ✅ zapis do localStorage (jak w Jambox)
// ======================
const LS_KEY = "fuz_offer_config_v1";
function buildOfferPayload() {
const phone = selectedPhoneId
? phonePlans.find((p) => String(p.id) === String(selectedPhoneId))
: null;
const addonsChosen = addonsList
.map((a) => {
const qty = Number(selectedQty[a.id] || 0);
if (qty <= 0) return null;
return { id: a.id, nazwa: a.nazwa, qty, unit: Number(a.cena || 0) };
})
.filter(Boolean);
return {
createdAt: new Date().toISOString(),
// ważne: plan internetowy jako "pkg" żeby kontakt miał wspólny format
pkg: { id: plan?.id ?? null, name: plan?.name ?? "", price: basePrice },
phone: phone ? { id: phone.id, name: phone.name, price: phone.price_monthly } : null,
// tu nie ma dekodera i tv-addons
decoder: null,
tvAddons: [],
addons: addonsChosen,
totals: {
base: basePrice,
phone: phonePrice,
decoder: 0,
tv: 0,
addons: addonsPrice,
total: totalMonthly,
currencyLabel: cenaOpis,
},
};
}
function moneyWithLabel(v) {
return `${money(v)} ${cenaOpis}`;
}
function buildOfferMessage(payload) {
const lines = [];
lines.push(`Wybrana oferta: ${payload?.pkg?.name || "—"}`);
lines.push("");
// ✅ WSZYSTKIE linie jak w podsumowaniu (wspólny standard)
lines.push(`Pakiet: ${moneyWithLabel(payload?.totals?.base ?? 0)}`);
lines.push(`Telefon: ${payload?.phone ? moneyWithLabel(payload.totals.phone) : "—"}`);
lines.push(`Dekoder: —`);
lines.push(`Dodatki TV: —`);
lines.push(`Dodatkowe usługi: ${payload?.addons?.length ? moneyWithLabel(payload.totals.addons) : "—"}`);
lines.push(`Łącznie: ${moneyWithLabel(payload?.totals?.total ?? 0)}`);
if (payload?.phone) {
lines.push("");
lines.push(`Telefon: ${payload.phone.name} (${moneyWithLabel(payload.phone.price)})`);
}
if (Array.isArray(payload?.addons) && payload.addons.length) {
lines.push("");
lines.push("Dodatkowe usługi:");
for (const it of payload.addons) {
lines.push(`- ${it.nazwa} x${it.qty} @ ${moneyWithLabel(it.unit)}`);
}
}
return lines.join("\n");
}
function saveOfferToLocalStorage() {
try {
const payload = buildOfferPayload();
payload.message = buildOfferMessage(payload);
localStorage.setItem(LS_KEY, JSON.stringify(payload));
} catch {}
}
// ======================
return (
e.stopPropagation()}>
{plan.name} — konfiguracja usług
{error &&
{error}
}
{/* ✅ INTERNET */}
{money(basePrice)} {cenaOpis}}
open={openSections.internet}
onToggle={() => toggleSection("internet")}
>
{plan.features?.length ? (
{plan.features.map((f, idx) => (
-
{f.label}
{formatFeatureValue(f.value)}
))}
) : (
Brak szczegółów.
)}
{/* ✅ TELEFON — identyczny wygląd jak w TV (f-radio-*) */}
{phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"}
}
open={openSections.phone}
onToggle={() => toggleSection("phone")}
>
{phonePlans.length === 0 ? (
Brak dostępnych pakietów telefonicznych.
) : (
{/* brak telefonu */}
{/* pakiety */}
{phonePlans.map((p) => {
const isSelected = String(selectedPhoneId) === String(p.id);
return (
{/* ✅ detale ZAWSZE widoczne (jak w TV) */}
{p.features?.length > 0 && (
{p.features
.filter(
(f) => !String(f.label || "").toLowerCase().includes("aktyw"),
)
.map((f, idx) => (
-
{f.label}
{formatFeatureValue(f.value)}
))}
)}
);
})}
)}
{/* ✅ DODATKI */}
{addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"}
}
open={openSections.addons}
onToggle={() => toggleSection("addons")}
>
{addonsList.length === 0 ? (
Brak dodatkowych usług.
) : (
{addonsList.map((a) => {
const qty = Number(selectedQty[a.id] || 0);
const isQty = a.typ === "quantity" || a.ilosc === true;
if (!isQty) {
const checked = qty > 0;
return (
);
}
// quantity
const min = Number.isFinite(a.min) ? a.min : 0;
const max = Number.isFinite(a.max) ? a.max : 10;
const step = Number.isFinite(a.krok) ? a.krok : 1;
const lineTotal = qty * Number(a.cena || 0);
return (
{a.nazwa}
{a.opis &&
{a.opis}
}
e.stopPropagation()}>
{qty}
{money(a.cena)} {cenaOpis}
{qty > 0 ? `${money(lineTotal)} ${cenaOpis}` : "—"}
);
})}
)}
{/* ✅ PODSUMOWANIE */}
{money(totalMonthly)} {cenaOpis}}
open={openSections.summary}
onToggle={() => toggleSection("summary")}
>
{/* ✅ WSZYSTKIE linie jak w Jambox (standard) */}
Pakiet
{money(basePrice)} {cenaOpis}
Telefon
{phonePrice ? `${money(phonePrice)} ${cenaOpis}` : "—"}
Dekoder
—
Dodatki TV
—
Dodatkowe usługi
{addonsPrice ? `${money(addonsPrice)} ${cenaOpis}` : "—"}
Łącznie
{money(totalMonthly)} {cenaOpis}
saveOfferToLocalStorage()}
>
Wyślij zapytanie z tym wyborem
e.stopPropagation()}>
Razem
{money(totalMonthly)}
{cenaOpis}
);
}