Modal usług doddatkowych podział na komponenty

This commit is contained in:
dm
2025-12-17 10:23:09 +01:00
parent 5c2f329174
commit 345fa23f84
16 changed files with 1266 additions and 1524 deletions

8
src/lib/money.js Normal file
View File

@@ -0,0 +1,8 @@
export function money(amount) {
const n = Number(amount || 0);
return n.toFixed(2).replace(".", ",");
}
export function moneyWithLabel(v, cenaOpis) {
return `${money(v)} ${cenaOpis}`;
}

View File

@@ -0,0 +1,47 @@
/** telefon z YAML (phone/cards.yaml -> cards[]) => { id, name, price_monthly, features[] } */
export 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,
})),
}));
}
/** dekodery z YAML */
export function normalizeDecoders(list) {
const arr = Array.isArray(list) ? list : [];
return arr
.filter((d) => d?.id && d?.nazwa)
.map((d) => ({
id: String(d.id),
nazwa: String(d.nazwa),
opis: d.opis ? String(d.opis) : "",
cena: Number(d.cena ?? 0),
}));
}
/** dodatki z YAML (tv-addons.yaml / addons.yaml) */
export 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: a.cena ?? 0,
tid: a.tid != null ? String(a.tid) : "",
}));
}

57
src/lib/offer-payload.js Normal file
View File

@@ -0,0 +1,57 @@
import { moneyWithLabel } from "./money.js";
export const LS_KEY = "fuz_offer_config_v1";
export function buildOfferMessage(payload, cenaOpis) {
const m = (v) => moneyWithLabel(v, cenaOpis);
const lines = [];
lines.push(`Wybrana oferta: ${payload?.pkg?.name || "—"}`);
lines.push("");
const t = payload?.totals || {};
lines.push(`Pakiet: ${m(t.base ?? 0)}`);
lines.push(`Telefon: ${payload?.phone ? m(t.phone ?? 0) : "—"}`);
if ("decoder" in t) lines.push(`Dekoder: ${payload?.decoder ? m(t.decoder ?? 0) : "—"}`);
if ("tv" in t) lines.push(`Dodatki TV: ${payload?.tvAddons?.length ? m(t.tv ?? 0) : "—"}`);
lines.push(`Dodatkowe usługi: ${payload?.addons?.length ? m(t.addons ?? 0) : "—"}`);
lines.push(`Łącznie: ${m(t.total ?? 0)}`);
if (payload?.phone) {
lines.push("");
lines.push(`Telefon: ${payload.phone.name} (${m(payload.phone.price)})`);
}
if (payload?.decoder) {
lines.push("");
lines.push(`Dekoder: ${payload.decoder.name} (${m(payload.decoder.price)})`);
}
if (Array.isArray(payload?.tvAddons) && payload.tvAddons.length) {
lines.push("");
lines.push("Pakiety dodatkowe TV:");
for (const it of payload.tvAddons) {
const termTxt = it.term ? `, ${it.term}` : "";
lines.push(`- ${it.nazwa} x${it.qty}${termTxt} @ ${m(it.unit)}`);
}
}
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} @ ${m(it.unit)}`);
}
}
return lines.join("\n");
}
export function saveOfferToLocalStorage(payload, cenaOpis) {
try {
payload.message = buildOfferMessage(payload, cenaOpis);
localStorage.setItem(LS_KEY, JSON.stringify(payload));
} catch {}
}

83
src/lib/offer-pricing.js Normal file
View File

@@ -0,0 +1,83 @@
function normKey(s) {
return String(s || "").trim().toLowerCase().replace(/\s+/g, " ");
}
/** TV: wybór wariantu ceny po pkg.name, albo fallback "*" */
export function pickTvVariant(addon, pkgName) {
const c = addon?.cena;
if (!Array.isArray(c)) return null;
const wanted = normKey(pkgName);
for (const row of c) {
const pk = row?.pakiety;
if (!Array.isArray(pk)) continue;
if (pk.some((p) => normKey(p) === wanted)) return row;
}
for (const row of c) {
const pk = row?.pakiety;
if (!Array.isArray(pk)) continue;
if (pk.some((p) => String(p).trim() === "*")) return row;
}
return null;
}
export function isTvAddonAvailableForPkg(addon, pkg) {
if (!pkg) return false;
const v = pickTvVariant(addon, String(pkg?.name ?? ""));
return !!v;
}
export function hasTvTermPricing(addon, pkg) {
const c = addon?.cena;
if (!Array.isArray(c)) return false;
const v = pickTvVariant(addon, String(pkg?.name ?? ""));
if (!v || typeof v !== "object") return false;
return v["12m"] != null && v.bezterminowo != null;
}
/**
* ✅ cena jednostkowa:
* - addons.yaml: number / string / legacy {default, by_name}
* - tv-addons.yaml: tablica wariantów
*/
export function getAddonUnitPrice(addon, pkg, term /* "12m"|"bezterminowo"|null */) {
const c = addon?.cena;
if (typeof c === "number") return c;
if (typeof c === "string" && c.trim() !== "" && !Number.isNaN(Number(c))) return Number(c);
// tv-addons.yaml
if (Array.isArray(c)) {
const v = pickTvVariant(addon, String(pkg?.name ?? ""));
if (!v) return 0;
const t = term || "12m";
if (v[t] != null) return Number(v[t]) || 0;
if (v.bezterminowo != null) return Number(v.bezterminowo) || 0;
if (v["12m"] != null) return Number(v["12m"]) || 0;
return 0;
}
// legacy object
if (c && typeof c === "object") {
const name = String(pkg?.name ?? "");
const wanted = normKey(name);
const byName = c.by_name || c.byName || c.by_nazwa || c.byNazwa;
if (byName && typeof byName === "object" && name) {
for (const k of Object.keys(byName)) {
if (normKey(k) === wanted) return Number(byName[k]) || 0;
}
}
if (c.default != null) return Number(c.default) || 0;
}
return 0;
}