The js code is not loading properly on MI os, on all others it works.
I have created a calendar that has a month view with green and gray day slots, green are clicktable and TimegridDay view that show the working time of day and slots that are clicktable and not(green and gray).TimegridDay is not working properly only on Xiaomi devices. Onall computer type (OS) and another mobile os is working fine, except Xiaomi!
Initialization of colors of timegridDay is totaly wrong, not controled by the code, the working hours are not set as i tried in code. PointerEvents- none is not placed on all needed slots.
I have overided the touch logic, because of user experience…
This is the code of calendar:
$(document).ready(function() {
window.addEventListener('pageshow', function(event) {
if (sessionStorage.getItem('refreshCalendar') === 'true') {
sessionStorage.removeItem('refreshCalendar');
window.location.reload();
}
});
const disabledSlots = new Set();
const disabledDates = new Set()
let isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
console.log("da lie je touch", isTouchDevice);
var calendar;
var csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
// Globalne promenljive za touch događaje
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
let isScrolling = false;
let touchDuration = 0;
let deltaX = 0;
let deltaY = 0;
const moveThreshold = 10;
const tapThreshold = 100; // Vremenski prag za tap
// Funkcija za dodavanje touch događaja
function addTouchEvents() {
const calendarElement = document.getElementById('calendar');
// touchstart događaj
calendarElement.addEventListener('touchstart', function(e) {
touchStartTime = Date.now();
touchStartX = e.changedTouches[0].screenX;
touchStartY = e.changedTouches[0].screenY;
isScrolling = false;
console.log('pocetak dodira');
// Pribavljanje vremena iz klika
let targetCell = e.target.closest('.fc-timegrid-slot');
if (targetCell) {
let timeString = targetCell.getAttribute('data-time'); // Dobijanje vremena
let activeDate = calendar.getDate(); // Trenutni datum u kalendaru
// Kombinovanje datuma i vremena u jedan Date objekat
selectedDateTime = new Date(activeDate.toDateString() + ' ' + timeString);
console.log('Izabrano vreme:', selectedDateTime);
}
}, { passive: true });
// touchmove događaj
calendarElement.addEventListener('touchmove', function(e) {
isScrolling = true; // Ako korisnik pomera prst, setuj da je skrolovanje u toku
console.log('skroluje');
}, { passive: true });
// touchend događaj
calendarElement.addEventListener('touchend', function(e) {
let touchEndTime = Date.now();
touchDuration = touchEndTime - touchStartTime;
let touchEndX = e.changedTouches[0].screenX;
let touchEndY = e.changedTouches[0].screenY;
deltaX = Math.abs(touchEndX - touchStartX);
deltaY = Math.abs(touchEndY - touchStartY);
}, { passive: true });
}
$('#locations').change(function() {
var location = $('#locations').val();
console.log(location)
//on every change hide calendar because procedure field is reseted
hideAndDestroyCalendar();
if (location) {
// Fetch available procedures for the selected location
$.ajax({
url: reservationUrl, // Update with the correct URL for fetching procedures
method: 'POST',
data: {
'action': 'get_procedures',
'location': location,
csrfmiddlewaretoken: csrfToken
},
success: function(response) {
// Populate the procedures dropdown
$('#procedures').html('<option value="">'+ selectLocationText +'</option>');
if (response.procedures.length > 0) {
response.procedures.forEach(function(procedure) {
$('#procedures').append('<option value="' + procedure.name + '">' + procedure.name + '</option>');
});
$('#procedures').prop('disabled', false); // Enable the procedures dropdown
} else {
$('#procedures').html('<option value="">'+ErrorText+'</option>');
$('#procedures').prop('disabled', true);
hideAndDestroyCalendar();
}
},
error: function() {
$('#procedures').html('<option value="">'+ErrorText+'</option>');
hideAndDestroyCalendar();
}
});
} else {
// Reset procedures dropdown if no location is selected
$('#procedures').html('<option value="">'+selectLocationText+'</option>');
$('#procedures').prop('disabled', true);
hideAndDestroyCalendar()
}
});
$('#procedures').change(function() {
var location = $('#locations').val();
var procedure = $('#procedures').val();
if(location && procedure) {
$('#calendar').show();
document.querySelector(".status-container").style.display = "flex";
if(calendar) {
calendar.destroy();
}
disabledDates.clear()
disabledSlots.clear()
$.ajax({
url: reservationUrl,
method: 'POST',
data: {
'action': 'get_available_dates',
'location': location,
'procedure': procedure,
csrfmiddlewaretoken: csrfToken
},
success: function(response) {
var availableDates = response.available_dates;
var events = response.events;
var proDuration = response.slottime
let formatedSlottime = "00:" + String(proDuration).padStart(2, '0');
console.log("dostupni dani",availableDates)
// Pronalaženje prvog dostupnog termina
let firstAvailableSlotTime = null;
for (let i = 0; i < availableDates.length; i++) {
let startDate = new Date(availableDates[i].start);
if (!firstAvailableSlotTime || startDate < firstAvailableSlotTime) {
firstAvailableSlotTime = startDate;
}
}
// Formatiraj vreme prvog dostupnog slota za `scrollTime`
let scrollTime = firstAvailableSlotTime
? `${String(firstAvailableSlotTime.getHours()).padStart(2, '0')}:${String(firstAvailableSlotTime.getMinutes()).padStart(2, '0')}:00`
: '09:00:00'; // Defaultno vreme ako nema dostupnih slotova
//
calendar = new FullCalendar.Calendar(document.getElementById('calendar'), {
locale: 'sr-latn',
buttonText: {
today: 'Danas',
month: 'Mjesec',
week: 'Sedmica',
day: 'Dan'
},
dayHeaderContent: function(info) {
const dayNames = [
'Nedelja',
'Ponedeljak',
'Utorak',
'Srijeda',
'Četvrtak',
'Petak',
'Subota'
];
const dayNamesShort = ['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub'];
const dayIndex = info.date.getDay(); // Dobija indeks dana od 0 (Nedjelja) do 6 (Subota)
if (info.view.type === 'dayGridMonth') {
// Ako je mjesečni prikaz, koristi skraćenice
return dayNamesShort[dayIndex];
} else {
// U ostalim prikazima, koristi pune nazive
return dayNames[dayIndex];
}
},
allDaySlot: false,
height: 650,
scrollTime: scrollTime,
eventDisplay: 'auto',
//nowIndicator: true,
views: {
dayGridMonth: {
eventDisplay: 'none',
},
timeGridDay: {
eventDisplay: 'auto',
}
},
timeZone: 'local',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridDay'
},
selectable: true,
selectOverlap: false,
editable: false,
eventDurationEditable: false,
slotDuration: "00:05",//formatedSlottime,
//height: 'auto',
contentHeight: 'auto',
initialDate: availableDates[0].start,
//callback koji se poziva kada se klikne na neki datum ili na slot
dateClick: function(info) {
console.log('dateclicked')
now = new Date()
nowLocale = new Date(now.toLocaleString())
const clickedLocale = info.date.toLocaleString()
const clickedLocaleForm = new Date (clickedLocale);
const clickedLocaleDate = clickedLocale.split(',')[0]
/*console.log(nowLocale);
console.log(clickedLocaleForm);
console.log(disabledSlots)
console.log(disabledDates)
console.log(clickedLocaleForm <= nowLocale)
console.log(disabledDates.has(clickedLocaleDate))
console.log(disabledSlots.has(clickedLocale))*/
// Provera sa `disabledSlots`
if (disabledSlots.has(clickedLocale) || disabledDates.has(clickedLocaleDate)) {
alert(forbbidenMessage)
console.log("Kliknut je onemogućen slot provera disableslots.");
return;
}
else {
let currentView = calendar.view.type;
if (currentView === 'dayGridMonth') {
// Ako je trenutni prikaz 'month', prebacujemo na 'timeGridDay'
calendar.changeView('timeGridDay', info.dateStr);
}
//ako je view timgridday i nije mobilni i ako je kliknuto vreme manje od trenutnog return
else if (currentView === 'timeGridDay' && !isTouchDevice) {
info.dayEl.style.pointerEvents = 'none'
if (clickedLocaleForm < nowLocale) {
console.log("Kliknut je onemogućen slot.");
return;
}
//console.log(info.dayEl.style.pointerEvents);
//console.log(!isTouchDevice)
// Ako je već 'timeGridDay', izvršavamo AJAX zahtev
if(confirm(questionMessage)){
$.ajax({
url: reservationUrl,
method: 'POST',
data: {
'action': 'create_reservation',
'start': info.dateStr,
'location': $('#locations').val(),
'procedure': $('#procedures').val(),
'note': note.value,
csrfmiddlewaretoken: csrfToken
},
success: function(response, end) {
if (response.status === 'success') {
calendar.addEvent({
start: info.dateStr,
end: end,
title: "",
});
alert(successMessage);
info.dayEl.style.pointerEvents = 'auto'
//console.log(info.dayEl.style.pointerEvents);
window.location.href = response.redirect_url;
} else {
alert(response.message);
}
}
});
}
else{
console.log('Rezervacija je otkazana od strane korisnika.');
}
}
//ako nije zabelezno skrolovanje nije dugi pritisak i jeste mob, generise se klik
else if (!isScrolling && touchDuration < tapThreshold && deltaX < moveThreshold && deltaY < moveThreshold && calendar.view.type === 'timeGridDay' && isTouchDevice) {
console.log('generiše se klik');
info.dayEl.style.pointerEvents = 'none'
if (clickedLocaleForm < nowLocale) {
console.log("Kliknut je onemogućen slot.");
return;
}
console.log(info.dayEl.style.pointerEvents);
console.log(touchDuration);
console.log(deltaX);
console.log(deltaY);
if(confirm(questionMessage)){
// Izvršavanje AJAX poziva
$.ajax({
url: reservationUrl,
method: 'POST',
data: {
'action': 'create_reservation',
'start': selectedDateTime.toISOString(), // Start vreme iz info objekta
'location': $('#locations').val(), // Uzimanje vrednosti lokacije
'procedure': $('#procedures').val(), // Uzimanje vrednosti procedure
'note': note.value, // Vrednost beleške
csrfmiddlewaretoken: csrfToken // CSRF token
},
success: function(response, end) {
console.log(response.status);
if (response.status === 'success') {
calendar.addEvent({
start: selectedDateTime.toISOString(),
end: end,
title: "",
});
alert(successMessage);
info.dayEl.style.pointerEvents = 'auto'
console.log(info.dayEl.style.pointerEvents);
window.location.href = response.redirect_url;
} else {
calendar.refetchEvents();
alert(response.message);
}
}
});
}
else{
console.log('Rezervacija je otkazana od strane korisnika.');
}
}
}
},
//poziva se pre nego se <td> element doda DOM-u za prikaz dana
dayCellDidMount: function(info) {
var cellDate = info.date;
var isAvailable = availableDates.some(function(item) {
startDate = new Date(item.start);
endDate = new Date(item.end);
//proverava da li je neki dan u proslosti i vraca true za taj dan, dodaj mu zelenu boju
return (cellDate.getFullYear() === startDate.getFullYear() &&
cellDate.getMonth() === startDate.getMonth() &&
cellDate.getDate() >= startDate.getDate() &&
cellDate.getDate() <= endDate.getDate());
});
if (isAvailable) {
info.el.style.backgroundColor = '#d1e8c2';
info.el.style.color = 'black';
} // ukoliko nije true dodaje mu sivu boju, dodaje taj datum u disabledDates i pointerevnets postavlja na none
else {
localCellDate =cellDate.toLocaleString()
info.el.style.backgroundColor = '#d3d3d3';
info.el.style.pointerEvents = "none";
disabledDates.add(localCellDate.split(',')[0])
console.log("disabledDates:", disabledDates)
}
},
events: events,
//ne prikazuje eventove u month view-u
eventDidMount: function(info) {
if (calendar.view.type === 'dayGridMonth') {
info.el.style.display = 'none';
}
},
});
console.log(isTouchDevice);
//dodaje touchevents listner ukoliko je touch uredjaj
if (isTouchDevice === true) {
console.log("jeste touch", isTouchDevice);
addTouchEvents();
}
calendar.on('datesSet', function(info) {
//reset promenljivih kada se promeni datum ili prikaz
touchStartTime = 0;
touchStartX = 0;
touchStartY = 0;
selectedDateTime = null;
isScrolling = false;
calendar.render()
calendar.refetchEvents();
console.log('dateclicked')
if (calendar.view.type === 'timeGridDay') {
addTouchEvents();
const currentDate = new Date(info.startStr).toLocaleString().split(',')[0];
let slots = document.querySelectorAll('.fc-timegrid-slots tr ');;
let nowDate = new Date();
let lastAvbailableDate = new Date(Math.max(...availableDates.map(item => new Date(item.end).getTime())));
let firstAvailableDate = new Date(Math.min(...availableDates.map(item => new Date(item.start).getTime())));
//vraca eventove za dan na kojem se nalazi
let events = calendar.getEvents().filter(event => {
let eventDate = event.start.toLocaleString().split(',')[0];
return eventDate == currentDate;
});
slots.forEach(function(slot) {
let timeString = slot.querySelector('td').getAttribute('data-time');
let activeDate = info.view.currentStart;
let slotDateTime = new Date(activeDate.toDateString() + ' ' + timeString);
let slotDateTimeLocal = slotDateTime.toLocaleString()
console.log("slotDateTime",slotDateTime)
console.log("slotDateTimeLocal",slotDateTimeLocal)
console.log("lastAvbailableDate",lastAvbailableDate)
console.log("firstAvailableDate",firstAvailableDate)
console.log("slot manji od sada",slotDateTime < nowDate)
console.log("slot veci od poslednjeg dostupnog",slotDateTime > lastAvbailableDate )
console.log("slot manji od prvog dostupnog",slotDateTime < firstAvailableDate)
console.log("datum slota se nalazi u nedozvoljenim datumima",disabledDates.has(slotDateTimeLocal.split(',')[0]))
console.log(disabledSlots)
//onemogucava kliktanje na slotove u proslosti i buducnosti(u odnosu na poslednji dostupni dan)
if (disabledDates.has(slotDateTimeLocal.split(',')[0]) || slotDateTime < nowDate || slotDateTime > lastAvbailableDate || slotDateTime < firstAvailableDate) {
slot.style.backgroundColor = '#d3d3d3';
slot.style.pointerEvents = 'none';
console.log('kliknuli ste onemogucen slot');
console.log('ehej')
}
else {
let availableSlot = true;
let procedureEndTime = new Date(slotDateTime.getTime() + proDuration * 60000);
for (let i = 0; i < events.length; i++) {
let eventStart = (events[i].start);
let eventEnd = (events[i].end);
//proverava da li je slot dostupan, tacnije da li trajanje procedure predugacko da bi slot bio dostupan,da ne upada u postojecu rez, dodaje se zelena tamnija boja i pointer-events none.
if ((eventStart <= slotDateTime && eventEnd > slotDateTime) ||
(procedureEndTime > eventStart && procedureEndTime <= eventEnd) ||
(slotDateTime < eventStart && procedureEndTime > eventStart)) {
slot.style.backgroundColor = '#8fdf82';
slot.style.setProperty('pointer-events', 'none', 'important');
let formatSlotDateTime = slotDateTime.toLocaleString()
disabledSlots.add(formatSlotDateTime)
availableSlot = false;
break;
}
if (availableSlot) {
slot.style.backgroundColor = '#d1e8c2';
slot.style.pointerEvents = 'auto';
}
}
}
});
//dinamicki radno vreme postavlja
availableDates.forEach(date => {
if (date.start.split('T')[0]==currentDate) {
console.log("radno vreme" ,date.start.split('T')[1])
calendar.setOption('slotMinTime', date.start.split('T')[1] || '09:00');
calendar.setOption('slotMaxTime', date.end.split('T')[1] || '18:00');
}
})
}
});
calendar.render();
}
});
} else {
$('#calendar').hide();
hideAndDestroyCalendar();
}
});
$('#calendar').hide();
hideAndDestroyCalendar();
// Function to hide and destroy calendar
function hideAndDestroyCalendar() {
$('#calendar').hide();
if (calendar) {
calendar.destroy();
}
}
});
Help me please:kostarasovic25gmail.com
I have created a calendar that has a month view with green and gray day slots, green are clicktable and TimegridDay view that show the working time of day and slots that are clicktable and not(green and gray).TimegridDay is not working properly only on Xiaomi devices.
Initialization of colors of timegridDay is totaly wrong, not controled by the code, the working hours are not set as i tried in code. PointerEvents- none is not placed on all needed slots.