// Company Settings - Data Lookups Management // Handles CRUD operations for Job Statuses, Job Priorities, and Quote Statuses (function () { 'use strict'; // Expose arrays globally for modal script window.jobStatuses = []; window.jobPriorities = []; window.quoteStatuses = []; window.inventoryCategories = []; window.prepServices = []; let currentEditItem = null; let currentEditType = null; // 'status', 'priority', 'quote', or 'category' // Initialize on DOM ready $(document).ready(function () { // Load data when Data Lookups tab is shown $('button[data-bs-target="#data-lookups"]').on('shown.bs.tab', function () { loadAllLookups(); }); // Load data for each sub-tab $('#job-statuses-subtab').on('shown.bs.tab', function () { if (window.jobStatuses.length === 0) loadJobStatuses(); }); $('#job-priorities-subtab').on('shown.bs.tab', function () { if (window.jobPriorities.length === 0) loadJobPriorities(); }); $('#quote-statuses-subtab').on('shown.bs.tab', function () { if (window.quoteStatuses.length === 0) loadQuoteStatuses(); }); $('#inventory-categories-subtab').on('shown.bs.tab', function () { if (window.inventoryCategories.length === 0) loadInventoryCategories(); }); // Add button handlers - will use modals from company-settings-lookups-modals.js $('#btnAddJobStatus').on('click', function () { if (typeof window.showJobStatusModal === 'function') { window.showJobStatusModal(null); } else { showAddEditModal('status', null); // Fallback } }); $('#btnAddJobPriority').on('click', function () { if (typeof window.showJobPriorityModal === 'function') { window.showJobPriorityModal(null); } else { showAddEditModal('priority', null); // Fallback } }); $('#btnAddQuoteStatus').on('click', function () { if (typeof window.showQuoteStatusModal === 'function') { window.showQuoteStatusModal(null); } else { showAddEditModal('quote', null); // Fallback } }); $('#btnAddInventoryCategory').on('click', function () { if (typeof window.showInventoryCategoryModal === 'function') { window.showInventoryCategoryModal(null); } else { showAddEditModal('category', null); // Fallback } }); $('#btnAddAppointmentType').on('click', function () { if (typeof window.showAppointmentTypeModal === 'function') { window.showAppointmentTypeModal(null); } else { console.error('Appointment type modal function not loaded'); } }); // Check if we should load lookups immediately (if tab is already active) if ($('#data-lookups').hasClass('active')) { loadAllLookups(); } }); // Load all lookup data function loadAllLookups() { loadJobStatuses(); loadJobPriorities(); loadQuoteStatuses(); loadInventoryCategories(); } // Load Job Statuses function loadJobStatuses() { $.ajax({ url: '/CompanySettings/GetJobStatuses', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.jobStatuses = data; renderJobStatusesTable(); } else if (data && data.success === false) { // Server returned an error response console.error('Error loading job statuses:', data.message); showToast('error', data.message || 'Failed to load job statuses'); } else { console.error('Invalid job statuses data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading job statuses:', xhr); showToast('error', 'Failed to load job statuses'); } }); } // Load Job Priorities function loadJobPriorities() { $.ajax({ url: '/CompanySettings/GetJobPriorities', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.jobPriorities = data; renderJobPrioritiesTable(); } else if (data && data.success === false) { // Server returned an error response console.error('Error loading job priorities:', data.message); showToast('error', data.message || 'Failed to load job priorities'); } else { console.error('Invalid job priorities data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading job priorities:', xhr); showToast('error', 'Failed to load job priorities'); } }); } // Load Quote Statuses function loadQuoteStatuses() { $.ajax({ url: '/CompanySettings/GetQuoteStatuses', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.quoteStatuses = data; renderQuoteStatusesTable(); } else if (data && data.success === false) { // Server returned an error response console.error('Error loading quote statuses:', data.message); showToast('error', data.message || 'Failed to load quote statuses'); } else { console.error('Invalid quote statuses data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading quote statuses:', xhr); showToast('error', 'Failed to load quote statuses'); } }); } // Load Inventory Categories function loadInventoryCategories() { $.ajax({ url: '/CompanySettings/GetInventoryCategories', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.inventoryCategories = data; renderInventoryCategoriesTable(); } else if (data && data.success === false) { console.error('Error loading inventory categories:', data.message); showToast('error', data.message || 'Failed to load inventory categories'); } else { console.error('Invalid inventory categories data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading inventory categories:', xhr); showToast('error', 'Failed to load inventory categories'); } }); } // Render Job Statuses Table function renderJobStatusesTable() { const tbody = $('#jobStatusesTableBody'); tbody.empty(); $('#jobStatusCount').text(window.jobStatuses.length); if (window.jobStatuses.length === 0) { tbody.append('No job statuses found'); return; } window.jobStatuses.forEach(function (status) { const flags = []; if (status.isTerminalStatus) flags.push('Terminal'); if (status.isWorkInProgressStatus) flags.push('WIP'); if (status.isSystemDefined) flags.push('System'); const row = ` ${escapeHtml(status.displayName)} ${escapeHtml(status.statusCode)} ${status.colorClass} ${escapeHtml(status.workflowCategory || '-')} ${flags.join(' ') || '-'} ${status.jobCount} jobs `; tbody.append(row); }); // Initialize drag-and-drop initializeSortable('jobStatusesTableBody', 'status'); } // Render Job Priorities Table function renderJobPrioritiesTable() { const tbody = $('#jobPrioritiesTableBody'); tbody.empty(); $('#jobPriorityCount').text(window.jobPriorities.length); if (window.jobPriorities.length === 0) { tbody.append('No job priorities found'); return; } window.jobPriorities.forEach(function (priority) { const row = ` ${escapeHtml(priority.displayName)} ${escapeHtml(priority.priorityCode)} ${priority.colorClass} ${priority.jobCount} jobs `; tbody.append(row); }); // Initialize drag-and-drop initializeSortable('jobPrioritiesTableBody', 'priority'); } // Render Quote Statuses Table function renderQuoteStatusesTable() { const tbody = $('#quoteStatusesTableBody'); tbody.empty(); $('#quoteStatusCount').text(window.quoteStatuses.length); if (window.quoteStatuses.length === 0) { tbody.append('No quote statuses found'); return; } window.quoteStatuses.forEach(function (status) { const flags = []; if (status.isApprovedStatus) flags.push('Approved'); if (status.isConvertedStatus) flags.push('Converted'); if (status.isDraftStatus) flags.push('Draft'); if (status.isSystemDefined) flags.push('System'); const row = ` ${escapeHtml(status.displayName)} ${escapeHtml(status.statusCode)} ${status.colorClass} ${flags.join(' ') || '-'} ${status.quoteCount} quotes `; tbody.append(row); }); // Initialize drag-and-drop initializeSortable('quoteStatusesTableBody', 'quote'); } // Render Inventory Categories Table function renderInventoryCategoriesTable() { const tbody = $('#inventoryCategoriesTableBody'); tbody.empty(); $('#inventoryCategoryCount').text(window.inventoryCategories.length); if (window.inventoryCategories.length === 0) { tbody.append('No inventory categories found'); return; } window.inventoryCategories.forEach(function (category) { const row = ` ${escapeHtml(category.displayName)}${category.isCoating ? ' Coating' : ''} ${escapeHtml(category.categoryCode)} ${escapeHtml(category.description || '-')} ${category.itemCount} items `; tbody.append(row); }); // Initialize drag-and-drop initializeSortable('inventoryCategoriesTableBody', 'category'); } // Show Add/Edit Modal function showAddEditModal(type, item) { currentEditType = type; currentEditItem = item; // For now, use browser prompt/confirm (can be enhanced with Bootstrap modals later) if (!item) { // Add mode const code = prompt(`Enter ${type} code (e.g., CUSTOM_STATUS):`); if (!code) return; const displayName = prompt('Enter display name:'); if (!displayName) return; const data = { statusCode: code.toUpperCase().replace(/\s+/g, '_'), displayName: displayName, displayOrder: 999, colorClass: 'secondary' }; if (type === 'status') { createJobStatus(data); } else if (type === 'priority') { data.priorityCode = data.statusCode; delete data.statusCode; createJobPriority(data); } else if (type === 'quote') { createQuoteStatus(data); } else if (type === 'category') { data.categoryCode = data.statusCode; delete data.statusCode; delete data.colorClass; createInventoryCategory(data); } } else { // Edit mode const newDisplayName = prompt('Enter new display name:', item.displayName); if (!newDisplayName || newDisplayName === item.displayName) return; const data = { id: item.id, displayName: newDisplayName, displayOrder: item.displayOrder, colorClass: item.colorClass, isActive: item.isActive }; if (type === 'status') { data.isTerminalStatus = item.isTerminalStatus; data.isWorkInProgressStatus = item.isWorkInProgressStatus; updateJobStatus(data); } else if (type === 'priority') { updateJobPriority(data); } else if (type === 'quote') { data.isApprovedStatus = item.isApprovedStatus; data.isConvertedStatus = item.isConvertedStatus; data.isDraftStatus = item.isDraftStatus; updateQuoteStatus(data); } else if (type === 'category') { delete data.colorClass; data.description = item.description; updateInventoryCategory(data); } } } // Create Job Status function createJobStatus(data) { $.ajax({ url: '/CompanySettings/CreateJobStatus', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadJobStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to create job status'); } }); } // Update Job Status function updateJobStatus(data) { $.ajax({ url: '/CompanySettings/UpdateJobStatus', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadJobStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to update job status'); } }); } // Delete Job Status function deleteJobStatus(id) { if (!confirm('Are you sure you want to delete this job status?')) return; $.ajax({ url: '/CompanySettings/DeleteJobStatus', type: 'POST', data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message); loadJobStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to delete job status'); } }); } // Similar functions for Job Priority function createJobPriority(data) { $.ajax({ url: '/CompanySettings/CreateJobPriority', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadJobPriorities(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to create job priority'); } }); } function updateJobPriority(data) { $.ajax({ url: '/CompanySettings/UpdateJobPriority', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadJobPriorities(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to update job priority'); } }); } function deleteJobPriority(id) { if (!confirm('Are you sure you want to delete this job priority?')) return; $.ajax({ url: '/CompanySettings/DeleteJobPriority', type: 'POST', data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message); loadJobPriorities(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to delete job priority'); } }); } // Similar functions for Quote Status function createQuoteStatus(data) { $.ajax({ url: '/CompanySettings/CreateQuoteStatus', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadQuoteStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to create quote status'); } }); } function updateQuoteStatus(data) { $.ajax({ url: '/CompanySettings/UpdateQuoteStatus', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadQuoteStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to update quote status'); } }); } function deleteQuoteStatus(id) { if (!confirm('Are you sure you want to delete this quote status?')) return; $.ajax({ url: '/CompanySettings/DeleteQuoteStatus', type: 'POST', data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message); loadQuoteStatuses(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to delete quote status'); } }); } // Similar functions for Inventory Categories function createInventoryCategory(data) { $.ajax({ url: '/CompanySettings/CreateInventoryCategory', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadInventoryCategories(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to create inventory category'); } }); } function updateInventoryCategory(data) { $.ajax({ url: '/CompanySettings/UpdateInventoryCategory', type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message); loadInventoryCategories(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to update inventory category'); } }); } function deleteInventoryCategory(id) { if (!confirm('Are you sure you want to delete this inventory category?')) return; $.ajax({ url: '/CompanySettings/DeleteInventoryCategory', type: 'POST', data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message); loadInventoryCategories(); } else { showToast('error', response.message); } }, error: function () { showToast('error', 'Failed to delete inventory category'); } }); } // Helper: Escape HTML function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Helper: Show Toast (uses existing toast implementation from CompanySettings) function showToast(type, message) { if (typeof window.showToast === 'function') { window.showToast(type, message); } else { // Fallback to global toast functions console.log(type.toUpperCase() + ': ' + message); if (type === 'success') { showSuccess(message); } else if (type === 'error') { showError(message); } else if (type === 'warning') { showWarning(message); } else { showInfo(message); } } } // Expose functions globally for onclick handlers window.editJobStatus = function (id) { const item = window.jobStatuses.find(s => s.id === id); if (item) showAddEditModal('status', item); }; window.deleteJobStatus = deleteJobStatus; window.editJobPriority = function (id) { const item = window.jobPriorities.find(p => p.id === id); if (item) showAddEditModal('priority', item); }; window.deleteJobPriority = deleteJobPriority; window.editQuoteStatus = function (id) { const item = window.quoteStatuses.find(s => s.id === id); if (item) showAddEditModal('quote', item); }; window.deleteQuoteStatus = deleteQuoteStatus; window.editInventoryCategory = function (id) { const item = window.inventoryCategories.find(c => c.id === id); if (item) showAddEditModal('category', item); }; window.deleteInventoryCategory = deleteInventoryCategory; // Expose load functions globally for modal script window.loadJobStatuses = loadJobStatuses; window.loadJobPriorities = loadJobPriorities; window.loadQuoteStatuses = loadQuoteStatuses; window.loadInventoryCategories = loadInventoryCategories; window.loadAppointmentTypes = loadAppointmentTypes; // Initialize Sortable.js for drag-and-drop reordering function initializeSortable(tbodyId, type) { const tbody = document.getElementById(tbodyId); if (!tbody || typeof Sortable === 'undefined') return; new Sortable(tbody, { animation: 150, handle: '.bi-grip-vertical', ghostClass: 'sortable-ghost', dragClass: 'sortable-drag', onEnd: function (evt) { // Get the new order of IDs const orderedIds = []; $('#' + tbodyId + ' tr[data-id]').each(function () { orderedIds.push(parseInt($(this).data('id'))); }); // Send AJAX request to update order const url = type === 'status' ? '/CompanySettings/ReorderJobStatuses' : type === 'priority' ? '/CompanySettings/ReorderJobPriorities' : type === 'quote' ? '/CompanySettings/ReorderQuoteStatuses' : '/CompanySettings/ReorderInventoryCategories'; $.ajax({ url: url, type: 'POST', contentType: 'application/json', headers: { 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() }, data: JSON.stringify({ orderedIds: orderedIds }), success: function (response) { if (response.success) { showToast('success', response.message || 'Order updated successfully'); // Reload to refresh display orders if (type === 'status') loadJobStatuses(); else if (type === 'priority') loadJobPriorities(); else if (type === 'quote') loadQuoteStatuses(); else if (type === 'category') loadInventoryCategories(); else if (type === 'appointmentType') loadAppointmentTypes(); } else { showToast('error', response.message || 'Failed to update order'); } }, error: function () { showToast('error', 'An error occurred while updating order'); } }); } }); } // ==================== Appointment Types Functions ==================== // Load Appointment Types function loadAppointmentTypes() { $.ajax({ url: '/CompanySettings/GetAppointmentTypes', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.appointmentTypes = data; renderAppointmentTypesTable(); } else if (data && data.success === false) { console.error('Error loading appointment types:', data.message); showToast('error', data.message || 'Failed to load appointment types'); } else { console.error('Invalid appointment types data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading appointment types:', xhr); showToast('error', 'Failed to load appointment types'); } }); } // Render Appointment Types Table function renderAppointmentTypesTable() { const tbody = $('#appointmentTypesTableBody'); tbody.empty(); $('#appointmentTypeCount').text(window.appointmentTypes.length); if (window.appointmentTypes.length === 0) { tbody.append('No appointment types found'); return; } window.appointmentTypes.forEach(function (type) { // Determine text color based on background color const textColorClass = (type.colorClass === 'warning' || type.colorClass === 'yellow' || type.colorClass === 'lime' || type.colorClass === 'cyan') ? 'text-dark' : 'text-white'; const row = ` ${escapeHtml(type.displayName)} ${escapeHtml(type.typeCode)} ${escapeHtml(type.colorClass)} ${type.requiresJobLink ? ' Yes' : ' No'} ${type.isActive ? 'Active' : 'Inactive'} ${type.appointmentCount} appointments `; tbody.append(row); }); } // Delete Appointment Type (Edit function is now in company-settings-lookups-modals.js) window.deleteAppointmentType = function (id) { const type = window.appointmentTypes.find(t => t.id === id); if (!type || type.isSystemDefined || type.appointmentCount > 0) { showToast('error', 'Type cannot be deleted (system-defined or in use)'); return; } if (!confirm(`Are you sure you want to delete "${type.displayName}"?`)) return; $.ajax({ url: '/CompanySettings/DeleteAppointmentType', type: 'POST', headers: { 'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val() }, data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message || 'Type deleted successfully'); loadAppointmentTypes(); } else { showToast('error', response.message || 'Failed to delete type'); } }, error: function () { showToast('error', 'An error occurred while deleting the type'); } }); }; // Load appointment types when tab is shown $('button[data-bs-target="#appointment-types-lookup"]').on('shown.bs.tab', function () { loadAppointmentTypes(); }); // ===================== // PREP SERVICES // ===================== window.loadPrepServices = function () { $.ajax({ url: '/CompanySettings/GetPrepServices', type: 'GET', success: function (data) { if (Array.isArray(data)) { window.prepServices = data; renderPrepServicesTable(); } else if (data && data.success === false) { console.error('Error loading prep services:', data.message); showToast('error', data.message || 'Failed to load prep services'); } else { console.error('Invalid prep services data:', data); showToast('error', 'Received invalid data format'); } }, error: function (xhr) { console.error('Error loading prep services:', xhr); showToast('error', 'Failed to load prep services'); } }); }; function renderPrepServicesTable() { const tbody = $('#prepServicesTableBody'); tbody.empty(); $('#prepServiceCount').text(window.prepServices.length); if (window.prepServices.length === 0) { tbody.append('No prep services defined yet'); return; } window.prepServices.forEach(service => { const statusBadge = service.isActive ? 'Active' : 'Inactive'; tbody.append(` ${escapeHtml(service.serviceName)} ${service.description ? escapeHtml(service.description) : '-'} ${statusBadge} `); }); // Initialize drag-and-drop for reordering (not implemented yet but infrastructure is ready) initializeSortable('prepServicesTableBody', 'prepService'); } window.showPrepServiceModal = function (serviceId = null) { const modal = new bootstrap.Modal(document.getElementById('prepServiceModal')); const form = document.getElementById('prepServiceForm'); form.reset(); if (serviceId) { // Edit mode const service = window.prepServices.find(s => s.id === serviceId); if (!service) { showToast('error', 'Service not found'); return; } document.getElementById('prepServiceModalTitle').textContent = 'Edit Prep Service'; document.getElementById('prepServiceId').value = service.id; document.getElementById('prepServiceName').value = service.serviceName; document.getElementById('prepServiceDescription').value = service.description || ''; document.getElementById('prepServiceIsActive').checked = service.isActive; } else { // Add mode document.getElementById('prepServiceModalTitle').textContent = 'Add Prep Service'; document.getElementById('prepServiceId').value = ''; document.getElementById('prepServiceIsActive').checked = true; } modal.show(); }; window.savePrepService = function () { const id = document.getElementById('prepServiceId').value; const serviceName = document.getElementById('prepServiceName').value.trim(); const description = document.getElementById('prepServiceDescription').value.trim(); const isActive = document.getElementById('prepServiceIsActive').checked; if (!serviceName) { showToast('error', 'Service name is required'); return; } const data = { serviceName: serviceName, description: description || null, displayOrder: 999, isActive: isActive }; const url = id ? '/CompanySettings/UpdatePrepService' : '/CompanySettings/CreatePrepService'; if (id) { data.id = parseInt(id); // Get existing display order const existing = window.prepServices.find(s => s.id === parseInt(id)); if (existing) { data.displayOrder = existing.displayOrder; } } $.ajax({ url: url, type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: function (response) { if (response.success) { showToast('success', response.message || (id ? 'Prep service updated' : 'Prep service created')); bootstrap.Modal.getInstance(document.getElementById('prepServiceModal')).hide(); loadPrepServices(); } else { showToast('error', response.message || 'Failed to save prep service'); } }, error: function () { showToast('error', 'An error occurred while saving the service'); } }); }; window.addPrepService = function () { showPrepServiceModal(); }; window.editPrepService = function (id) { showPrepServiceModal(id); }; window.deletePrepService = function (id) { const service = window.prepServices.find(s => s.id === id); if (!service) { showToast('error', 'Service not found'); return; } if (!confirm(`Are you sure you want to delete "${service.serviceName}"?`)) return; $.ajax({ url: '/CompanySettings/DeletePrepService', type: 'POST', data: { id: id }, success: function (response) { if (response.success) { showToast('success', response.message || 'Prep service deleted'); loadPrepServices(); } else { showToast('error', response.message || 'Failed to delete prep service'); } }, error: function () { showToast('error', 'An error occurred while deleting the service'); } }); }; // Button click handlers $('#btnAddPrepService').on('click', addPrepService); $('#savePrepServiceBtn').on('click', savePrepService); // Load prep services when tab is shown $('button[data-bs-target="#prep-services-lookup"]').on('shown.bs.tab', function () { if (window.prepServices.length === 0) loadPrepServices(); }); })();