272 lines
8.3 KiB
JavaScript
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>
|
|
);
|
|
}
|