Files
laitica.pagedev.pl/script.js
2026-02-16 23:05:31 +01:00

491 lines
13 KiB
JavaScript

const stepCount = 6;
const catalogs = {
systems: {
mono_230: { name: "System 1-fazowy 230V", base: 159, meter: 89 },
tri_230: { name: "System 3-fazowy 230V", base: 249, meter: 129 },
magnetic_48: { name: "System magnetyczny 48V", base: 399, meter: 179 }
},
layouts: {
line: { name: "Układ liniowy", surcharge: 0 },
l_shape: { name: "Układ L", surcharge: 59 },
u_shape: { name: "Układ U", surcharge: 99 },
rectangle: { name: "Układ prostokątny", surcharge: 149 }
},
colors: {
black: { name: "Czarny mat", surcharge: 0, line: "#252627" },
white: { name: "Biały mat", surcharge: 0, line: "#f7f6f8" },
champagne: { name: "Champagne", surcharge: 40, line: "#d7bd8b" }
},
temperatures: {
"3000": "3000K ciepła",
"4000": "4000K neutralna",
cct: "CCT regulowana"
}
};
const state = {
step: 1,
system: null,
layout: null,
trackLength: 4,
roomHeight: 2.8,
trackColor: null,
temperature: null,
fixtures: {},
accessories: new Set()
};
const fixtureGrid = document.getElementById("fixtureGrid");
const accessoryList = document.getElementById("accessoryList");
const summaryList = document.getElementById("summaryList");
const summaryTotal = document.getElementById("summaryTotal");
const finalSummary = document.getElementById("finalSummary");
const previewCanvas = document.getElementById("previewCanvas");
const trackLengthInput = document.getElementById("trackLength");
const trackLengthOut = document.getElementById("trackLengthOut");
const roomHeightInput = document.getElementById("roomHeight");
const roomHeightOut = document.getElementById("roomHeightOut");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const toast = document.getElementById("toast");
const fixtures = buildFixtures();
const accessories = buildAccessories();
init();
function init() {
renderFixtures();
renderAccessories();
bindBaseEvents();
syncInputs();
updateUI();
}
function buildFixtures() {
const names = [
"Reflektor Punto",
"Spot Slim Tube",
"Lampa liniowa Aero",
"Reflektor Move Pro",
"Tubus Mono Flex",
"Panel Linear Edge",
"Oprawa Twist Mini"
];
return names.slice(0, 6).map((name, i) => ({
id: `fx_${i + 1}`,
code: `LT-FX-${1200 + i}`,
name,
price: randomInt(85, 279)
}));
}
function buildAccessories() {
const names = [
"Zasilacz końcowy",
"Łącznik prosty",
"Łącznik narożny L",
"Maskownica zasilania",
"Pilot CCT",
"Zawiesie sufitowe 1 m"
];
return names.map((name, i) => ({
id: `acc_${i + 1}`,
name,
price: randomInt(24, 119)
}));
}
function bindBaseEvents() {
document.addEventListener("change", (event) => {
const target = event.target;
if (target.name === "system") {
state.system = target.value;
}
if (target.name === "layout") {
state.layout = target.value;
}
if (target.name === "trackColor") {
state.trackColor = target.value;
}
if (target.name === "temperature") {
state.temperature = target.value;
}
if (target.matches("[data-accessory]")) {
if (target.checked) {
state.accessories.add(target.value);
} else {
state.accessories.delete(target.value);
}
}
updateUI();
});
fixtureGrid.addEventListener("click", (event) => {
const button = event.target.closest("button[data-fixture-id]");
if (!button) {
return;
}
const id = button.dataset.fixtureId;
const action = button.dataset.action;
const currentQty = state.fixtures[id] || 0;
const nextQty = action === "inc" ? currentQty + 1 : Math.max(0, currentQty - 1);
state.fixtures[id] = nextQty;
updateUI();
});
trackLengthInput.addEventListener("input", () => {
state.trackLength = Number(trackLengthInput.value);
syncInputs();
updateUI();
});
roomHeightInput.addEventListener("input", () => {
const clamped = Math.min(4.5, Math.max(2.2, Number(roomHeightInput.value || 2.8)));
state.roomHeight = Number(clamped.toFixed(1));
syncInputs();
});
prevBtn.addEventListener("click", () => {
if (state.step > 1) {
state.step -= 1;
updateUI();
}
});
nextBtn.addEventListener("click", () => {
if (state.step < stepCount) {
const valid = validateCurrentStep();
if (!valid.ok) {
showToast(valid.message);
return;
}
state.step += 1;
updateUI();
return;
}
showToast("Makieta: zestaw został przykładowo dodany do koszyka.");
});
document.querySelectorAll("[data-goto-step]").forEach((button) => {
button.addEventListener("click", () => {
const goto = Number(button.dataset.gotoStep);
if (goto <= state.step || goto === state.step + 1) {
if (goto > state.step) {
const valid = validateCurrentStep();
if (!valid.ok) {
showToast(valid.message);
return;
}
}
state.step = goto;
updateUI();
}
});
});
}
function renderFixtures() {
fixtureGrid.innerHTML = fixtures
.map(
(fixture) => `
<article class="product-card">
<div class="product-code">${fixture.code}</div>
<div class="product-name">${fixture.name}</div>
<div class="product-meta">
<div class="price">${formatPLN(fixture.price)}</div>
<div class="qty">
<button type="button" data-fixture-id="${fixture.id}" data-action="dec">-</button>
<span>${state.fixtures[fixture.id] || 0}</span>
<button type="button" data-fixture-id="${fixture.id}" data-action="inc">+</button>
</div>
</div>
</article>
`
)
.join("");
}
function renderAccessories() {
accessoryList.innerHTML = accessories
.map(
(item) => `
<article class="accessory-item">
<label>
<input type="checkbox" data-accessory value="${item.id}">
<span>${item.name}</span>
</label>
<strong>${formatPLN(item.price)}</strong>
</article>
`
)
.join("");
}
function updateUI() {
updateStepPanels();
updateStepButtons();
updateActions();
renderFixtures();
refreshAccessoryChecks();
renderSummary();
renderFinalSummary();
renderPreview();
}
function updateStepPanels() {
document.querySelectorAll(".step-panel").forEach((panel) => {
panel.classList.toggle("is-active", Number(panel.dataset.step) === state.step);
});
}
function updateStepButtons() {
document.querySelectorAll(".step-item").forEach((button) => {
const step = Number(button.dataset.gotoStep);
button.classList.toggle("is-active", step === state.step);
button.classList.toggle("is-done", step < state.step);
});
}
function updateActions() {
prevBtn.disabled = state.step === 1;
nextBtn.textContent = state.step === stepCount ? "Dodaj do koszyka" : "Dalej";
}
function renderSummary() {
const breakdown = getBreakdown();
summaryList.innerHTML = breakdown.items
.map((item) => `<li><span>${item.label}</span><strong>${formatPLN(item.value)}</strong></li>`)
.join("");
summaryTotal.textContent = formatPLN(breakdown.total);
}
function renderFinalSummary() {
const breakdown = getBreakdown();
finalSummary.innerHTML = `
<ul>
${breakdown.items
.map((item) => `<li><span>${item.label}</span><strong>${formatPLN(item.value)}</strong></li>`)
.join("")}
<li><span><strong>Razem brutto</strong></span><strong>${formatPLN(breakdown.total)}</strong></li>
</ul>
`;
}
function refreshAccessoryChecks() {
document.querySelectorAll("[data-accessory]").forEach((input) => {
input.checked = state.accessories.has(input.value);
});
}
function getBreakdown() {
const items = [];
let total = 0;
if (state.system) {
const system = catalogs.systems[state.system];
const value = system.base;
items.push({ label: system.name, value });
total += value;
}
if (state.layout) {
const layout = catalogs.layouts[state.layout];
if (layout.surcharge > 0) {
items.push({ label: layout.name, value: layout.surcharge });
total += layout.surcharge;
}
}
if (state.system) {
const system = catalogs.systems[state.system];
const trackValue = Math.round(state.trackLength * system.meter);
items.push({ label: `Szyny ${state.trackLength.toFixed(1)} m`, value: trackValue });
total += trackValue;
}
if (state.trackColor) {
const color = catalogs.colors[state.trackColor];
if (color.surcharge > 0) {
items.push({ label: `Wykończenie ${color.name}`, value: color.surcharge });
total += color.surcharge;
}
}
fixtures.forEach((fixture) => {
const qty = state.fixtures[fixture.id] || 0;
if (qty > 0) {
const value = qty * fixture.price;
items.push({ label: `${fixture.name} x${qty}`, value });
total += value;
}
});
accessories.forEach((acc) => {
if (state.accessories.has(acc.id)) {
items.push({ label: acc.name, value: acc.price });
total += acc.price;
}
});
if (items.length === 0) {
items.push({ label: "Brak wybranych pozycji", value: 0 });
}
return { items, total };
}
function renderPreview() {
const colorToken = state.trackColor ? catalogs.colors[state.trackColor].line : "#f7f6f8";
const spotCount = Math.max(1, Object.values(state.fixtures).reduce((sum, qty) => sum + qty, 0));
const points = getPreviewPoints(state.layout || "line");
const circles = Array.from({ length: spotCount }, (_, i) => {
const point = points[i % points.length];
const tone = getPreviewLightTone(state.temperature, i);
return `<circle cx="${point.x}" cy="${point.y}" r="5.2" fill="${tone.fill}" stroke="${tone.stroke}" stroke-width="2" />`;
}).join("");
const lines = getPreviewPath(state.layout || "line", colorToken);
previewCanvas.innerHTML = `
<svg viewBox="0 0 360 170" width="100%" height="170" aria-hidden="true">
${lines}
${circles}
</svg>
`;
}
function getPreviewPath(layout, color) {
const stroke = `<g stroke="${color}" stroke-width="9" fill="none" stroke-linecap="round" stroke-linejoin="round">`;
if (layout === "l_shape") {
return `${stroke}<path d="M48 38 L48 130 L280 130" /></g>`;
}
if (layout === "u_shape") {
return `${stroke}<path d="M45 38 L45 130 L300 130 L300 38" /></g>`;
}
if (layout === "rectangle") {
return `${stroke}<path d="M60 40 L300 40 L300 130 L60 130 Z" /></g>`;
}
return `${stroke}<path d="M45 86 L315 86" /></g>`;
}
function getPreviewPoints(layout) {
if (layout === "l_shape") {
return [
{ x: 48, y: 54 },
{ x: 48, y: 82 },
{ x: 48, y: 112 },
{ x: 98, y: 130 },
{ x: 150, y: 130 },
{ x: 205, y: 130 },
{ x: 260, y: 130 }
];
}
if (layout === "u_shape") {
return [
{ x: 45, y: 55 },
{ x: 45, y: 100 },
{ x: 95, y: 130 },
{ x: 145, y: 130 },
{ x: 195, y: 130 },
{ x: 245, y: 130 },
{ x: 300, y: 102 },
{ x: 300, y: 58 }
];
}
if (layout === "rectangle") {
return [
{ x: 76, y: 40 },
{ x: 128, y: 40 },
{ x: 196, y: 40 },
{ x: 262, y: 40 },
{ x: 300, y: 76 },
{ x: 300, y: 114 },
{ x: 250, y: 130 },
{ x: 175, y: 130 },
{ x: 100, y: 130 },
{ x: 60, y: 96 }
];
}
return [
{ x: 64, y: 86 },
{ x: 104, y: 86 },
{ x: 146, y: 86 },
{ x: 188, y: 86 },
{ x: 230, y: 86 },
{ x: 272, y: 86 },
{ x: 312, y: 86 }
];
}
function getPreviewLightTone(temperature, index) {
if (temperature === "3000") {
return { fill: "#ffb766", stroke: "#ffd4a2" };
}
if (temperature === "4000") {
return { fill: "#fff3d2", stroke: "#fff9ea" };
}
if (temperature === "cct") {
return index % 2 === 0
? { fill: "#ffbc77", stroke: "#ffe0b5" }
: { fill: "#c8e8ff", stroke: "#e9f6ff" };
}
return { fill: "#f7614d", stroke: "#ffd2cc" };
}
function validateCurrentStep() {
if (state.step === 1 && !state.system) {
return { ok: false, message: "Wybierz system szynowy, aby przejść dalej." };
}
if (state.step === 2 && !state.layout) {
return { ok: false, message: "Wybierz kształt konfiguracji." };
}
if (state.step === 3 && (!state.trackColor || !state.temperature)) {
return { ok: false, message: "Uzupełnij kolor szyny i temperaturę barwową." };
}
return { ok: true };
}
function syncInputs() {
trackLengthOut.textContent = state.trackLength.toFixed(1);
roomHeightInput.value = state.roomHeight.toFixed(1);
roomHeightOut.textContent = state.roomHeight.toFixed(1);
}
function showToast(text) {
toast.textContent = text;
toast.classList.add("is-visible");
clearTimeout(showToast.timer);
showToast.timer = setTimeout(() => {
toast.classList.remove("is-visible");
}, 2100);
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function formatPLN(value) {
return `${value.toLocaleString("pl-PL")}`;
}