feat: Enhance calendar functionality with multi-day view and time range selection
This commit is contained in:
@@ -141,6 +141,7 @@
|
|||||||
const appointmentId = {{ appointment_id|tojson }};
|
const appointmentId = {{ appointment_id|tojson }};
|
||||||
const slider = document.getElementById('day-slider');
|
const slider = document.getElementById('day-slider');
|
||||||
const sliderLabel = document.getElementById('day-slider-label');
|
const sliderLabel = document.getElementById('day-slider-label');
|
||||||
|
const sliderWrap = slider ? slider.closest('.day-slider-wrap') : null;
|
||||||
const selectedSlotInput = document.getElementById('start_day_time');
|
const selectedSlotInput = document.getElementById('start_day_time');
|
||||||
const selectedSlotBadge = document.getElementById('selected-slot-badge');
|
const selectedSlotBadge = document.getElementById('selected-slot-badge');
|
||||||
const clientNameInput = document.getElementById('client_name');
|
const clientNameInput = document.getElementById('client_name');
|
||||||
@@ -191,6 +192,19 @@
|
|||||||
return new Date(dateObj.getTime() + minutes * 60000);
|
return new Date(dateObj.getTime() + minutes * 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTimeForCalendar(dateObj) {
|
||||||
|
return String(dateObj.getHours()).padStart(2, '0') + ':' + String(dateObj.getMinutes()).padStart(2, '0') + ':00';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDays(dateStr, days) {
|
||||||
|
const d = new Date(dateStr + 'T00:00:00');
|
||||||
|
d.setDate(d.getDate() + days);
|
||||||
|
const y = d.getFullYear();
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(d.getDate()).padStart(2, '0');
|
||||||
|
return y + '-' + m + '-' + day;
|
||||||
|
}
|
||||||
|
|
||||||
function parseTimeSpanEntry(entry) {
|
function parseTimeSpanEntry(entry) {
|
||||||
const value = String(entry || '').trim();
|
const value = String(entry || '').trim();
|
||||||
let m = value.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$/);
|
let m = value.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})-(\d{2}:\d{2})$/);
|
||||||
@@ -242,12 +256,48 @@
|
|||||||
|
|
||||||
const candidateSlots = buildCandidateSlots();
|
const candidateSlots = buildCandidateSlots();
|
||||||
const slotStartSet = new Set(candidateSlots.map(function (slot) { return slot.start; }));
|
const slotStartSet = new Set(candidateSlots.map(function (slot) { return slot.start; }));
|
||||||
|
const allDays = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
|
||||||
|
|
||||||
|
let slotMinTime = '08:00:00';
|
||||||
|
let slotMaxTime = '18:00:00';
|
||||||
|
if (candidateSlots.length > 0) {
|
||||||
|
const starts = candidateSlots.map(function (slot) {
|
||||||
|
return new Date(slot.start.replace(' ', 'T') + ':00');
|
||||||
|
}).filter(function (d) { return !Number.isNaN(d.getTime()); });
|
||||||
|
const ends = candidateSlots.map(function (slot) {
|
||||||
|
return new Date(slot.end.replace(' ', 'T') + ':00');
|
||||||
|
}).filter(function (d) { return !Number.isNaN(d.getTime()); });
|
||||||
|
|
||||||
|
if (starts.length > 0 && ends.length > 0) {
|
||||||
|
let minStart = starts[0];
|
||||||
|
let maxEnd = ends[0];
|
||||||
|
starts.forEach(function (d) { if (d < minStart) minStart = d; });
|
||||||
|
ends.forEach(function (d) { if (d > maxEnd) maxEnd = d; });
|
||||||
|
slotMinTime = formatTimeForCalendar(minStart);
|
||||||
|
slotMaxTime = formatTimeForCalendar(maxEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleStart = allDays[0] || String(available.date_start || '');
|
||||||
|
const visibleEndExclusive = allDays.length > 0
|
||||||
|
? addDays(allDays[allDays.length - 1], 1)
|
||||||
|
: addDays(String(available.date_end || available.date_start || ''), 1);
|
||||||
|
|
||||||
|
const multiDayDuration = Math.max(1, allDays.length || 1);
|
||||||
|
const initialViewName = multiDayDuration === 1 ? 'timeGridDay' : 'timeGridRange';
|
||||||
let selectedSlot = '';
|
let selectedSlot = '';
|
||||||
let selectedEvent = null;
|
let selectedEvent = null;
|
||||||
let selectedShownOnce = false;
|
let selectedShownOnce = false;
|
||||||
|
|
||||||
const calendar = new FullCalendar.Calendar(calendarEl, {
|
const calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
initialView: 'timeGridWeek',
|
initialView: initialViewName,
|
||||||
|
views: {
|
||||||
|
timeGridRange: {
|
||||||
|
type: 'timeGrid',
|
||||||
|
duration: { days: multiDayDuration },
|
||||||
|
buttonText: 'Zeitraum'
|
||||||
|
}
|
||||||
|
},
|
||||||
locale: 'de',
|
locale: 'de',
|
||||||
firstDay: 1,
|
firstDay: 1,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
@@ -258,11 +308,21 @@
|
|||||||
selectable: false,
|
selectable: false,
|
||||||
slotDuration: '00:15:00',
|
slotDuration: '00:15:00',
|
||||||
snapDuration: '00:15:00',
|
snapDuration: '00:15:00',
|
||||||
|
slotMinTime: slotMinTime,
|
||||||
|
slotMaxTime: slotMaxTime,
|
||||||
nowIndicator: true,
|
nowIndicator: true,
|
||||||
|
validRange: {
|
||||||
|
start: visibleStart,
|
||||||
|
end: visibleEndExclusive,
|
||||||
|
},
|
||||||
|
visibleRange: {
|
||||||
|
start: visibleStart,
|
||||||
|
end: visibleEndExclusive,
|
||||||
|
},
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: 'prev,next today',
|
left: '',
|
||||||
center: 'title',
|
center: 'title',
|
||||||
right: 'timeGridDay,timeGridWeek'
|
right: multiDayDuration === 1 ? '' : 'timeGridDay,timeGridRange'
|
||||||
},
|
},
|
||||||
events: [],
|
events: [],
|
||||||
eventDrop: function (info) {
|
eventDrop: function (info) {
|
||||||
@@ -386,7 +446,10 @@
|
|||||||
|
|
||||||
clientNameInput.addEventListener('input', refreshIcsLink);
|
clientNameInput.addEventListener('input', refreshIcsLink);
|
||||||
|
|
||||||
const allDays = dateRangeInclusive(String(available.date_start || ''), String(available.date_end || ''));
|
if (sliderWrap) {
|
||||||
|
sliderWrap.style.display = multiDayDuration > 1 ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
slider.max = String(Math.max(0, allDays.length - 1));
|
slider.max = String(Math.max(0, allDays.length - 1));
|
||||||
slider.value = '0';
|
slider.value = '0';
|
||||||
updateSliderLabel();
|
updateSliderLabel();
|
||||||
@@ -398,8 +461,10 @@
|
|||||||
const idx = Number.parseInt(slider.value, 10) || 0;
|
const idx = Number.parseInt(slider.value, 10) || 0;
|
||||||
updateSliderLabel();
|
updateSliderLabel();
|
||||||
if (allDays[idx]) {
|
if (allDays[idx]) {
|
||||||
|
if (calendar.view.type === 'timeGridDay') {
|
||||||
calendar.gotoDate(allDays[idx]);
|
calendar.gotoDate(allDays[idx]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
calendar.render();
|
calendar.render();
|
||||||
|
|||||||
Reference in New Issue
Block a user