This commit is contained in:
2026-05-08 00:12:37 +02:00
parent 811069a25c
commit 7278a422af
18 changed files with 1356 additions and 43 deletions

View File

@@ -86,15 +86,30 @@
cursor: default;
}
.yacht-calendar-all .fc-event-title,
.yacht-calendar-all .fc-daygrid-event-dot,
.yacht-calendar-all .fc-event-time {
display: none !important;
}
/* Pasek eventu zawsze wyższy żeby był czytelny bez tekstu */
/* Custom kontener tytułu (renderowany przez eventContent w JS). */
.yacht-calendar-all .yc-event-title {
padding: 1px 6px;
font-size: 11px;
font-weight: 600;
color: #fff;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
/* Pasek eventu wyższy + gap między dziennymi segmentami (rezerwacja wielonocna
= N osobnych pasków zamiast jednej belki — patrz REST split per-day). */
.yacht-calendar-all .fc-daygrid-event {
min-height: 18px;
margin: 1px 2px !important;
border-radius: 2px;
}
/* Half-day visual: gradient wpisywany przez calendar-all.js (eventDidMount), który
@@ -106,6 +121,11 @@
font-size: 10px;
}
.yacht-calendar-all .yc-event-title {
font-size: 10px;
padding: 1px 4px;
}
.yacht-calendar-all .fc-toolbar.fc-header-toolbar {
flex-direction: column;
gap: 8px;

View File

@@ -21,6 +21,14 @@
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate());
}
function firstOfMonth(d) {
return new Date(d.getFullYear(), d.getMonth(), 1);
}
function nextMonthFirst(d) {
return new Date(d.getFullYear(), d.getMonth() + 1, 1);
}
function initCalendar(wrapper) {
var $wrapper = $(wrapper);
var $cal = $wrapper.find('.yacht-calendar-all');
@@ -32,6 +40,31 @@
var heightPx = parseInt($wrapper.data('height'), 10);
if (!heightPx || heightPx < 200) heightPx = 650;
// Bounds: pobierz max_booking_date żeby ograniczyć validRange w FullCalendar.
// Endpoint `/availability/bounds` jest siostrzany do `/availability/all`.
var boundsUrl = restUrl.replace(/\/availability\/all.*$/, '/availability/bounds');
$.getJSON(boundsUrl)
.done(function (data) {
buildCalendar($wrapper, $cal, restUrl, heightPx, data && data.max_booking_date ? data.max_booking_date : null);
})
.fail(function () {
// Graceful degradation — kalendarz bez validRange.
buildCalendar($wrapper, $cal, restUrl, heightPx, null);
});
}
function buildCalendar($wrapper, $cal, restUrl, heightPx, maxBookingDate) {
var today = new Date();
var rangeStart = firstOfMonth(today);
var rangeEnd;
if (maxBookingDate) {
var maxDate = new Date(maxBookingDate + 'T00:00:00');
rangeEnd = maxDate >= today ? nextMonthFirst(maxDate) : nextMonthFirst(today);
} else {
rangeEnd = nextMonthFirst(today);
}
var calendar = new window.FullCalendar.Calendar($cal.get(0), {
initialView: 'dayGridMonth',
locale: 'pl',
@@ -41,6 +74,7 @@
center: 'title',
right: ''
},
validRange: { start: rangeStart, end: rangeEnd },
height: heightPx,
displayEventTime: false,
eventDisplay: 'block',
@@ -63,6 +97,10 @@
var color = info.event.backgroundColor || '#3498db';
info.el.style.setProperty('--yc-event-color', color);
// Native tooltip na hover — tytuł + data dnia.
var dayStr = info.event.startStr ? info.event.startStr.slice(0, 10) : '';
info.el.setAttribute('title', info.event.title + (dayStr ? ' (' + dayStr + ')' : ''));
// Half-day visual: gradient z half-cell transparent na pierwszym/ostatnim dniu.
// Liczone po renderze (wymaga znajomości szerokości komórki dnia w siatce).
// Przekładamy na requestAnimationFrame żeby DOM był ułożony.
@@ -70,9 +108,11 @@
applyHalfDayGradient(info);
});
},
eventContent: function () {
// Pasek koloru bez treści — privacy: nie pokazujemy nazwisk klientów ani nazw jachtów.
return { html: '' };
eventContent: function (arg) {
// Tytuł renderowany przez .text() → automatyczny escaping (XSS safe).
var title = arg.event.title || '';
var $el = $('<div class="yc-event-title"></div>').text(title);
return { domNodes: [$el.get(0)] };
}
});
@@ -169,8 +209,11 @@
if (halfPct > 49) halfPct = 49;
var color = el.style.getPropertyValue('--yc-event-color') || '#3498db';
var startTrans = info.isStart;
var endTrans = info.isEnd;
// Server emituje 1 event per doba — info.isStart/isEnd byłyby zawsze true.
// Flagi half-day pochodzą z extendedProps (server wie czy to pierwszy/ostatni dzień rezerwacji).
var props = info.event.extendedProps || {};
var startTrans = !!props.is_first;
var endTrans = !!props.is_last_night;
// Build gradient.
var stops;