fixing rolodex and search

This commit is contained in:
HotSwapp
2025-08-11 21:58:25 -05:00
parent 278eb7c5d4
commit c76b68d009
25 changed files with 1651 additions and 915 deletions

View File

@@ -1113,15 +1113,7 @@ let currentUsers = [];
let currentSettings = [];
let userPagination = { page: 1, limit: 10 };
// Helper function for authenticated API calls
function getAuthHeaders() {
const token = localStorage.getItem('auth_token');
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
// Authorization and JSON headers are injected by window.http.wrappedFetch
// Check if current user has admin access
async function checkAdminAccess() {
const token = localStorage.getItem('auth_token');
@@ -1131,11 +1123,7 @@ async function checkAdminAccess() {
}
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const response = await window.http.wrappedFetch('/api/auth/me');
if (!response.ok) {
window.location.href = '/login';
@@ -1245,9 +1233,7 @@ document.addEventListener('DOMContentLoaded', function() {
// System Health Functions
async function loadSystemHealth() {
try {
const response = await fetch('/api/admin/health', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/health');
const data = await response.json();
// Update status indicator
@@ -1300,9 +1286,7 @@ async function loadSystemHealth() {
async function loadSystemStats() {
try {
const response = await fetch('/api/admin/stats', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/stats');
const data = await response.json();
// Update dashboard cards
@@ -1346,9 +1330,7 @@ async function loadUsers() {
if (search) url += 'search=' + encodeURIComponent(search) + '&';
if (filter === 'active') url += 'active_only=true&';
const response = await fetch(url, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(url);
const users = await response.json();
currentUsers = users;
@@ -1408,9 +1390,7 @@ function showCreateUserModal() {
async function editUser(userId) {
try {
const response = await fetch('/api/admin/users/' + userId, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/users/' + userId);
const user = await response.json();
document.getElementById('userModalTitle').textContent = 'Edit User';
@@ -1452,9 +1432,8 @@ async function saveUser() {
const url = isEdit ? '/api/admin/users/' + userId : '/api/admin/users';
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(url, {
const response = await window.http.wrappedFetch(url, {
method: method,
headers: getAuthHeaders(),
body: JSON.stringify(userData)
});
@@ -1490,9 +1469,8 @@ async function resetPassword() {
}
try {
const response = await fetch('/api/admin/users/' + userId + '/reset-password', {
const response = await window.http.wrappedFetch('/api/admin/users/' + userId + '/reset-password', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
new_password: newPassword,
confirm_password: confirmPassword
@@ -1517,9 +1495,8 @@ async function deactivateUser(userId) {
if (!confirm('Are you sure you want to deactivate this user?')) return;
try {
const response = await fetch('/api/admin/users/' + userId, {
method: 'DELETE',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/users/' + userId, {
method: 'DELETE'
});
if (response.ok) {
@@ -1539,9 +1516,7 @@ async function deactivateUser(userId) {
// Settings Management Functions
async function loadSettings() {
try {
const response = await fetch('/api/admin/settings', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/settings');
const data = await response.json();
currentSettings = data.settings;
@@ -1592,9 +1567,7 @@ function showCreateSettingModal() {
async function editSetting(settingKey) {
try {
const response = await fetch('/api/admin/settings/' + encodeURIComponent(settingKey), {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/settings/' + encodeURIComponent(settingKey));
const setting = await response.json();
document.getElementById('settingModalTitle').textContent = 'Edit Setting';
@@ -1627,9 +1600,8 @@ async function saveSetting() {
const url = isEdit ? '/api/admin/settings/' + encodeURIComponent(settingKey) : '/api/admin/settings';
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(url, {
const response = await window.http.wrappedFetch(url, {
method: method,
headers: getAuthHeaders(),
body: JSON.stringify(isEdit ? {
setting_value: settingData.setting_value,
description: settingData.description
@@ -1688,9 +1660,8 @@ async function saveInactivitySetting() {
};
try {
const response = await fetch(url, {
const response = await window.http.wrappedFetch(url, {
method,
headers: getAuthHeaders(),
body: JSON.stringify(body)
});
if (!response.ok) throw new Error('Failed to save setting');
@@ -1708,9 +1679,8 @@ async function deleteSetting(settingKey) {
if (!confirm('Are you sure you want to delete this setting?')) return;
try {
const response = await fetch('/api/admin/settings/' + encodeURIComponent(settingKey), {
method: 'DELETE',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/settings/' + encodeURIComponent(settingKey), {
method: 'DELETE'
});
if (response.ok) {
@@ -1730,9 +1700,7 @@ async function deleteSetting(settingKey) {
// Maintenance Functions
async function loadLookupTables() {
try {
const response = await fetch('/api/admin/lookups/tables', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/lookups/tables');
const data = await response.json();
const element = document.getElementById('lookup-tables');
@@ -1756,9 +1724,8 @@ async function vacuumDatabase() {
if (!confirm('This will optimize the database. Continue?')) return;
try {
const response = await fetch('/api/admin/maintenance/vacuum', {
method: 'POST',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/maintenance/vacuum', {
method: 'POST'
});
const result = await response.json();
@@ -1779,9 +1746,8 @@ async function analyzeDatabase() {
if (!confirm('This will analyze database statistics. Continue?')) return;
try {
const response = await fetch('/api/admin/maintenance/analyze', {
method: 'POST',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/maintenance/analyze', {
method: 'POST'
});
const result = await response.json();
@@ -1818,9 +1784,7 @@ function addMaintenanceLog(operation, message) {
// Backup Functions
async function loadBackups() {
try {
const response = await fetch('/api/admin/backup/list', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/backup/list');
const data = await response.json();
const tbody = document.getElementById('backup-list');
@@ -1861,9 +1825,8 @@ async function createBackup() {
if (!confirm('Create a new database backup?')) return;
try {
const response = await fetch('/api/admin/backup/create', {
method: 'POST',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/backup/create', {
method: 'POST'
});
const result = await response.json();
@@ -1882,9 +1845,8 @@ async function createBackup() {
async function downloadBackup(filename) {
try {
const response = await fetch('/api/admin/backup/download', {
method: 'GET',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch('/api/admin/backup/download', {
method: 'GET'
});
if (!response.ok) {
@@ -1959,9 +1921,7 @@ async function loadIssues() {
if (categoryFilter) url += 'category=' + encodeURIComponent(categoryFilter) + '&';
if (assignedToMe) url += 'assigned_to_me=true&';
const response = await fetch(url, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(url);
if (!response.ok) {
throw new Error('Failed to load issues');
@@ -1981,9 +1941,7 @@ async function loadIssues() {
async function loadIssueStats() {
try {
const response = await fetch('/api/support/stats', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/support/stats');
if (!response.ok) {
throw new Error('Failed to load issue stats');
@@ -2082,9 +2040,7 @@ function filterIssues() {
async function viewIssue(issueId) {
try {
const response = await fetch('/api/support/tickets/' + issueId, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/support/tickets/' + issueId);
if (!response.ok) {
throw new Error('Failed to load issue details');
@@ -2174,9 +2130,7 @@ async function viewIssue(issueId) {
async function loadUsersForAssignment() {
try {
if (allUsers.length === 0) {
const response = await fetch('/api/admin/users', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/admin/users');
allUsers = await response.json();
}
@@ -2235,9 +2189,8 @@ async function updateIssue() {
assigned_to: document.getElementById('updateAssignee').value || null
};
const response = await fetch('/api/support/tickets/' + window.currentIssueId, {
const response = await window.http.wrappedFetch('/api/support/tickets/' + window.currentIssueId, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(updateData)
});
@@ -2273,9 +2226,8 @@ async function addResponse() {
}
try {
const response = await fetch('/api/support/tickets/' + window.currentIssueId + '/responses', {
const response = await window.http.wrappedFetch('/api/support/tickets/' + window.currentIssueId + '/responses', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
message: message,
is_internal: isInternal
@@ -2307,9 +2259,7 @@ let importInProgress = false;
async function loadAvailableImportFiles() {
try {
const response = await fetch('/api/import/available-files', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/import/available-files');
if (!response.ok) throw new Error('Failed to load available files');
@@ -2351,9 +2301,7 @@ async function loadAvailableImportFiles() {
async function loadImportStatus() {
try {
const response = await fetch('/api/import/status', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/import/status');
if (!response.ok) throw new Error('Failed to load import status');
@@ -2431,15 +2379,13 @@ async function validateAdminFile() {
try {
showAdminProgress(true, 'Validating file...');
const response = await fetch(`/api/import/validate/${fileType}`, {
const response = await window.http.wrappedFetch(`/api/import/validate/${fileType}`, {
method: 'POST',
headers: getAuthHeaders(),
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Validation failed');
throw await window.http.toError(response, 'Validation failed');
}
const result = await response.json();
@@ -2447,7 +2393,10 @@ async function validateAdminFile() {
} catch (error) {
console.error('Validation error:', error);
showAlert('Validation failed: ' + error.message, 'error');
const message = window.http && typeof window.http.formatAlert === 'function'
? window.http.formatAlert(error, 'Validation failed')
: 'Validation failed: ' + (error && error.message ? error.message : String(error));
showAlert(message, 'error');
} finally {
showAdminProgress(false);
}
@@ -2550,15 +2499,13 @@ async function handleAdminImport(event) {
try {
showAdminProgress(true, 'Importing data...');
const response = await fetch(`/api/import/upload/${fileType}`, {
const response = await window.http.wrappedFetch(`/api/import/upload/${fileType}`, {
method: 'POST',
headers: getAuthHeaders(),
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Import failed');
throw await window.http.toError(response, 'Import failed');
}
const result = await response.json();
@@ -2572,7 +2519,10 @@ async function handleAdminImport(event) {
} catch (error) {
console.error('Import error:', error);
showAlert('Import failed: ' + error.message, 'error');
const message = window.http && typeof window.http.formatAlert === 'function'
? window.http.formatAlert(error, 'Import failed')
: 'Import failed: ' + (error && error.message ? error.message : String(error));
showAlert(message, 'error');
} finally {
importInProgress = false;
showAdminProgress(false);
@@ -2640,14 +2590,12 @@ async function clearAdminTable() {
}
try {
const response = await fetch(`/api/import/clear/${fileType}`, {
method: 'DELETE',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch(`/api/import/clear/${fileType}`, {
method: 'DELETE'
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Clear operation failed');
throw await window.http.toError(response, 'Clear operation failed');
}
const result = await response.json();
@@ -2658,7 +2606,10 @@ async function clearAdminTable() {
} catch (error) {
console.error('Clear table error:', error);
showAlert('Clear operation failed: ' + error.message, 'error');
const message = window.http && typeof window.http.formatAlert === 'function'
? window.http.formatAlert(error, 'Clear operation failed')
: 'Clear operation failed: ' + (error && error.message ? error.message : String(error));
showAlert(message, 'error');
}
}

View File

@@ -362,8 +362,9 @@
</script>
<!-- Custom JavaScript -->
<!-- Fetch wrapper must be loaded first to augment global fetch() before other scripts -->
<!-- Fetch wrapper should be loaded early. It exposes window.http.wrappedFetch and also wraps global fetch for compatibility. -->
<script src="/static/js/fetch-wrapper.js"></script>
<script src="/static/js/sanitizer.js"></script>
<!-- Load main.js first so global handlers are registered before other scripts -->
<script src="/static/js/main.js"></script>
<script src="/static/js/alerts.js"></script>

View File

@@ -32,12 +32,12 @@
<div class="lg:col-span-2">
<label for="searchInput" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Search Customers</label>
<div class="relative">
<input type="text" id="searchInput" class="w-full pl-10 pr-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" placeholder="Search by name, ID, city, email...">
<input type="text" id="searchInput" class="w-full pl-10 pr-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" placeholder="Start typing to search customers...">
<div class="absolute inset-y-0 left-0 flex items-center pl-3">
<i class="fa-solid fa-magnifying-glass text-neutral-400 dark:text-neutral-500"></i>
</div>
<button id="searchBtn" class="absolute inset-y-0 right-0 flex items-center pr-3 text-neutral-400 hover:text-primary-600 dark:text-neutral-500 dark:hover:text-primary-400 transition-colors">
<i class="fa-solid fa-arrow-right text-lg"></i>
<i id="searchIcon" class="fa-solid fa-magnifying-glass text-lg"></i>
</button>
</div>
</div>
@@ -58,7 +58,7 @@
<div class="max-w-md">
<label for="phoneSearch" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Phone Search</label>
<div class="relative">
<input type="text" id="phoneSearch" class="w-full pl-10 pr-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" placeholder="Search by phone number...">
<input type="text" id="phoneSearch" class="w-full pl-10 pr-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" placeholder="Type phone number to search...">
<div class="absolute inset-y-0 left-0 flex items-center pl-3">
<i class="fa-solid fa-phone text-neutral-400 dark:text-neutral-500"></i>
</div>
@@ -77,13 +77,13 @@
<table class="w-full text-sm text-left text-neutral-900 dark:text-neutral-100" id="customersTable">
<thead class="bg-neutral-50 dark:bg-neutral-800/50">
<tr class="border-b border-neutral-200 dark:border-neutral-700">
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">ID</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Name</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Group</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Location</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Phone</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Email</th>
<th class="px-6 py-4 text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider text-right">Actions</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Customer</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Name</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Group</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Location</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Phone</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Email</th>
<th class="px-4 py-3 text-right text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="customersTableBody" class="divide-y divide-neutral-200 dark:divide-neutral-700">
@@ -277,7 +277,7 @@
</div>
</div>
<script src="/static/js/customers-tailwind.js"></script>
<script src="/static/js/customers-tailwind.js?v=12"></script>
<script>
// Initialize on page load
@@ -296,16 +296,49 @@ document.addEventListener('DOMContentLoaded', function() {
});
function setupEventListeners() {
// Search functionality
document.getElementById('searchBtn').addEventListener('click', performSearch);
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') performSearch();
// Real-time search functionality with debouncing
let searchTimeout;
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch();
}, 300); // Wait 300ms after user stops typing
});
// Phone search
// Keep existing functionality for search button and Enter key
document.getElementById('searchBtn').addEventListener('click', performSearch);
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
clearTimeout(searchTimeout);
performSearch();
}
});
// Real-time phone search functionality with debouncing
let phoneSearchTimeout;
const phoneSearchInput = document.getElementById('phoneSearch');
phoneSearchInput.addEventListener('input', function(e) {
clearTimeout(phoneSearchTimeout);
phoneSearchTimeout = setTimeout(() => {
if (phoneSearchInput.value.trim()) {
performPhoneSearch();
} else {
// If phone search is cleared, go back to regular customer list
loadCustomers();
}
}, 300); // Wait 300ms after user stops typing
});
// Keep existing functionality for phone search button and Enter key
document.getElementById('phoneSearchBtn').addEventListener('click', performPhoneSearch);
document.getElementById('phoneSearch').addEventListener('keypress', function(e) {
if (e.key === 'Enter') performPhoneSearch();
phoneSearchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
clearTimeout(phoneSearchTimeout);
performPhoneSearch();
}
});
// Modal buttons
@@ -313,6 +346,7 @@ function setupEventListeners() {
document.getElementById('saveCustomerBtn').addEventListener('click', saveCustomer);
document.getElementById('deleteCustomerBtn').addEventListener('click', deleteCustomer);
document.getElementById('statsBtn').addEventListener('click', showStats);
document.getElementById('addPhoneBtn').addEventListener('click', addPhoneNumber);
// Form validation
const customerIdInput = document.getElementById('customerId');
@@ -329,6 +363,9 @@ function closeStatsModal() {
// Load customers with enhanced formatting
async function loadCustomers(page = 0, search = '') {
try {
// Show loading state
setSearchLoading(true);
const params = new URLSearchParams({
skip: page * 50,
limit: 50
@@ -336,9 +373,7 @@ async function loadCustomers(page = 0, search = '') {
if (search) params.append('search', search);
const response = await fetch(`/api/customers/?${params}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/customers/?${params}`);
if (!response.ok) throw new Error('Failed to load customers');
@@ -348,11 +383,23 @@ async function loadCustomers(page = 0, search = '') {
} catch (error) {
console.error('Error loading customers:', error);
showAlert('Error loading customers: ' + error.message, 'danger');
} finally {
// Hide loading state
setSearchLoading(false);
}
}
function setSearchLoading(isLoading) {
const searchIcon = document.getElementById('searchIcon');
if (isLoading) {
searchIcon.className = 'fa-solid fa-spinner fa-spin text-lg';
} else {
searchIcon.className = 'fa-solid fa-magnifying-glass text-lg';
}
}
function performSearch() {
currentSearch = document.getElementById('searchInput').value;
currentSearch = document.getElementById('searchInput').value.trim();
currentPage = 0;
loadCustomers(currentPage, currentSearch);
}
@@ -362,9 +409,7 @@ async function performPhoneSearch() {
if (!phone) return;
try {
const response = await fetch(`/api/customers/search/phone?phone=${encodeURIComponent(phone)}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/customers/search/phone?phone=${encodeURIComponent(phone)}`);
if (!response.ok) throw new Error('Phone search failed');
@@ -394,25 +439,25 @@ function displayPhoneSearchResults(results) {
const row = document.createElement('tr');
row.className = 'hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors duration-150';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-neutral-900 dark:text-neutral-100">${result.customer.id}</div>
<td class=\"px-3 py-2 whitespace-nowrap\">
<div class=\"text-sm font-mono text-neutral-900 dark:text-neutral-100 truncate\" title=\"${result.customer.id}\">${result.customer.id}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-neutral-900 dark:text-neutral-100">${result.customer.name}</div>
<td class=\"px-3 py-2 whitespace-nowrap\">
<div class=\"text-sm font-medium text-neutral-900 dark:text-neutral-100 truncate\" title=\"${result.customer.name}\">${result.customer.name}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class=\"px-3 py-2 whitespace-nowrap\">
<span class="text-neutral-400 dark:text-neutral-500">-</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100">
<td class=\"px-3 py-2 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100 truncate\" title=\"${result.customer.city}, ${result.customer.state}\">
${result.customer.city}, ${result.customer.state}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100">
<td class=\"px-3 py-2 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100 truncate\" title=\"${result.location}: ${result.phone}\">
<div class="font-semibold text-warning-600 dark:text-warning-400">${result.location}: ${result.phone}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class=\"px-3 py-2 whitespace-nowrap\">
<span class="text-neutral-400 dark:text-neutral-500">-</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<td class=\"px-3 py-2 whitespace-nowrap text-right text-sm font-medium\">
<button onclick="editCustomer('${result.customer.id}')" class="inline-flex items-center gap-1 px-3 py-1.5 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors duration-200 text-xs">
<i class="fa-solid fa-pencil"></i>
<span>Edit</span>
@@ -425,9 +470,7 @@ function displayPhoneSearchResults(results) {
async function loadGroups() {
try {
const response = await fetch('/api/customers/groups', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/customers/groups');
if (response.ok) {
const groups = await response.json();
@@ -446,9 +489,7 @@ async function loadGroups() {
async function loadStates() {
try {
const response = await fetch('/api/customers/states', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/customers/states');
if (response.ok) {
const states = await response.json();
@@ -467,9 +508,7 @@ async function loadStates() {
async function showStats() {
try {
const response = await fetch('/api/customers/stats', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/customers/stats');
if (!response.ok) throw new Error('Failed to load statistics');
@@ -520,25 +559,6 @@ function displayStats(stats) {
document.getElementById('statsModal').classList.remove('hidden');
}
// Placeholder functions for now
function editCustomer(customerId) {
showAlert('Edit customer feature coming soon...', 'info');
}
function viewCustomer(customerId) {
showAlert('View customer feature coming soon...', 'info');
}
function saveCustomer() {
showAlert('Save customer feature coming soon...', 'info');
}
function deleteCustomer() {
showAlert('Delete customer feature coming soon...', 'info');
}
function validateCustomerId() {
// Placeholder
}
// Functions are now implemented in the external customers-tailwind.js file
</script>
{% endblock %}

View File

@@ -191,11 +191,7 @@
// Load dashboard data
async function loadDashboardData() {
try {
const response = await fetch('/api/admin/stats', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
}
});
const response = await window.http.wrappedFetch('/api/admin/stats');
if (response.ok) {
const data = await response.json();

View File

@@ -446,9 +446,7 @@ document.addEventListener('DOMContentLoaded', function() {
function initializeDocuments() {
if (typeof apiGet === 'function') {
// Ensure API headers are set up with token
if (window.apiHeaders && token) {
window.apiHeaders['Authorization'] = `Bearer ${token}`;
}
// Authorization is injected by window.http.wrappedFetch
// Initialize the first tab as active
document.getElementById('templates-tab').click();
@@ -594,14 +592,7 @@ function setupEventHandlers() {
document.getElementById('refreshQdrosBtn').addEventListener('click', loadQdros);
}
// Helper function for authenticated API calls
function getAuthHeaders() {
const token = localStorage.getItem('auth_token');
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
// Authorization and JSON headers are injected by window.http.wrappedFetch
async function loadTemplates() {
try {
@@ -624,9 +615,7 @@ async function loadTemplates() {
console.log('🔍 DEBUG: Making API call to:', url);
const response = await fetch(url, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(url);
console.log('🔍 DEBUG: Response status:', response.status);
if (!response.ok) {
@@ -658,7 +647,7 @@ function createTemplateRow(template) {
const row = document.createElement('tr');
const variableCount = Object.keys(template.variables || {}).length;
row.innerHTML = `
const rowHtml = `
<td class="px-4 py-2"><code>${template.form_id}</code></td>
<td class="px-4 py-2">${template.form_name}</td>
<td class="px-4 py-2"><span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-100 text-neutral-700 border border-neutral-300">${template.category}</span></td>
@@ -677,6 +666,7 @@ function createTemplateRow(template) {
</div>
</td>
`;
if (window.setSafeHTML) { window.setSafeHTML(row, rowHtml); } else { row.innerHTML = rowHtml; }
return row;
}
@@ -696,9 +686,7 @@ async function loadQdros() {
return;
}
const response = await fetch(url, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(url);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
@@ -727,7 +715,7 @@ function displayQdros(qdros) {
function createQdroRow(qdro) {
const row = document.createElement('tr');
row.innerHTML = `
const qdroRowHtml = `
<td><code>${qdro.file_no}</code></td>
<td>${qdro.version}</td>
<td>${qdro.participant_name || ''}</td>
@@ -747,6 +735,7 @@ function createQdroRow(qdro) {
</div>
</td>
`;
if (window.setSafeHTML) { window.setSafeHTML(row, qdroRowHtml); } else { row.innerHTML = qdroRowHtml; }
return row;
}
@@ -768,9 +757,7 @@ async function loadCategories() {
return;
}
const response = await fetch('/api/documents/categories/', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/documents/categories/');
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
@@ -819,9 +806,7 @@ function openTemplateModal(templateId = null) {
async function loadTemplateForEditing(templateId) {
try {
const response = await fetch(`/api/documents/templates/${templateId}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/documents/templates/${templateId}`);
if (!response.ok) throw new Error('Failed to load template');
const template = await response.json();
@@ -856,11 +841,8 @@ async function saveTemplate() {
const url = isEdit ? `/api/documents/templates/${templateData.form_id}` : '/api/documents/templates/';
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(url, {
const response = await window.http.wrappedFetch(url, {
method: method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(templateData)
});
@@ -901,9 +883,7 @@ function extractVariables(content) {
async function loadDocumentStats() {
try {
const response = await fetch('/api/documents/stats/summary', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/documents/stats/summary');
if (!response.ok) throw new Error('Failed to load statistics');
const stats = await response.json();
@@ -918,7 +898,8 @@ async function loadDocumentStats() {
Object.entries(stats.templates_by_category).forEach(([category, count]) => {
const div = document.createElement('div');
div.className = 'flex items-center justify-between mb-1';
div.innerHTML = `<span>${category}</span><span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-200 text-neutral-700">${count}</span>`;
const html = `<span>${category}</span><span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-200 text-neutral-700">${count}</span>`;
if (window.setSafeHTML) { window.setSafeHTML(div, html); } else { div.innerHTML = html; }
categoriesDiv.appendChild(div);
});
@@ -932,11 +913,12 @@ async function loadDocumentStats() {
stats.recent_activity.forEach(activity => {
const div = document.createElement('div');
div.className = 'mb-2 p-2 border rounded';
div.innerHTML = `
const activityHtml = `
<small class="text-neutral-500">${activity.type}</small><br>
<strong>File: ${activity.file_no}</strong><br>
<span class="${getStatusBadgeClass(activity.status)}">${activity.status}</span>
`;
if (window.setSafeHTML) { window.setSafeHTML(div, activityHtml); } else { div.innerHTML = activityHtml; }
activityDiv.appendChild(div);
});
}
@@ -983,9 +965,8 @@ async function logClientError({ message, action = null, error = null, extra = nu
user_agent: navigator.userAgent,
extra
};
await fetch('/api/documents/client-error', {
await window.http.wrappedFetch('/api/documents/client-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
} catch (_) {
@@ -1010,9 +991,8 @@ async function generateFromTemplate(templateId) {
async function deleteTemplate(templateId) {
if (confirm('Are you sure you want to delete this template?')) {
try {
const response = await fetch(`/api/documents/templates/${templateId}`, {
method: 'DELETE',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch(`/api/documents/templates/${templateId}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete template');
@@ -1034,9 +1014,7 @@ function openGenerateModal() {
async function loadTemplatesForGeneration() {
try {
const response = await fetch('/api/documents/templates/', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/documents/templates/');
if (!response.ok) throw new Error('Failed to load templates');
const templates = await response.json();
@@ -1057,9 +1035,7 @@ async function loadTemplatesForGeneration() {
async function loadTemplatePreview(templateId) {
try {
const response = await fetch(`/api/documents/templates/${templateId}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/documents/templates/${templateId}`);
if (!response.ok) throw new Error('Failed to load template');
const template = await response.json();
@@ -1100,9 +1076,8 @@ async function generateDocument() {
});
}
const response = await fetch(`/api/documents/generate/${templateId}`, {
const response = await window.http.wrappedFetch(`/api/documents/generate/${templateId}`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(requestData)
});
@@ -1131,7 +1106,7 @@ function addCustomVariableInput() {
const container = document.getElementById('customVariables');
const div = document.createElement('div');
div.className = 'grid grid-cols-12 gap-2 mb-2 custom-var-input';
div.innerHTML = `
const customVarHtml = `
<div class="col-span-12 md:col-span-5">
<input type="text" class="w-full px-3 py-1.5 border border-neutral-300 dark:border-neutral-600 rounded var-name" placeholder="Variable name">
</div>
@@ -1144,6 +1119,7 @@ function addCustomVariableInput() {
</button>
</div>
`;
if (window.setSafeHTML) { window.setSafeHTML(div, customVarHtml); } else { div.innerHTML = customVarHtml; }
container.appendChild(div);
}

View File

@@ -381,14 +381,7 @@ let lookupData = {
employees: []
};
// Helper function for authenticated API calls
function getAuthHeaders() {
const token = localStorage.getItem('auth_token');
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
// Authorization and JSON headers are injected by window.http.wrappedFetch
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
@@ -446,9 +439,9 @@ async function loadLookupData() {
try {
// Load all lookup data in parallel
const [fileTypesRes, statusesRes, employeesRes] = await Promise.all([
fetch('/api/files/lookups/file-types', { headers: getAuthHeaders() }),
fetch('/api/files/lookups/file-statuses', { headers: getAuthHeaders() }),
fetch('/api/files/lookups/employees', { headers: getAuthHeaders() })
window.http.wrappedFetch('/api/files/lookups/file-types'),
window.http.wrappedFetch('/api/files/lookups/file-statuses'),
window.http.wrappedFetch('/api/files/lookups/employees')
]);
if (fileTypesRes.ok) {
@@ -505,9 +498,7 @@ async function loadFiles(page = 0, filters = {}) {
...filters
});
const response = await fetch(`/api/files/?${params}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/?${params}`);
if (!response.ok) throw new Error('Failed to load files');
@@ -628,9 +619,7 @@ function showAddFileModal() {
async function editFile(fileNo) {
try {
const response = await fetch(`/api/files/${fileNo}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/${fileNo}`);
if (!response.ok) throw new Error('Failed to load file');
@@ -687,9 +676,7 @@ function populateFileForm(file) {
async function loadClientInfo(clientId) {
try {
const response = await fetch(`/api/customers/${clientId}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/customers/${clientId}`);
if (response.ok) {
const client = await response.json();
@@ -705,9 +692,7 @@ async function loadClientInfo(clientId) {
async function loadFinancialSummary(fileNo) {
try {
const response = await fetch(`/api/files/${fileNo}/financial-summary`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/${fileNo}/financial-summary`);
if (response.ok) {
const data = await response.json();
@@ -771,9 +756,8 @@ async function saveFile() {
const url = isEditing ? `/api/files/${editingFileNo}` : '/api/files/';
const method = isEditing ? 'PUT' : 'POST';
const response = await fetch(url, {
const response = await window.http.wrappedFetch(url, {
method: method,
headers: getAuthHeaders(),
body: JSON.stringify(fileData)
});
@@ -798,9 +782,8 @@ async function deleteFile() {
}
try {
const response = await fetch(`/api/files/${editingFileNo}`, {
method: 'DELETE',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch(`/api/files/${editingFileNo}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete file');
@@ -820,9 +803,8 @@ async function closeFile() {
try {
const body = closeDate ? JSON.stringify({ close_date: closeDate }) : '';
const response = await fetch(`/api/files/${editingFileNo}/close`, {
const response = await window.http.wrappedFetch(`/api/files/${editingFileNo}/close`, {
method: 'POST',
headers: getAuthHeaders(),
body: body
});
@@ -841,9 +823,8 @@ async function closeFile() {
async function reopenFile() {
try {
const response = await fetch(`/api/files/${editingFileNo}/reopen`, {
method: 'POST',
headers: getAuthHeaders()
const response = await window.http.wrappedFetch(`/api/files/${editingFileNo}/reopen`, {
method: 'POST'
});
if (!response.ok) throw new Error('Failed to reopen file');
@@ -871,9 +852,7 @@ async function searchClients() {
const params = new URLSearchParams({ limit: 100 });
if (search) params.append('search', search);
const response = await fetch(`/api/customers/?${params}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/customers/?${params}`);
if (!response.ok) throw new Error('Failed to search clients');
@@ -916,9 +895,7 @@ async function validateFileNumber() {
if (!fileNo || isEditing) return;
try {
const response = await fetch(`/api/files/${fileNo}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/${fileNo}`);
if (response.ok) {
showAlert('File number already exists', 'warning');
@@ -951,9 +928,7 @@ async function performAdvancedSearch() {
}
try {
const response = await fetch(`/api/files/search/advanced?${params}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/search/advanced?${params}`);
if (!response.ok) throw new Error('Advanced search failed');
@@ -975,9 +950,7 @@ function clearAdvancedSearch() {
async function showStats() {
try {
const response = await fetch('/api/files/stats/summary', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/files/stats/summary');
if (!response.ok) throw new Error('Failed to load statistics');
@@ -1057,7 +1030,7 @@ function showAlert(message, type = 'info') {
async function loadDocuments(fileNo) {
try {
const response = await fetch(`/api/documents/${fileNo}/uploaded`, { headers: getAuthHeaders() });
const response = await window.http.wrappedFetch(`/api/documents/${fileNo}/uploaded`);
if (!response.ok) throw new Error('Failed to load documents');
const docs = await response.json();
displayDocuments(docs);
@@ -1126,10 +1099,10 @@ async function uploadDocument() {
formData.append('description', description);
try {
const response = await fetch(`/api/documents/upload/${editingFileNo}`, {
const response = await window.http.wrappedFetch(`/api/documents/upload/${editingFileNo}`, {
method: 'POST',
body: formData,
headers: getAuthHeaders() // Note: no Content-Type, browser sets it
// Note: no Content-Type; browser sets multipart boundaries
});
if (!response.ok) {
const error = await response.json();
@@ -1164,9 +1137,8 @@ function downloadDocument(path) {
async function deleteDocument(docId) {
if (!confirm('Are you sure you want to delete this document?')) return;
try {
const response = await fetch(`/api/documents/uploaded/${docId}`, {
const response = await window.http.wrappedFetch(`/api/documents/uploaded/${docId}`, {
method: 'DELETE',
headers: getAuthHeaders()
});
if (!response.ok) throw new Error('Failed to delete');
showAlert('Document deleted', 'success');
@@ -1186,10 +1158,9 @@ async function updateDocumentDescription(docId, description) {
const formData = new FormData();
formData.append('description', description);
try {
const response = await fetch(`/api/documents/uploaded/${docId}`, {
const response = await window.http.wrappedFetch(`/api/documents/uploaded/${docId}`, {
method: 'PUT',
body: formData,
headers: getAuthHeaders()
});
if (!response.ok) throw new Error('Failed to update');
showAlert('Description updated', 'success');

View File

@@ -483,14 +483,7 @@ let dashboardData = null;
let recentEntries = [];
let unbilledData = null;
// Helper function for authenticated API calls
function getAuthHeaders() {
const token = localStorage.getItem('auth_token');
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
// Authorization and JSON headers are injected by window.http.wrappedFetch
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
@@ -555,9 +548,7 @@ function setupEventListeners() {
async function loadDashboardData() {
try {
const response = await fetch('/api/financial/financial-dashboard', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/financial/financial-dashboard');
if (!response.ok) throw new Error('Failed to load dashboard data');
@@ -599,9 +590,7 @@ async function loadRecentTimeEntries() {
const params = new URLSearchParams({ days });
if (employee) params.append('employee', employee);
const response = await fetch(`/api/financial/time-entries/recent?${params}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/financial/time-entries/recent?${params}`);
if (!response.ok) throw new Error('Failed to load recent entries');
@@ -652,9 +641,7 @@ function displayRecentTimeEntries(entries) {
async function loadEmployeeOptions() {
try {
const response = await fetch('/api/files/lookups/employees', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/files/lookups/employees');
if (response.ok) {
const employees = await response.json();
@@ -719,9 +706,8 @@ async function saveQuickTime() {
};
try {
const response = await fetch('/api/financial/time-entry/quick', {
const response = await window.http.wrappedFetch('/api/financial/time-entry/quick', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
});
@@ -769,9 +755,8 @@ async function savePayment() {
};
try {
const response = await fetch('/api/financial/payments/', {
const response = await window.http.wrappedFetch('/api/financial/payments/', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
});
@@ -818,9 +803,8 @@ async function saveExpense() {
};
try {
const response = await fetch('/api/financial/expenses/', {
const response = await window.http.wrappedFetch('/api/financial/expenses/', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
});
@@ -850,9 +834,7 @@ async function saveExpense() {
async function showUnbilledModal() {
// Load unbilled data
try {
const response = await fetch('/api/financial/unbilled-entries', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/financial/unbilled-entries');
if (!response.ok) throw new Error('Failed to load unbilled entries');
@@ -979,9 +961,8 @@ async function billSelectedEntries() {
}
try {
const response = await fetch('/api/financial/bill-entries', {
const response = await window.http.wrappedFetch('/api/financial/bill-entries', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ entry_ids: entryIds })
});
@@ -1081,9 +1062,7 @@ async function validateQuickTimeFile() {
if (!fileNo) return;
try {
const response = await fetch(`/api/files/${fileNo}`, {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/files/${fileNo}`);
if (response.ok) {
const file = await response.json();

View File

@@ -235,13 +235,7 @@
let availableFiles = {};
let importInProgress = false;
// Helper function for authenticated API calls
function getAuthHeaders() {
const token = localStorage.getItem('auth_token');
return {
'Authorization': `Bearer ${token}`
};
}
// Authorization is injected by window.http.wrappedFetch
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
@@ -288,9 +282,7 @@ function setupEventListeners() {
async function loadAvailableFiles() {
try {
const response = await fetch('/api/import/available-files', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/import/available-files');
if (response.status === 401 || response.status === 403) {
console.error('Authentication error - redirecting to login');
@@ -335,9 +327,7 @@ async function loadAvailableFiles() {
async function loadImportStatus() {
try {
const response = await fetch('/api/import/status', {
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch('/api/import/status');
if (response.status === 401 || response.status === 403) {
console.error('Authentication error - redirecting to login');
@@ -422,9 +412,8 @@ async function validateFile() {
try {
showProgress(true, 'Validating file...');
const response = await fetch(`/api/import/validate/${fileType}`, {
const response = await window.http.wrappedFetch(`/api/import/validate/${fileType}`, {
method: 'POST',
headers: getAuthHeaders(),
body: formData
});
@@ -540,9 +529,8 @@ async function handleImport(event) {
try {
showProgress(true, 'Importing data...');
const response = await fetch(`/api/import/upload/${fileType}`, {
const response = await window.http.wrappedFetch(`/api/import/upload/${fileType}`, {
method: 'POST',
headers: getAuthHeaders(),
body: formData
});
@@ -629,10 +617,9 @@ async function clearTable() {
}
try {
const response = await fetch(`/api/import/clear/${fileType}`, {
method: 'DELETE',
headers: getAuthHeaders()
});
const response = await window.http.wrappedFetch(`/api/import/clear/${fileType}`, {
method: 'DELETE'
});
if (!response.ok) {
const error = await response.json();
@@ -769,11 +756,10 @@ async function handleBatchImport(event) {
try {
showProgress(true, 'Processing batch import...');
const response = await fetch('/api/import/batch-upload', {
method: 'POST',
headers: getAuthHeaders(),
body: formData
});
const response = await window.http.wrappedFetch('/api/import/batch-upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
const error = await response.json();

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Delphi Consulting Group Database System</title>
<script src="/static/js/fetch-wrapper.js"></script>
<script src="/static/js/alerts.js"></script>
<!-- Tailwind CSS -->
<link href="/static/css/tailwind.css" rel="stylesheet">
@@ -96,7 +97,7 @@
try {
console.log('Sending request to /api/auth/login');
const response = await fetch('/api/auth/login', {
const response = await window.http.wrappedFetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -154,11 +155,7 @@
async function checkTokenAndRedirect(token) {
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const response = await window.http.wrappedFetch('/api/auth/me');
if (response.ok) {
// Token is valid, redirect to customers page

View File

@@ -443,7 +443,7 @@ function initializeAdvancedSearch() {
async function loadSearchFacets() {
try {
const response = await fetch('/api/search/facets');
const response = await window.http.wrappedFetch('/api/search/facets');
if (!response.ok) throw new Error('Failed to load search facets');
facetsData = await response.json();
@@ -576,7 +576,7 @@ function setupKeyboardShortcuts() {
async function loadSearchSuggestions(query) {
try {
const response = await fetch(`/api/search/suggestions?q=${encodeURIComponent(query)}&limit=10`);
const response = await window.http.wrappedFetch(`/api/search/suggestions?q=${encodeURIComponent(query)}&limit=10`);
if (!response.ok) throw new Error('Failed to load suggestions');
const data = await response.json();
@@ -631,11 +631,8 @@ async function performSearch(offset = 0) {
currentSearchCriteria = criteria;
try {
const response = await fetch('/api/search/advanced', {
const response = await window.http.wrappedFetch('/api/search/advanced', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(criteria)
});

View File

@@ -295,11 +295,8 @@ let supportSystem = {
browser_info: this.browserInfo
};
const response = await fetch('/api/support/tickets', {
const response = await window.http.wrappedFetch('/api/support/tickets', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(ticketData)
});