Files
fuz-site/src/islands/jambox/JamboxAddonsModal.jsx
2025-12-17 19:00:42 +01:00

272 lines
8.3 KiB
JavaScript

import { useEffect, useMemo, useState } from "preact/hooks";
import OfferModalShell from "../modals/OfferModalShell.jsx";
import PlanSection from "../modals/sections/PlanSection.jsx";
import DecoderSection from "../modals/sections/DecoderSection.jsx";
import TvAddonsSection from "../modals/sections/TvAddonsSection.jsx";
import PhoneSection from "../modals/sections/PhoneSection.jsx";
import AddonsSection from "../modals/sections/AddonsSection.jsx";
import SummarySection from "../modals/sections/SummarySection.jsx";
import FloatingTotal from "../modals/sections/FloatingTotal.jsx";
import { mapPhoneYamlToPlans, normalizeAddons, normalizeDecoders } from "../../lib/offer-normalize.js";
import { isTvAddonAvailableForPkg, hasTvTermPricing, getAddonUnitPrice } from "../../lib/offer-pricing.js";
import { saveOfferToLocalStorage } from "../../lib/offer-payload.js";
import "../../styles/modal.css";
import "../../styles/addons.css";
export default function JamboxAddonsModal({
isOpen,
onClose,
pkg,
phoneCards = [],
tvAddons = [],
addons = [],
decoders = [],
cenaOpis = "zł/mies.",
}) {
const phonePlans = useMemo(() => mapPhoneYamlToPlans(phoneCards), [phoneCards]);
const tvAddonsList = useMemo(() => normalizeAddons(tvAddons), [tvAddons]);
const addonsList = useMemo(() => normalizeAddons(addons), [addons]);
const decodersList = useMemo(() => normalizeDecoders(decoders), [decoders]);
const tvAddonsVisible = useMemo(() => {
if (!pkg) return [];
return tvAddonsList.filter((a) => isTvAddonAvailableForPkg(a, pkg));
}, [tvAddonsList, pkg]);
const [selectedPhoneId, setSelectedPhoneId] = useState(null);
const [selectedDecoderId, setSelectedDecoderId] = useState(null);
const [selectedQty, setSelectedQty] = useState({});
const [tvTerm, setTvTerm] = useState({}); // { [id]: "12m" | "bezterminowo" }
const [openSections, setOpenSections] = useState({
base: true,
decoder: false,
tv: false,
phone: false,
addons: false,
summary: false,
});
const toggleSection = (key) => {
setOpenSections((prev) => {
const nextOpen = !prev[key];
return {
base: false,
decoder: false,
tv: false,
phone: false,
addons: false,
summary: false,
[key]: nextOpen,
};
});
};
useEffect(() => {
if (!isOpen) return;
setSelectedPhoneId(null);
setSelectedQty({});
setTvTerm({});
const d0 =
(Array.isArray(decodersList) && decodersList.find((d) => Number(d.cena) === 0)) ||
(Array.isArray(decodersList) ? decodersList[0] : null);
setSelectedDecoderId(d0 ? String(d0.id) : null);
setOpenSections({
base: true,
decoder: false,
tv: false,
phone: false,
addons: false,
summary: false,
});
}, [isOpen, pkg?.id, decodersList]);
if (!isOpen || !pkg) return null;
const basePrice = Number(pkg.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 decoderPrice = useMemo(() => {
if (!selectedDecoderId) return 0;
const d = decodersList.find((x) => String(x.id) === String(selectedDecoderId));
return Number(d?.cena || 0);
}, [selectedDecoderId, decodersList]);
const tvAddonsPrice = useMemo(() => {
return tvAddonsVisible.reduce((sum, a) => {
const qty = Number(selectedQty[a.id] || 0);
if (qty <= 0) return sum;
const termPricing = hasTvTermPricing(a, pkg);
const term = tvTerm[a.id] || "12m";
const unit = getAddonUnitPrice(a, pkg, termPricing ? term : null);
return sum + qty * unit;
}, 0);
}, [selectedQty, tvAddonsVisible, tvTerm, pkg]);
const addonsOnlyPrice = useMemo(() => {
return addonsList.reduce((sum, a) => {
const qty = Number(selectedQty[a.id] || 0);
const unit = getAddonUnitPrice(a, pkg, null);
return sum + qty * unit;
}, 0);
}, [selectedQty, addonsList, pkg]);
const totalMonthly = basePrice + phonePrice + decoderPrice + tvAddonsPrice + addonsOnlyPrice;
function buildOfferPayload() {
const phone = selectedPhoneId
? phonePlans.find((p) => String(p.id) === String(selectedPhoneId))
: null;
const decoder = selectedDecoderId
? decodersList.find((d) => String(d.id) === String(selectedDecoderId))
: null;
const tvChosen = tvAddonsVisible
.map((a) => {
const qty = Number(selectedQty[a.id] || 0);
if (qty <= 0) return null;
const termPricing = hasTvTermPricing(a, pkg);
const term = tvTerm[a.id] || "12m";
const unit = getAddonUnitPrice(a, pkg, termPricing ? term : null);
return {
id: a.id,
nazwa: a.nazwa,
qty,
term: termPricing ? term : null,
unit,
};
})
.filter(Boolean);
const addonsChosen = addonsList
.map((a) => {
const qty = Number(selectedQty[a.id] || 0);
if (qty <= 0) return null;
const unit = getAddonUnitPrice(a, pkg, null);
return { id: a.id, nazwa: a.nazwa, qty, unit };
})
.filter(Boolean);
return {
createdAt: new Date().toISOString(),
pkg: { id: pkg?.id ?? null, name: pkg?.name ?? "", price: basePrice },
phone: phone ? { id: phone.id, name: phone.name, price: phone.price_monthly } : null,
decoder: decoder ? { id: decoder.id, name: decoder.nazwa, price: decoder.cena } : null,
tvAddons: tvChosen,
addons: addonsChosen,
totals: {
base: basePrice,
phone: phonePrice,
decoder: decoderPrice,
tv: tvAddonsPrice,
addons: addonsOnlyPrice,
total: totalMonthly,
currencyLabel: cenaOpis,
},
};
}
const onSend = () => {
const payload = buildOfferPayload();
saveOfferToLocalStorage(payload, cenaOpis);
};
return (
<OfferModalShell isOpen={isOpen} onClose={onClose} title={`${pkg.name} — konfiguracja usług`}>
<PlanSection
title={pkg.name}
open={openSections.base}
onToggle={() => toggleSection("base")}
price={basePrice}
cenaOpis={cenaOpis}
features={pkg.features || []}
/>
<DecoderSection
open={openSections.decoder}
onToggle={() => toggleSection("decoder")}
cenaOpis={cenaOpis}
decoders={decodersList}
selectedDecoderId={selectedDecoderId}
setSelectedDecoderId={setSelectedDecoderId}
decoderPrice={decoderPrice}
/>
<TvAddonsSection
open={openSections.tv}
onToggle={() => toggleSection("tv")}
cenaOpis={cenaOpis}
pkg={pkg}
tvAddonsVisible={tvAddonsVisible}
selectedQty={selectedQty}
setSelectedQty={setSelectedQty}
tvTerm={tvTerm}
setTvTerm={setTvTerm}
tvAddonsPrice={tvAddonsPrice}
/>
<PhoneSection
open={openSections.phone}
onToggle={() => toggleSection("phone")}
cenaOpis={cenaOpis}
phonePlans={phonePlans}
selectedPhoneId={selectedPhoneId}
setSelectedPhoneId={setSelectedPhoneId}
phonePrice={phonePrice}
/>
<AddonsSection
open={openSections.addons}
onToggle={() => toggleSection("addons")}
cenaOpis={cenaOpis}
addonsList={addonsList}
selectedQty={selectedQty}
setSelectedQty={setSelectedQty}
addonsPrice={addonsOnlyPrice}
getUnitPrice={(a) => getAddonUnitPrice(a, pkg, null)}
/>
<SummarySection
open={openSections.summary}
onToggle={() => toggleSection("summary")}
cenaOpis={cenaOpis}
totalMonthly={totalMonthly}
ctaHref="/kontakt"
onSend={onSend}
rows={[
{ label: "Pakiet", value: basePrice, showDashIfZero: false },
{ label: "Telefon", value: phonePrice, showDashIfZero: true },
{ label: "Dekoder", value: decoderPrice, showDashIfZero: true },
{ label: "Pakiety premium", value: tvAddonsPrice, showDashIfZero: true },
{ label: "Dodatkowe usługi", value: addonsOnlyPrice, showDashIfZero: true },
]}
/>
<FloatingTotal
storageKey="fuz_floating_total_pos_tv_v1"
totalMonthly={totalMonthly}
cenaOpis={cenaOpis}
/>
</OfferModalShell>
);
}