1075 lines
42 KiB
JavaScript
1075 lines
42 KiB
JavaScript
// 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('<tr><td colspan="8" class="text-center text-muted">No job statuses found</td></tr>');
|
|
return;
|
|
}
|
|
|
|
window.jobStatuses.forEach(function (status) {
|
|
const flags = [];
|
|
if (status.isTerminalStatus) flags.push('<span class="badge bg-secondary">Terminal</span>');
|
|
if (status.isWorkInProgressStatus) flags.push('<span class="badge bg-info">WIP</span>');
|
|
if (status.isSystemDefined) flags.push('<span class="badge bg-dark">System</span>');
|
|
|
|
const row = `
|
|
<tr data-id="${status.id}">
|
|
<td><i class="bi bi-grip-vertical text-muted" style="cursor: move;"></i></td>
|
|
<td><strong>${escapeHtml(status.displayName)}</strong></td>
|
|
<td><code>${escapeHtml(status.statusCode)}</code></td>
|
|
<td><span class="badge bg-${status.colorClass}">${status.colorClass}</span></td>
|
|
<td>${escapeHtml(status.workflowCategory || '-')}</td>
|
|
<td>${flags.join(' ') || '-'}</td>
|
|
<td>${status.jobCount} jobs</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editJobStatus(${status.id})" ${status.isSystemDefined ? 'disabled title="System-defined"' : ''}>
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteJobStatus(${status.id})" ${status.isSystemDefined || status.jobCount > 0 ? 'disabled title="' + (status.isSystemDefined ? 'System-defined' : 'In use') + '"' : ''}>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
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('<tr><td colspan="6" class="text-center text-muted">No job priorities found</td></tr>');
|
|
return;
|
|
}
|
|
|
|
window.jobPriorities.forEach(function (priority) {
|
|
const row = `
|
|
<tr data-id="${priority.id}">
|
|
<td><i class="bi bi-grip-vertical text-muted" style="cursor: move;"></i></td>
|
|
<td><strong>${escapeHtml(priority.displayName)}</strong></td>
|
|
<td><code>${escapeHtml(priority.priorityCode)}</code></td>
|
|
<td><span class="badge bg-${priority.colorClass}">${priority.colorClass}</span></td>
|
|
<td>${priority.jobCount} jobs</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editJobPriority(${priority.id})" ${priority.isSystemDefined ? 'disabled title="System-defined"' : ''}>
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteJobPriority(${priority.id})" ${priority.isSystemDefined || priority.jobCount > 0 ? 'disabled title="' + (priority.isSystemDefined ? 'System-defined' : 'In use') + '"' : ''}>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
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('<tr><td colspan="7" class="text-center text-muted">No quote statuses found</td></tr>');
|
|
return;
|
|
}
|
|
|
|
window.quoteStatuses.forEach(function (status) {
|
|
const flags = [];
|
|
if (status.isApprovedStatus) flags.push('<span class="badge bg-success">Approved</span>');
|
|
if (status.isConvertedStatus) flags.push('<span class="badge bg-primary">Converted</span>');
|
|
if (status.isDraftStatus) flags.push('<span class="badge bg-secondary">Draft</span>');
|
|
if (status.isSystemDefined) flags.push('<span class="badge bg-dark">System</span>');
|
|
|
|
const row = `
|
|
<tr data-id="${status.id}">
|
|
<td><i class="bi bi-grip-vertical text-muted" style="cursor: move;"></i></td>
|
|
<td><strong>${escapeHtml(status.displayName)}</strong></td>
|
|
<td><code>${escapeHtml(status.statusCode)}</code></td>
|
|
<td><span class="badge bg-${status.colorClass}">${status.colorClass}</span></td>
|
|
<td>${flags.join(' ') || '-'}</td>
|
|
<td>${status.quoteCount} quotes</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editQuoteStatus(${status.id})" ${status.isSystemDefined ? 'disabled title="System-defined"' : ''}>
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteQuoteStatus(${status.id})" ${status.isSystemDefined || status.quoteCount > 0 ? 'disabled title="' + (status.isSystemDefined ? 'System-defined' : 'In use') + '"' : ''}>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
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('<tr><td colspan="6" class="text-center text-muted">No inventory categories found</td></tr>');
|
|
return;
|
|
}
|
|
|
|
window.inventoryCategories.forEach(function (category) {
|
|
const row = `
|
|
<tr data-id="${category.id}">
|
|
<td><i class="bi bi-grip-vertical text-muted" style="cursor: move;"></i></td>
|
|
<td><strong>${escapeHtml(category.displayName)}</strong>${category.isCoating ? ' <span class="badge bg-primary"><i class="bi bi-paint-bucket"></i> Coating</span>' : ''}</td>
|
|
<td><code>${escapeHtml(category.categoryCode)}</code></td>
|
|
<td>${escapeHtml(category.description || '-')}</td>
|
|
<td>${category.itemCount} items</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editInventoryCategory(${category.id})">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteInventoryCategory(${category.id})" ${category.itemCount > 0 ? 'disabled title="In use"' : ''}>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
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('<tr><td colspan="7" class="text-center text-muted">No appointment types found</td></tr>');
|
|
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 = `
|
|
<tr data-id="${type.id}">
|
|
<td><strong>${escapeHtml(type.displayName)}</strong></td>
|
|
<td><code>${escapeHtml(type.typeCode)}</code></td>
|
|
<td><span class="badge bg-${type.colorClass} ${textColorClass}"><i class="bi bi-calendar-event me-1"></i>${escapeHtml(type.colorClass)}</span></td>
|
|
<td>${type.requiresJobLink ? '<i class="bi bi-check-circle text-success"></i> Yes' : '<i class="bi bi-x-circle text-muted"></i> No'}</td>
|
|
<td>${type.isActive ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'}</td>
|
|
<td>${type.appointmentCount} appointments</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editAppointmentType(${type.id})">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteAppointmentType(${type.id})" ${type.isSystemDefined || type.appointmentCount > 0 ? 'disabled title="' + (type.isSystemDefined ? 'System-defined' : 'In use') + '"' : ''}>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
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('<tr><td colspan="5" class="text-center text-muted">No prep services defined yet</td></tr>');
|
|
return;
|
|
}
|
|
|
|
window.prepServices.forEach(service => {
|
|
const statusBadge = service.isActive
|
|
? '<span class="badge bg-success">Active</span>'
|
|
: '<span class="badge bg-secondary">Inactive</span>';
|
|
|
|
tbody.append(`
|
|
<tr data-id="${service.id}">
|
|
<td><i class="bi bi-grip-vertical text-muted" style="cursor: move;"></i></td>
|
|
<td><strong>${escapeHtml(service.serviceName)}</strong></td>
|
|
<td>${service.description ? escapeHtml(service.description) : '<span class="text-muted">-</span>'}</td>
|
|
<td>${statusBadge}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="editPrepService(${service.id})">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deletePrepService(${service.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
|
|
// 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();
|
|
});
|
|
|
|
})();
|