Files
itflow/agent/js/calendar_event_modal.js
johnnyq 3ec52974db Calendar modernization: calendar page rebuilt to load only the dates on screen with show or hide toggles for each calendar and for built-in sources (scheduled tickets, project start and due dates, service reviews, recurring tickets, renewals, invoices due), a filter for one agent, quick looks on click, drag to reschedule tickets and events that do not repeat, and a To schedule panel for dragging open unscheduled tickets onto the calendar; events get a shared add/edit form with repeats every N days/weeks/months/years with an optional end date and single skipped occurrences, a client site, location or meeting link, agents, client contacts and guest emails as attendees, iCalendar invitations with updates and cancellations sent to invited attendees, an event quick look with map and join links, .ics download and resend; each agent gets a personal subscription link for their schedule (their own or the whole team, busy only option, respects role and client access, stops when the account is disabled) with setup help for Google, Outlook, Apple, Nextcloud and Thunderbird; shared calendar feeds and invites now publish timed events with TZID and VTIMEZONE so repeating events keep their time across DST, with INTERVAL, UNTIL and EXDATE support; managing calendars is limited to admins; ticket schedule logic moved into functions/ticket.php and shared with the calendar, the schedule dialog shows the assigned agent's day, and ticket visit calendar files are rebuilt without zapcal as one-hour published events; mail queue sends invitations and cancellations as a text/calendar part and other calendar files as invite.ics; dashboard schedule shows only events the agent is on or added, client overview Coming up and client sidebar count use upcoming events, sites and contacts get New event shortcuts, demo data uses repeating events and attendees; database update 2.8.8 adds event repeat interval, until, skip, sequence and created by, attendee user and invited at, user_calendar_feeds, and widens email_queue.email_cal_str to text
2026-10-15 19:17:17 -04:00

117 lines
4.3 KiB
JavaScript

// New Event and Edit Event (agent/modals/calendar/calendar_event_form.php):
// All day hides the times, Repeats shows "every N weeks, until", and the
// client's sites and contacts follow the client picked - an internal event
// has neither. The end date and time keep up with the start (js/app.js).
//
// Wrapped in an IIFE because a modal can be opened, closed and opened again
// in one page load, which re-runs this file.
(function () {
const allDay = document.getElementById('event_form_all_day');
const repeat = document.getElementById('eventRepeat');
const interval = document.getElementById('eventRepeatInterval');
const unit = document.getElementById('eventRepeatUnit');
const clientSelect = document.getElementById('eventClientSelect');
const siteField = document.getElementById('eventSiteField');
const siteSelect = document.getElementById('eventSiteSelect');
const contactsField = document.getElementById('eventContactsField');
const contactsSelect = document.getElementById('eventContactsSelect');
function syncAllDay() {
if (!allDay) {
return;
}
document.querySelectorAll('.event_form_time_fields').forEach(function (field) {
field.classList.toggle('d-none', allDay.checked);
field.querySelectorAll('input').forEach(function (input) {
input.required = !allDay.checked;
});
});
}
if (allDay) {
allDay.addEventListener('change', syncAllDay);
syncAllDay();
}
function syncRepeat() {
if (!repeat) {
return;
}
const option = repeat.options[repeat.selectedIndex];
const on = repeat.value !== '';
document.getElementById('eventRepeatEvery').hidden = !on;
document.getElementById('eventRepeatUntilField').hidden = !on;
if (on && unit) {
unit.textContent = Number(interval.value) === 1 ? option.dataset.one : option.dataset.many;
}
}
if (repeat) {
repeat.addEventListener('change', syncRepeat);
interval.addEventListener('input', syncRepeat);
syncRepeat();
}
// The sites and contacts of the client picked
function loadClient(clientId) {
const internal = !clientId || clientId === '0';
if (siteField) {
siteField.hidden = internal;
}
if (contactsField) {
contactsField.hidden = internal;
}
if (siteSelect) {
siteSelect.innerHTML = '';
siteSelect.add(new Option('- No site -', '0'));
}
if (contactsSelect) {
contactsSelect.innerHTML = '';
}
if (internal) {
return;
}
itflowGet('ajax.php', {get_client_locations: 'true', client_id: clientId}, function (data) {
let locations = [];
try {
locations = JSON.parse(data).locations || [];
} catch (e) {
locations = [];
}
locations.forEach(function (location) {
const option = new Option(location.location_name, location.location_id);
option.dataset.icon = 'fas fa-map-marker-alt';
siteSelect.add(option);
});
});
itflowGet('ajax.php', {get_client_contacts: 'true', client_id: clientId}, function (data) {
let contacts = [];
try {
contacts = JSON.parse(data).contacts || [];
} catch (e) {
contacts = [];
}
contacts.forEach(function (contact) {
const option = new Option(contact.contact_name, contact.contact_id);
option.dataset.description = contact.contact_email || 'No email';
if (contact.contact_photo_url) {
option.dataset.image = contact.contact_photo_url;
} else {
option.dataset.initials = contact.contact_initials;
option.dataset.shape = 'circle';
option.dataset.tone = contact.contact_tone || 'secondary';
option.dataset.soft = '1';
}
contactsSelect.add(option);
});
});
}
if (clientSelect) {
clientSelect.addEventListener('change', function () {
loadClient(clientSelect.value);
});
}
})();