fixing rolodex and search
This commit is contained in:
@@ -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 %}
|
||||
Reference in New Issue
Block a user