Rezygnacja z bazy, przeniesienie danych do plików yamla
This commit is contained in:
@@ -1,16 +1,75 @@
|
||||
//InternetCards.jsx
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import Markdown from "../Markdown.jsx";
|
||||
import OffersSwitches from "../OffersSwitches.jsx";
|
||||
import InternetAddonsModal from "./InternetAddonsModal.jsx";
|
||||
import "../../styles/offers/offers-table.css";
|
||||
|
||||
export default function InternetDbOffersCards({
|
||||
function formatMoney(amount, currency = "PLN") {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ mapper: InternetCard(YAML) + match + labels -> plan (dla modala)
|
||||
function mapCardToPlan(card, match, labels, waluta) {
|
||||
const baseParams = Array.isArray(card?.parametry) ? card.parametry : [];
|
||||
|
||||
const features = baseParams.map((p) => ({
|
||||
label: p.label,
|
||||
value: p.value,
|
||||
}));
|
||||
|
||||
// na końcu jak parametry:
|
||||
features.push({ label: "Umowa", value: labels?.umowa || "—" });
|
||||
features.push({
|
||||
label: "Aktywacja",
|
||||
value: typeof match?.aktywacja === "number" ? formatMoney(match.aktywacja, waluta) : "—",
|
||||
});
|
||||
|
||||
return {
|
||||
name: card?.nazwa || "—",
|
||||
price_monthly: typeof match?.miesiecznie === "number" ? match.miesiecznie : 0,
|
||||
price_installation: typeof match?.aktywacja === "number" ? match.aktywacja : 0,
|
||||
features,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* title?: string,
|
||||
* description?: string,
|
||||
* cards?: any[],
|
||||
* waluta?: string,
|
||||
* cenaOpis?: string,
|
||||
* phoneCards?: any[],
|
||||
* addons?: any[],
|
||||
* addonsCenaOpis?: string
|
||||
* }} props
|
||||
*/
|
||||
export default function InternetCards({
|
||||
title = "",
|
||||
description = "",
|
||||
cards = [],
|
||||
waluta = "PLN",
|
||||
cenaOpis = "zł/mies.",
|
||||
phoneCards = [],
|
||||
addons = [],
|
||||
addonsCenaOpis = "zł/mies.",
|
||||
}) {
|
||||
const visibleCards = Array.isArray(cards) ? cards : [];
|
||||
|
||||
// switch state (z /api/switches)
|
||||
const [selected, setSelected] = useState({});
|
||||
const [labels, setLabels] = useState({});
|
||||
const [plans, setPlans] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
// modal
|
||||
const [addonsModalOpen, setAddonsModalOpen] = useState(false);
|
||||
const [activePlan, setActivePlan] = useState(null);
|
||||
|
||||
@@ -22,75 +81,40 @@ export default function InternetDbOffersCards({
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
const detail = e.detail || {};
|
||||
if (detail.selected) {
|
||||
setSelected(detail.selected);
|
||||
}
|
||||
if (detail.labels) {
|
||||
setLabels(detail.labels);
|
||||
}
|
||||
const detail = e?.detail || {};
|
||||
if (detail.selected) setSelected(detail.selected);
|
||||
if (detail.labels) setLabels(detail.labels);
|
||||
}
|
||||
|
||||
window.addEventListener("fuz:switch-change", handler);
|
||||
return () => window.removeEventListener("fuz:switch-change", handler);
|
||||
}, []);
|
||||
|
||||
|
||||
const buildingCode = Number(selected.budynek) || 1;
|
||||
const contractCode = Number(selected.umowa) || 1;
|
||||
|
||||
useEffect(() => {
|
||||
if (!buildingCode || !contractCode) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
building: String(buildingCode),
|
||||
contract: String(contractCode),
|
||||
});
|
||||
|
||||
const res = await fetch(`/api/internet/plans?${params.toString()}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
|
||||
const json = await res.json();
|
||||
if (!cancelled) {
|
||||
setPlans(Array.isArray(json.data) ? json.data : []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Błąd pobierania planów internetu:", err);
|
||||
if (!cancelled) setError("Nie udało się załadować ofert.");
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [buildingCode, contractCode]);
|
||||
|
||||
const contractLabel = labels.umowa || "";
|
||||
|
||||
return (
|
||||
<section class="f-offers">
|
||||
{title && <h1 class="f-section-header">{title}</h1>}
|
||||
|
||||
{loading && <p>Ładowanie ofert...</p>}
|
||||
{error && <p class="text-red-600">{error}</p>}
|
||||
{description && (
|
||||
<div class="mb-4">
|
||||
<Markdown text={description} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<div class={`f-offers-grid f-count-${plans.length || 1}`}>
|
||||
{plans.map((plan) => (
|
||||
<OffersSwitches />
|
||||
|
||||
{visibleCards.length === 0 ? (
|
||||
<p class="opacity-80">Brak dostępnych pakietów.</p>
|
||||
) : (
|
||||
<div class={`f-offers-grid f-count-${visibleCards.length || 1}`}>
|
||||
{visibleCards.map((card) => (
|
||||
<OfferCard
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
contractLabel={contractLabel}
|
||||
onConfigureAddons={() => {
|
||||
key={card.nazwa}
|
||||
card={card}
|
||||
selected={selected}
|
||||
labels={labels}
|
||||
waluta={waluta}
|
||||
cenaOpis={cenaOpis}
|
||||
onConfigureAddons={(plan) => {
|
||||
setActivePlan(plan);
|
||||
setAddonsModalOpen(true);
|
||||
}}
|
||||
@@ -103,62 +127,77 @@ export default function InternetDbOffersCards({
|
||||
isOpen={addonsModalOpen}
|
||||
onClose={() => setAddonsModalOpen(false)}
|
||||
plan={activePlan}
|
||||
phoneCards={phoneCards}
|
||||
addons={addons}
|
||||
cenaOpis={addonsCenaOpis || cenaOpis}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function OfferCard({ plan, contractLabel, onConfigureAddons }) {
|
||||
const basePrice = plan.price_monthly;
|
||||
const installPrice = plan.price_installation;
|
||||
function OfferCard({ card, selected, labels, waluta, cenaOpis, onConfigureAddons }) {
|
||||
const baseParams = Array.isArray(card?.parametry) ? card.parametry : [];
|
||||
const ceny = Array.isArray(card?.ceny) ? card.ceny : [];
|
||||
|
||||
const featureRows = plan.features || [];
|
||||
const budynek = selected?.budynek;
|
||||
const umowa = selected?.umowa;
|
||||
|
||||
const effectiveContract = contractLabel || "—";
|
||||
const match = ceny.find(
|
||||
(c) => String(c?.budynek) === String(budynek) && String(c?.umowa) === String(umowa),
|
||||
);
|
||||
|
||||
const mies = match?.miesiecznie;
|
||||
const akt = match?.aktywacja;
|
||||
|
||||
// na końcu jako parametry
|
||||
const params = [
|
||||
...baseParams,
|
||||
{ klucz: "umowa", label: "Umowa", value: labels?.umowa || "—" },
|
||||
{
|
||||
klucz: "aktywacja",
|
||||
label: "Aktywacja",
|
||||
value: typeof akt === "number" ? formatMoney(akt, waluta) : "—",
|
||||
},
|
||||
];
|
||||
|
||||
const canConfigureAddons = !!match;
|
||||
|
||||
return (
|
||||
<div class={`f-card ${plan.popular ? "f-card-popular" : ""}`}>
|
||||
<div class={`f-card ${card.popularny ? "f-card-popular" : ""}`}>
|
||||
{card.popularny && <div class="f-card-badge">Najczęściej wybierany</div>}
|
||||
|
||||
<div class="f-card-header">
|
||||
<div class="f-card-name">{plan.name}</div>
|
||||
<div class="f-card-name">{card.nazwa}</div>
|
||||
|
||||
<div class="f-card-price">
|
||||
{basePrice != null ? `${basePrice} zł/mies.` : "—"}
|
||||
{typeof mies === "number" ? (
|
||||
<>
|
||||
{formatMoney(mies, waluta)} <span class="opacity-80">{cenaOpis}</span>
|
||||
</>
|
||||
) : (
|
||||
<span class="opacity-70">Wybierz opcje</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="f-card-features">
|
||||
{featureRows.map((f) => {
|
||||
let val = f.value;
|
||||
let display;
|
||||
|
||||
if (val === true || val === "true") display = "✓";
|
||||
else if (val === false || val === "false" || val == null) display = "✕";
|
||||
else display = val;
|
||||
|
||||
return (
|
||||
<li class="f-card-row">
|
||||
<span class="f-card-label">{f.label}</span>
|
||||
<span class="f-card-value">{display}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
<li class="f-card-row">
|
||||
<span class="f-card-label">Umowa</span>
|
||||
<span class="f-card-value">{effectiveContract}</span>
|
||||
</li>
|
||||
|
||||
<li class="f-card-row">
|
||||
<span class="f-card-label">Aktywacja</span>
|
||||
<span class="f-card-value">
|
||||
{installPrice != null ? `${installPrice} zł` : "—"}
|
||||
</span>
|
||||
</li>
|
||||
{params.map((p) => (
|
||||
<li class="f-card-row" key={p.klucz || p.label}>
|
||||
<span class="f-card-label">{p.label}</span>
|
||||
<span class="f-card-value">{p.value}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary mt-4"
|
||||
onClick={onConfigureAddons}
|
||||
disabled={!canConfigureAddons}
|
||||
onClick={() => {
|
||||
const plan = mapCardToPlan(card, match, labels, waluta);
|
||||
onConfigureAddons(plan);
|
||||
}}
|
||||
title={!canConfigureAddons ? "Wybierz typ budynku i umowę" : ""}
|
||||
>
|
||||
Skonfiguruj usługi dodatkowe
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user