feat(13-protection-packages): Pakiety ochronne SOFT/PREMIUM z panelu WP

- Panel admina (wp-admin > Rezerwacje > Pakiety ochronne) do zarzadzania
  nazwami, cenami za dobe, aktywnoscia i opisami pakietow SOFT i PREMIUM
  (zapis w wp_options carei_protection_packages)
- REST endpoint GET /carei/v1/protection-packages zwracajacy aktywne pakiety
- Radio cards SOFT/PREMIUM w modalu rezerwacji nad pozycjami "Pakiety ochronne"
  z API (osobne zrodlo danych, separator wizualny)
- Radio z deselect (klik zaznaczonego odznacza), natywny input z accent-color
- Pakiet NIE wysylany w priceItems Softra (powodowalo HTTP 400) - zamiast tego
  doklejany do comments booking i zapisywany w _carei_protection_package meta
- Summary frontend dokorysowuje wiersz pakietu w tabeli cen i dolicza do
  total gross (grandGross = softraGross + protectionTotal)
- Plan 13-01 oznaczony jako superseded (klient zmienil zrodlo danych)
- Phase 13 Complete, Milestone v0.5 Complete

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 00:45:24 +02:00
parent 9e4f47de25
commit 42efe93cdd
13 changed files with 987 additions and 25 deletions

View File

@@ -597,6 +597,89 @@ button.carei-reservation-trigger:hover {
color: inherit;
}
/* ═══════════════════════════════════════════
Protection Packages (SOFT / PREMIUM — zarządzane w panelu WP)
═══════════════════════════════════════════ */
.carei-form__row--protection-packages {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.carei-form__protection-package {
display: flex;
flex-direction: column;
gap: 6px;
padding: 16px;
border: 1px solid var(--carei-border);
border-radius: var(--carei-radius);
background: var(--carei-white);
cursor: pointer;
transition: border-color 0.2s;
min-width: 0;
overflow: hidden;
}
.carei-form__protection-package:hover {
border-color: rgba(47, 36, 130, 0.4);
}
.carei-form__protection-package.is-selected {
border-color: var(--carei-blue);
}
.carei-form__protection-package__row {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.carei-form__protection-package__input {
flex-shrink: 0;
width: 16px;
height: 16px;
accent-color: var(--carei-blue);
cursor: pointer;
margin: 0;
}
.carei-form__protection-package__name {
font-weight: 600;
font-size: 15px;
color: var(--carei-blue);
flex: 1;
min-width: 0;
word-break: break-word;
}
.carei-form__protection-package__price {
font-weight: 400;
font-size: 14px;
color: #505050;
margin-left: auto;
white-space: nowrap;
flex-shrink: 0;
}
.carei-form__protection-package__price strong {
color: inherit;
}
.carei-form__protection-package__desc {
font-size: 12px;
color: #666;
line-height: 1.4;
}
.carei-form__protection-package__desc:empty {
display: none;
}
.carei-form__protection-divider {
margin: 14px 0;
border-top: 1px dashed rgba(47, 36, 130, 0.18);
height: 0;
}
.carei-form__row--protection-packages:empty + .carei-form__protection-divider {
display: none;
}
@media (max-width: 640px) {
.carei-form__row--protection-packages {
grid-template-columns: 1fr;
}
}
.carei-form__checkbox-label--abroad {
align-items: center;
font-weight: 600;

View File

@@ -83,6 +83,9 @@
var summaryOverlay, summaryDetails, summaryTable, summaryTotal, summaryError;
var summaryBack, summaryConfirm;
var successView, successNumber, successClose;
var protectionContainer;
var protectionPackages = { soft: null, premium: null };
var selectedProtectionKey = null;
function initRefs() {
overlay = document.querySelector('[data-carei-modal]');
@@ -102,6 +105,7 @@
extrasWrapper = document.getElementById('carei-extras-wrapper');
extrasContainer = document.getElementById('carei-extras-container');
insuranceContainer = document.getElementById('carei-insurance-container');
protectionContainer = document.getElementById('carei-protection-packages-container');
abroadSection = document.getElementById('carei-abroad-section');
abroadToggle = document.getElementById('carei-abroad-toggle');
abroadSearch = document.getElementById('carei-abroad-search');
@@ -419,6 +423,69 @@
daysCount.innerHTML = 'Wybrano: <strong>' + diff + ' ' + (diff === 1 ? 'dzień' : 'dni') + '</strong>';
}
// ─── Protection Packages (WP-managed: SOFT, PREMIUM) ──────────
function loadProtectionPackages() {
return fetch(REST_URL + 'protection-packages', {
credentials: 'same-origin'
}).then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
if (!data || typeof data !== 'object') return;
protectionPackages.soft = data.soft || null;
protectionPackages.premium = data.premium || null;
renderProtectionPackages();
}).catch(function (err) { console.error('Failed to load protection packages:', err); });
}
function renderProtectionPackages() {
if (!protectionContainer) return;
protectionContainer.innerHTML = '';
['soft', 'premium'].forEach(function (key) {
var pkg = protectionPackages[key];
if (!pkg) return;
var price = parseFloat(pkg.pricePerDay || 0);
var priceLabel = price > 0 ? price.toFixed(0) + ' zł/doba' : 'Gratis';
var descHtml = pkg.description ? '<span class="carei-form__protection-package__desc">' + escHtml(pkg.description) + '</span>' : '';
var card = document.createElement('label');
card.className = 'carei-form__protection-package';
card.setAttribute('data-key', key);
card.setAttribute('data-price', price);
card.innerHTML =
'<span class="carei-form__protection-package__row">' +
'<input type="radio" name="protectionPackage" value="' + escAttr(key) + '" class="carei-form__protection-package__input">' +
'<span class="carei-form__protection-package__name">' + escHtml(pkg.name || '') + '</span>' +
'<span class="carei-form__protection-package__price"><strong>' + escHtml(priceLabel) + '</strong></span>' +
'</span>' +
descHtml;
card.addEventListener('click', function (e) {
e.preventDefault();
onProtectionCardClick(key);
});
protectionContainer.appendChild(card);
});
}
function onProtectionCardClick(key) {
if (!protectionContainer) return;
if (selectedProtectionKey === key) {
selectedProtectionKey = null;
} else {
selectedProtectionKey = key;
}
var cards = protectionContainer.querySelectorAll('.carei-form__protection-package');
cards.forEach(function (card) {
var k = card.getAttribute('data-key');
var input = card.querySelector('input');
if (k === selectedProtectionKey) {
card.classList.add('is-selected');
if (input) input.checked = true;
} else {
card.classList.remove('is-selected');
if (input) input.checked = false;
}
});
}
// ─── Load Extras from Pricelist ───────────────────────────────
function loadExtras() {
@@ -875,9 +942,33 @@
priceAfterDiscount: price
});
});
// UWAGA: pakiet ochronny (SOFT/PREMIUM) zarządzany w panelu WP
// NIE jest dołączany do priceItems — Softra nie zna tych ID (zwróciłaby HTTP 400).
// Jest liczony osobno w getSelectedProtectionPayload() i dodawany do podsumowania po stronie frontu.
return items;
}
function getSelectedProtectionPayload() {
if (!selectedProtectionKey || !protectionPackages[selectedProtectionKey]) return null;
var pkg = protectionPackages[selectedProtectionKey];
var pricePerDay = parseFloat(pkg.pricePerDay || 0);
var days = getRentalDays();
return {
key: selectedProtectionKey,
name: pkg.name,
pricePerDay: pricePerDay,
days: days,
total: pricePerDay * days
};
}
function buildBookingComments(userMessage) {
var pkg = getSelectedProtectionPayload();
if (!pkg) return userMessage || '';
var pkgLine = 'Pakiet ochronny: ' + pkg.name + ' — ' + pkg.pricePerDay.toFixed(2) + ' zł/doba × ' + pkg.days + ' = ' + pkg.total.toFixed(2) + ' zł (do doliczenia poza systemem)';
return userMessage ? (pkgLine + '\n\n' + userMessage) : pkgLine;
}
// ─── Step Transitions ──────────────────────────────────────────
function hideStep(el) {
@@ -961,7 +1052,8 @@
// Selected extras
var selectedExtras = getSelectedExtrasForApi();
if (selectedExtras.length > 0) {
var pkgForDetails = getSelectedProtectionPayload();
if (selectedExtras.length > 0 || pkgForDetails) {
html += '<div style="margin-top:8px"><strong>Wybrane opcje:</strong></div><ul style="margin:4px 0 0 16px;padding:0;list-style:disc;">';
selectedExtras.forEach(function (ex) {
var totalPrice = ex.priceAfterDiscount * (ex.amount || 1);
@@ -970,6 +1062,12 @@
: fmtPrice(totalPrice) + ' zł';
html += '<li>' + escHtml(toSentenceCase(ex.name)) + ' — ' + priceInfo + '</li>';
});
if (pkgForDetails) {
var pkgInfo = pkgForDetails.days > 1
? fmtPrice(pkgForDetails.pricePerDay) + ' zł/doba × ' + pkgForDetails.days + ' = ' + fmtPrice(pkgForDetails.total) + ' zł'
: fmtPrice(pkgForDetails.total) + ' zł';
html += '<li>' + escHtml(pkgForDetails.name) + ' — ' + pkgInfo + '</li>';
}
html += '</ul>';
}
@@ -980,6 +1078,8 @@
summaryDetails.innerHTML = html;
}
var protectionPayload = getSelectedProtectionPayload();
// Price table
if (summaryTable && summary.pricelist) {
var html = '<table><thead><tr><th>Nazwa</th><th>Ilość</th><th>Netto</th><th>Brutto</th></tr></thead><tbody>';
@@ -991,16 +1091,30 @@
'<td>' + fmtPrice(item.netValue) + '</td>' +
'<td>' + fmtPrice(item.grossValue) + '</td></tr>';
});
if (protectionPayload) {
html += '<tr class="carei-summary__protection-row">' +
'<td>' + escHtml(protectionPayload.name) + ' <small>(do doliczenia)</small></td>' +
'<td>' + protectionPayload.days + ' doba</td>' +
'<td>—</td>' +
'<td>' + fmtPrice(protectionPayload.total) + '</td></tr>';
}
html += '</tbody></table>';
summaryTable.innerHTML = html;
}
// Totals
if (summaryTotal) {
summaryTotal.innerHTML =
var softraGross = parseFloat(summary.totalGrossValue || 0);
var protectionTotal = protectionPayload ? protectionPayload.total : 0;
var grandGross = softraGross + protectionTotal;
var totalsHtml =
'<div class="carei-summary__total-row"><span class="carei-summary__total-label">Netto:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalNetValue) + '</span></div>' +
'<div class="carei-summary__total-row"><span class="carei-summary__total-label">VAT:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalVatValue) + '</span></div>' +
'<div class="carei-summary__total-row carei-summary__total-row--gross"><span class="carei-summary__total-label">Do zapłaty:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalGrossValue) + ' zł</span></div>';
'<div class="carei-summary__total-row"><span class="carei-summary__total-label">VAT:</span><span class="carei-summary__total-value">' + fmtPrice(summary.totalVatValue) + '</span></div>';
if (protectionPayload) {
totalsHtml += '<div class="carei-summary__total-row"><span class="carei-summary__total-label">Pakiet ochronny:</span><span class="carei-summary__total-value">' + fmtPrice(protectionPayload.total) + '</span></div>';
}
totalsHtml += '<div class="carei-summary__total-row carei-summary__total-row--gross"><span class="carei-summary__total-label">Do zapłaty:</span><span class="carei-summary__total-value">' + fmtPrice(grandGross) + ' zł</span></div>';
summaryTotal.innerHTML = totalsHtml;
}
}
@@ -1047,7 +1161,8 @@
phone: fd.phone,
email: fd.email
}],
comments: fd.message || ''
comments: buildBookingComments(fd.message || ''),
protectionPackage: getSelectedProtectionPayload()
};
// Add agreement items
@@ -1344,6 +1459,7 @@
initAbroad();
initSubmit();
initMap();
loadProtectionPackages();
}
/* ═══════════════════════════════════════════