Formatowanie cen

This commit is contained in:
dm
2025-12-19 08:13:59 +01:00
parent 2920dc7c08
commit 97668743b2
6 changed files with 47 additions and 64 deletions

View File

@@ -3,8 +3,6 @@ opis: |
Wybierz rodzaj budynku i czas trwania umowy Wybierz rodzaj budynku i czas trwania umowy
cena_opis: "zł/mies." cena_opis: "zł/mies."
grupy: grupy:
- id: "EVIO" - id: "EVIO"
nazwa: "Evio" nazwa: "Evio"

View File

@@ -16,7 +16,12 @@ grupy:
canal_plus: canal_plus:
tytul: CANAL+ tytul: CANAL+
rejestracja: rejestracja:
opis: Aby skorzystać z Platformy należy się zarejestrować opis: |
Platforma CANAL+ jest dostępna za pośrednictwem strony internetowej, podłączonych telewizorów, aplikacji na smartfony i tablety, konsol do gier i urządzeń takich jak Apple TV i Google Chromecast.
Wszystkie obsługiwane urządzenia opisane są na stronie: canalplus.com
Aby skorzystać z Platformy należy się zarejestrować
label: Rejestracja CANAL+ label: Rejestracja CANAL+
href: "https://jambox.pl/cplus-rejestracja" href: "https://jambox.pl/cplus-rejestracja"
title: "Rejestracja w usłudze streamingowej" title: "Rejestracja w usłudze streamingowej"
@@ -63,9 +68,6 @@ dodatki:
opis: | opis: |
Pakiet sportowy Canal+ oraz dostęp do platformy streamingowej Canal+ Pakiet sportowy Canal+ oraz dostęp do platformy streamingowej Canal+
Platforma CANAL+ jest dostępna za pośrednictwem strony internetowej, podłączonych telewizorów, aplikacji na smartfony i tablety, konsol do gier i urządzeń takich jak Apple TV i Google Chromecast.
Wszystkie obsługiwane urządzenia opisane są na stronie: canalplus.com
cena: cena:
- pakiety: - pakiety:
- Smart - Smart

View File

@@ -4,21 +4,10 @@ import OffersSwitches from "../Switches.jsx";
import InternetAddonsModal from "./InternetAddonsModal.jsx"; import InternetAddonsModal from "./InternetAddonsModal.jsx";
import "../../styles/cards.css"; import "../../styles/cards.css";
function formatMoney(amount, currency = "PLN") { import { moneyWithLabel, money } from "../../lib/money.js";
if (typeof amount !== "number" || Number.isNaN(amount)) return "";
try {
return new Intl.NumberFormat("pl-PL", {
style: "",
currency,
maximumFractionDigits: 0,
}).format(amount);
} catch {
return String(amount);
}
}
// ✅ mapper: InternetCard(YAML) + match + labels -> plan (dla modala) // ✅ mapper: InternetCard(YAML) + match + labels -> plan (dla modala)
function mapCardToPlan(card, match, labels, waluta) { function mapCardToPlan(card, match, labels, cenaOpis) {
const baseParams = Array.isArray(card?.parametry) ? card.parametry : []; const baseParams = Array.isArray(card?.parametry) ? card.parametry : [];
const features = baseParams.map((p) => ({ const features = baseParams.map((p) => ({
@@ -30,8 +19,7 @@ function mapCardToPlan(card, match, labels, waluta) {
features.push({ label: "Umowa", value: labels?.umowa || "—" }); features.push({ label: "Umowa", value: labels?.umowa || "—" });
features.push({ features.push({
label: "Aktywacja", label: "Aktywacja",
value: value: typeof match?.aktywacja === "number" ? `${money(match.aktywacja)}` : "—",
typeof match?.aktywacja === "number" ? formatMoney(match.aktywacja, waluta) : "—",
}); });
return { return {
@@ -39,6 +27,7 @@ function mapCardToPlan(card, match, labels, waluta) {
price_monthly: typeof match?.miesiecznie === "number" ? match.miesiecznie : 0, price_monthly: typeof match?.miesiecznie === "number" ? match.miesiecznie : 0,
price_installation: typeof match?.aktywacja === "number" ? match.aktywacja : 0, price_installation: typeof match?.aktywacja === "number" ? match.aktywacja : 0,
features, features,
cenaOpis,
}; };
} }
@@ -52,23 +41,23 @@ function mapCardToPlan(card, match, labels, waluta) {
* phoneCards?: any[], * phoneCards?: any[],
* addons?: any[], * addons?: any[],
* addonsCenaOpis?: string, * addonsCenaOpis?: string,
* switches?: any[] // ✅ NOWE: przełączniki z YAML * switches?: any[]
* }} props * }} props
*/ */
export default function InternetCards({ export default function InternetCards({
title = "", title = "",
description = "", description = "",
cards = [], cards = [],
waluta = "PLN", waluta = "PLN", // zostawiamy, bo może się przydać dalej (np. w modalu), ale tu nie jest używana
cenaOpis = "zł/mies.", cenaOpis = "zł/mies.",
phoneCards = [], phoneCards = [],
addons = [], addons = [],
addonsCenaOpis = "zł/mies.", addonsCenaOpis = "zł/mies.",
switches = [], // ✅ NOWE switches = [],
}) { }) {
const visibleCards = Array.isArray(cards) ? cards : []; const visibleCards = Array.isArray(cards) ? cards : [];
// switch state (teraz idzie z OffersSwitches na podstawie YAML) // switch state (idzie z OffersSwitches na podstawie YAML)
const [selected, setSelected] = useState({}); const [selected, setSelected] = useState({});
const [labels, setLabels] = useState({}); const [labels, setLabels] = useState({});
@@ -103,7 +92,6 @@ export default function InternetCards({
</div> </div>
)} )}
{/* ✅ TERAZ switcher dostaje dane z YAML */}
<OffersSwitches switches={switches} /> <OffersSwitches switches={switches} />
{visibleCards.length === 0 ? ( {visibleCards.length === 0 ? (
@@ -116,7 +104,6 @@ export default function InternetCards({
card={card} card={card}
selected={selected} selected={selected}
labels={labels} labels={labels}
waluta={waluta}
cenaOpis={cenaOpis} cenaOpis={cenaOpis}
onConfigureAddons={(plan) => { onConfigureAddons={(plan) => {
setActivePlan(plan); setActivePlan(plan);
@@ -139,7 +126,7 @@ export default function InternetCards({
); );
} }
function OfferCard({ card, selected, labels, waluta, cenaOpis, onConfigureAddons }) { function OfferCard({ card, selected, labels, cenaOpis, onConfigureAddons }) {
const baseParams = Array.isArray(card?.parametry) ? card.parametry : []; const baseParams = Array.isArray(card?.parametry) ? card.parametry : [];
const ceny = Array.isArray(card?.ceny) ? card.ceny : []; const ceny = Array.isArray(card?.ceny) ? card.ceny : [];
@@ -160,7 +147,7 @@ function OfferCard({ card, selected, labels, waluta, cenaOpis, onConfigureAddons
{ {
klucz: "aktywacja", klucz: "aktywacja",
label: "Aktywacja", label: "Aktywacja",
value: typeof akt === "number" ? formatMoney(akt, waluta) : "—", value: typeof akt === "number" ? `${money(akt)}` : "—",
}, },
]; ];
@@ -175,9 +162,7 @@ function OfferCard({ card, selected, labels, waluta, cenaOpis, onConfigureAddons
<div class="f-card-price"> <div class="f-card-price">
{typeof mies === "number" ? ( {typeof mies === "number" ? (
<> <>{moneyWithLabel(mies, cenaOpis, false)}</>
{formatMoney(mies, waluta)} <span class="opacity-80">{cenaOpis}</span>
</>
) : ( ) : (
<span class="opacity-70">Wybierz opcje</span> <span class="opacity-70">Wybierz opcje</span>
)} )}
@@ -198,7 +183,7 @@ function OfferCard({ card, selected, labels, waluta, cenaOpis, onConfigureAddons
class="btn btn-primary mt-4" class="btn btn-primary mt-4"
disabled={!canConfigureAddons} disabled={!canConfigureAddons}
onClick={() => { onClick={() => {
const plan = mapCardToPlan(card, match, labels, waluta); const plan = mapCardToPlan(card, match, labels, cenaOpis);
onConfigureAddons(plan); onConfigureAddons(plan);
}} }}
title={!canConfigureAddons ? "Wybierz typ budynku i umowę" : ""} title={!canConfigureAddons ? "Wybierz typ budynku i umowę" : ""}

View File

@@ -6,18 +6,7 @@ import JamboxChannelsModal from "./JamboxChannelsModal.jsx";
import JamboxAddonsModal from "./JamboxAddonsModal.jsx"; import JamboxAddonsModal from "./JamboxAddonsModal.jsx";
import Markdown from "../Markdown.jsx"; import Markdown from "../Markdown.jsx";
function formatMoney(amount, currency = "PLN") { import { moneyWithLabel, money } from "../../lib/money.js";
if (typeof amount !== "number" || Number.isNaN(amount)) return "";
try {
return new Intl.NumberFormat("pl-PL", {
style: "currency",
currency,
maximumFractionDigits: 0,
}).format(amount);
} catch {
return String(amount);
}
}
function toFeatureRows(params) { function toFeatureRows(params) {
const list = Array.isArray(params) ? params : []; const list = Array.isArray(params) ? params : [];
@@ -52,7 +41,6 @@ function toFeatureRows(params) {
* decoders?: Decoder[], * decoders?: Decoder[],
* addonsCenaOpis?: string, * addonsCenaOpis?: string,
* channels?: ChannelYaml[], * channels?: ChannelYaml[],
* channels?: ChannelYaml[]
* }} props * }} props
*/ */
export default function JamboxCards({ export default function JamboxCards({
@@ -60,7 +48,7 @@ export default function JamboxCards({
description = "", description = "",
cards = [], cards = [],
internetWspolne = [], internetWspolne = [],
waluta = "PLN", waluta = "PLN", // zostawiamy (może być potrzebne w modalach), ale tu nie jest używane do formatowania
cenaOpis = "zł/mies.", cenaOpis = "zł/mies.",
phoneCards = [], phoneCards = [],
@@ -122,7 +110,6 @@ export default function JamboxCards({
wsp={wsp} wsp={wsp}
selected={selected} selected={selected}
labels={labels} labels={labels}
waluta={waluta}
cenaOpis={cenaOpis} cenaOpis={cenaOpis}
onShowChannels={(pkg) => { onShowChannels={(pkg) => {
setActivePkg(pkg); setActivePkg(pkg);
@@ -163,7 +150,6 @@ function JamboxPackageCard({
wsp, wsp,
selected, selected,
labels, labels,
waluta,
cenaOpis, cenaOpis,
onShowChannels, onShowChannels,
onConfigureAddons, onConfigureAddons,
@@ -186,7 +172,7 @@ function JamboxPackageCard({
{ {
klucz: "aktywacja", klucz: "aktywacja",
label: "Aktywacja", label: "Aktywacja",
value: typeof installPrice === "number" ? formatMoney(installPrice, waluta) : "—", value: typeof installPrice === "number" ? `${money(installPrice)}` : "—",
}, },
]; ];
@@ -212,9 +198,7 @@ function JamboxPackageCard({
<div class="f-card-price"> <div class="f-card-price">
{hasPrice ? ( {hasPrice ? (
<> <>{moneyWithLabel(basePrice, cenaOpis, false)}</>
{formatMoney(basePrice, waluta)} <span class="opacity-80">{cenaOpis}</span>
</>
) : ( ) : (
<span class="opacity-70">Wybierz opcje</span> <span class="opacity-70">Wybierz opcje</span>
)} )}

View File

@@ -1,4 +1,5 @@
import Markdown from "../../islands/Markdown.jsx"; import Markdown from "../../islands/Markdown.jsx";
import { moneyWithLabel } from "../../lib/money.js";
import "../../styles/cards.css"; import "../../styles/cards.css";
/** /**
@@ -25,9 +26,13 @@ export default function PhoneDbOffersCards({
return ( return (
<section class="f-offers"> <section class="f-offers">
{title && <h2 class="f-section-header">{title}</h2>} {title && <h2 class="f-section-header">{title}</h2>}
<div>
{description && (
<div class="mb-4">
<Markdown text={description} /> <Markdown text={description} />
</div> </div>
)}
{visibleCards.length === 0 ? ( {visibleCards.length === 0 ? (
<p class="opacity-80">Brak dostępnych pakietów.</p> <p class="opacity-80">Brak dostępnych pakietów.</p>
) : ( ) : (
@@ -42,8 +47,8 @@ export default function PhoneDbOffersCards({
} }
function PhoneOfferCard({ card }) { function PhoneOfferCard({ card }) {
const price = card?.cena?.wartosc ?? ""; const priceValue = card?.cena?.wartosc;
const priceDesc = card?.cena?.opis ?? "zł/mies."; const priceLabel = card?.cena?.opis || "zł/mies.";
const params = Array.isArray(card?.parametry) ? card.parametry : []; const params = Array.isArray(card?.parametry) ? card.parametry : [];
@@ -53,8 +58,11 @@ function PhoneOfferCard({ card }) {
<div class="f-card-header"> <div class="f-card-header">
<div class="f-card-name">{card.nazwa}</div> <div class="f-card-name">{card.nazwa}</div>
<div class="f-card-price"> <div class="f-card-price">
{price} {priceDesc} {typeof priceValue === "number"
? moneyWithLabel(priceValue, priceLabel, false)
: "—"}
</div> </div>
</div> </div>

View File

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