Files
itflow/agent/js/project_modal.js
johnnyq bbb2456f0b Projects modernization: projects list with Active/Mine/Overdue/Due soon/Stalled/Over budget/Ready to close/Completed/Archived/All views, ticket and task progress bars, time logged against budget with unbilled hours, due badges, manager changed in place, manager/client/created filters, bulk set manager, set due date, complete, reopen, archive, restore and delete, CSV/PDF export; project page with header actions, attention banners, tickets/tasks/time/due stat cards, Tickets/Tasks/Activity tabs, scope, details and team with hours; project quick look; New/Edit Project with client contact, start date, budget hours and template that can assign its tickets to the manager; Add tickets by picking or by number; add a project template's tickets to an existing project; template ticket subjects fill in [Client], [Project], [Month], [Quarter] and [Year]; project templates admin rework with ordered ticket templates, multi-add, duplicate, archive and restore; projects on the ticket page, client overview, dashboard, calendar, global search and client actions menu; New Ticket offers internal projects; tickets only join a project of their own client or an internal one; completing a project is checked on the server and completed projects can be reopened; deleting a project keeps its tickets; deleting a client deletes its projects; bulk ticket creation limits project choices to matching clients; project template reordering limited to admins; new manager notifications; new functions/project.php; removed project_link_closed_ticket.php; DB 2.8.6 adds project_start, project_budget_hours, project_contact_id and a ticket_project_id index, and cleans up orphaned projects, ticket project links and template links
2026-10-15 16:35:00 -04:00

98 lines
3.6 KiB
JavaScript

// New Project and Edit Project (agent/modals/project/project_form.php):
// the client contact picker follows the client picked, and an internal
// project has none; "assign the template's tickets" shows once a template
// is picked; the due date can't be before the start.
//
// 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 clientSelect = document.getElementById('projectClientSelect');
const contactField = document.getElementById('projectContactField');
const contactSelect = document.getElementById('projectContactSelect');
const templateSelect = document.getElementById('projectTemplateSelect');
const assignBox = document.getElementById('projectAssignToManager');
const start = document.getElementById('projectStart');
const due = document.getElementById('projectDue');
// The client's contacts, primary first - the primary is picked for a new project
function loadContacts(clientId) {
if (!contactSelect) {
return;
}
contactSelect.innerHTML = '';
contactSelect.add(new Option('Nobody', '0'));
const internal = !clientId || clientId === '0';
if (contactField) {
contactField.hidden = internal;
}
if (internal) {
contactSelect.value = '0';
return;
}
itflowGet('ajax.php', {get_client_contacts: 'true', client_id: clientId}, function (data) {
let contacts = [];
try {
contacts = JSON.parse(data).contacts || [];
} catch (e) {
contacts = [];
}
let primary = '0';
contacts.forEach(function (contact) {
const details = [];
if (contact.contact_title) {
details.push(contact.contact_title);
}
if (contact.contact_primary == '1') {
details.push('Primary');
if (primary === '0') {
primary = String(contact.contact_id);
}
} else if (contact.contact_technical == '1') {
details.push('Technical');
}
const option = new Option(contact.contact_name, contact.contact_id);
option.dataset.description = details.join(' · ');
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';
}
contactSelect.add(option);
});
contactSelect.value = primary;
});
}
if (clientSelect) {
clientSelect.addEventListener('change', function () {
loadContacts(clientSelect.value);
});
loadContacts(clientSelect.value);
}
function syncAssign() {
if (templateSelect && assignBox) {
assignBox.hidden = !templateSelect.value || templateSelect.value === '0';
}
}
if (templateSelect) {
templateSelect.addEventListener('change', syncAssign);
syncAssign();
}
if (start && due) {
start.addEventListener('change', function () {
due.min = start.value;
if (start.value && due.value && due.value < start.value) {
due.value = '';
}
});
}
})();