Fix syntax error and reduce search section spacing
This commit is contained in:
+176
-43
@@ -202,12 +202,34 @@
|
||||
|
||||
<!-- Table Container -->
|
||||
<div id="tableContainer" class="hidden">
|
||||
<!-- Header Section -->
|
||||
<div class="header-section bg-white rounded-lg shadow-2xl p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-800">Estimator Job Table</h1>
|
||||
<p class="text-gray-600 mt-1">Scroll horizontally and vertically • First two columns are frozen</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-2xl p-4" style="display: flex; flex-direction: column; flex: 1; min-height: 0;">
|
||||
<!-- Search Section -->
|
||||
<div class="bg-white rounded-lg shadow-2xl px-4 py-2 mt-2">
|
||||
<div class="flex justify-end">
|
||||
<div style="width: 400px;">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="searchInput"
|
||||
placeholder="Search jobs..."
|
||||
class="w-full px-4 py-1.5 pl-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<svg class="absolute left-3 top-2 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<span id="searchResultCount" class="absolute right-3 top-2 text-sm text-gray-600"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Section -->
|
||||
<div class="bg-white rounded-lg shadow-2xl p-4 mt-2" style="display: flex; flex-direction: column; flex: 1; min-height: 0;">
|
||||
<div class="table-container">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
@@ -301,6 +323,8 @@
|
||||
let appSettingsUnsubscribe = null;
|
||||
let tableReloadTimer = null;
|
||||
let isLoadingTableData = false;
|
||||
let allRecords = []; // Store all records for filtering
|
||||
let currentSearchTerm = '';
|
||||
|
||||
function normalizeColumnOrders(columns) {
|
||||
if (!Array.isArray(columns)) return [];
|
||||
@@ -342,13 +366,17 @@
|
||||
// Load app settings from PocketBase (global settings)
|
||||
async function loadAppSettings() {
|
||||
try {
|
||||
console.log('Loading app settings for:', APP_NAME);
|
||||
const records = await pb.collection('app_preferences_settings').getFullList({
|
||||
filter: `app_name = "${APP_NAME}"`
|
||||
});
|
||||
|
||||
console.log('Settings records found:', records.length);
|
||||
|
||||
if (records.length > 0) {
|
||||
const settingsData = records[0].app_json_prefs;
|
||||
appSettingsRecordId = records[0].id;
|
||||
console.log('Settings record ID:', appSettingsRecordId);
|
||||
if (settingsData) {
|
||||
appSettings = typeof settingsData === 'string' ? JSON.parse(settingsData) : settingsData;
|
||||
// Ensure all columns have order field
|
||||
@@ -366,11 +394,16 @@
|
||||
console.log('Loaded app settings:', appSettings);
|
||||
applyGlobalSettings();
|
||||
renderTableHeader();
|
||||
scheduleTableReload();
|
||||
} else {
|
||||
console.log('No settings data in record');
|
||||
}
|
||||
} else {
|
||||
console.log('No settings record found, using defaults');
|
||||
renderTableHeader();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading app settings:', error);
|
||||
renderTableHeader(); // Still render with defaults
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +448,12 @@
|
||||
// Render table header dynamically based on current settings
|
||||
function renderTableHeader() {
|
||||
const headerRow = document.querySelector('.data-table thead tr');
|
||||
if (!headerRow) {
|
||||
console.error('Header row not found');
|
||||
return;
|
||||
}
|
||||
const visibleColumns = getVisibleColumns();
|
||||
console.log('Rendering header with', visibleColumns.length, 'columns');
|
||||
headerRow.innerHTML = visibleColumns.map(col => `
|
||||
<th class="px-4 py-3 text-left font-semibold whitespace-nowrap">${prettyLabel(col.name)}</th>
|
||||
`).join('');
|
||||
@@ -498,8 +536,11 @@
|
||||
// Ensure header reflects current order/visibility
|
||||
renderTableHeader();
|
||||
|
||||
// Re-render body to reflect new order/visibility
|
||||
scheduleTableReload();
|
||||
// Re-render body only if data is already loaded
|
||||
if (allRecords && allRecords.length > 0) {
|
||||
renderTableRows();
|
||||
}
|
||||
// Note: Don't call scheduleTableReload here - it will be called after initial load
|
||||
}
|
||||
|
||||
// Get column settings
|
||||
@@ -521,12 +562,23 @@
|
||||
async function checkAuth() {
|
||||
if (pb.authStore.isValid) {
|
||||
// Already logged in
|
||||
console.log('User authenticated, loading...');
|
||||
loadingIndicator.classList.remove('hidden');
|
||||
|
||||
await loadAppSettings();
|
||||
console.log('Settings loaded');
|
||||
|
||||
await startSettingsWatch();
|
||||
console.log('Settings watch started');
|
||||
|
||||
showTable();
|
||||
console.log('Table shown');
|
||||
|
||||
await loadTableData();
|
||||
console.log('Data loaded');
|
||||
} else {
|
||||
// Need to login
|
||||
console.log('User not authenticated');
|
||||
loginContainer.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
@@ -536,9 +588,11 @@
|
||||
try {
|
||||
loginBtn.disabled = true;
|
||||
loginBtn.textContent = 'Logging in...';
|
||||
loadingIndicator.classList.remove('hidden');
|
||||
|
||||
const authData = await pb.collection('users').authWithOAuth2({ provider: 'microsoft' });
|
||||
|
||||
console.log('Login successful');
|
||||
await loadAppSettings();
|
||||
await startSettingsWatch();
|
||||
showTable();
|
||||
@@ -548,6 +602,7 @@
|
||||
alert('Login failed. Please try again.');
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = 'Login with Microsoft';
|
||||
loadingIndicator.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -559,6 +614,9 @@
|
||||
userDisplay.classList.remove('hidden');
|
||||
userDisplayName.textContent = pb.authStore.model.name || pb.authStore.model.email || 'User';
|
||||
}
|
||||
|
||||
// Setup search listener after table is shown
|
||||
setupSearchListener();
|
||||
}
|
||||
|
||||
// Settings button handler
|
||||
@@ -803,46 +861,13 @@
|
||||
throw new Error(result.message || 'Failed to fetch jobs');
|
||||
}
|
||||
|
||||
const records = result.data;
|
||||
console.log(`Loaded ${records.length} jobs ${result.cached ? 'from cache' : 'from PocketBase'}`);
|
||||
allRecords = result.data; // Store all records for filtering
|
||||
console.log(`Loaded ${allRecords.length} jobs ${result.cached ? 'from cache' : 'from PocketBase'}`);
|
||||
console.log('First record:', allRecords[0]);
|
||||
|
||||
tableBody.innerHTML = '';
|
||||
const visibleColumns = getVisibleColumns();
|
||||
|
||||
records.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'border-b border-gray-200';
|
||||
|
||||
const rowIndex = Array.from(tableBody.children).length;
|
||||
|
||||
const cellsHtml = visibleColumns.map((col, idx) => {
|
||||
const columnName = col.name;
|
||||
const value = record[columnName];
|
||||
const titleValue = getCellTitle(columnName, record);
|
||||
const className = columnClassMap[columnName] || 'px-4 py-3';
|
||||
let style = '';
|
||||
const colSettings = getColumnSettings(columnName);
|
||||
if (colSettings && value && colSettings.choices && colSettings.choices.length > 0) {
|
||||
const choice = colSettings.choices.find(c => c.value === value);
|
||||
if (choice) {
|
||||
const bgColor = rowIndex % 2 === 0 ? choice.bgColor : choice.bgColorAlt;
|
||||
style += `background-color: ${bgColor} !important;`;
|
||||
if (choice.fontStyle === 'bold') {
|
||||
style += 'font-weight: bold;';
|
||||
} else if (choice.fontStyle === 'italic') {
|
||||
style += 'font-style: italic;';
|
||||
}
|
||||
}
|
||||
}
|
||||
const styleAttr = style ? `style="${style}"` : '';
|
||||
const titleAttr = titleValue ? ` title="${titleValue}"` : '';
|
||||
const content = renderCellContent(columnName, record, value);
|
||||
return `<td class="${className}" ${styleAttr}${titleAttr}>${content}</td>`;
|
||||
}).join('');
|
||||
|
||||
row.innerHTML = cellsHtml;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
// Render with current search filter
|
||||
renderTableRows();
|
||||
console.log('Render complete');
|
||||
|
||||
isLoadingTableData = false;
|
||||
loadingIndicator.classList.add('hidden');
|
||||
@@ -854,6 +879,114 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Render table rows with optional filtering
|
||||
function renderTableRows() {
|
||||
if (!tableBody) {
|
||||
console.error('tableBody element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allRecords || allRecords.length === 0) {
|
||||
console.log('No records to render');
|
||||
tableBody.innerHTML = '<tr><td colspan="50" class="px-4 py-8 text-center text-gray-500">No data available</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = '';
|
||||
const visibleColumns = getVisibleColumns();
|
||||
|
||||
// Filter records based on search term
|
||||
const filteredRecords = filterRecords(allRecords, currentSearchTerm);
|
||||
|
||||
// Update search result count
|
||||
const searchResultCount = document.getElementById('searchResultCount');
|
||||
if (searchResultCount) {
|
||||
if (currentSearchTerm.trim()) {
|
||||
searchResultCount.textContent = `${filteredRecords.length} of ${allRecords.length}`;
|
||||
} else {
|
||||
searchResultCount.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredRecords.length === 0) {
|
||||
tableBody.innerHTML = '<tr><td colspan="50" class="px-4 py-8 text-center text-gray-500">No matching results</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
filteredRecords.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'border-b border-gray-200';
|
||||
|
||||
const rowIndex = Array.from(tableBody.children).length;
|
||||
|
||||
const cellsHtml = visibleColumns.map((col, idx) => {
|
||||
const columnName = col.name;
|
||||
const value = record[columnName];
|
||||
const titleValue = getCellTitle(columnName, record);
|
||||
const className = columnClassMap[columnName] || 'px-4 py-3';
|
||||
let style = '';
|
||||
const colSettings = getColumnSettings(columnName);
|
||||
if (colSettings && value && colSettings.choices && colSettings.choices.length > 0) {
|
||||
const choice = colSettings.choices.find(c => c.value === value);
|
||||
if (choice) {
|
||||
const bgColor = rowIndex % 2 === 0 ? choice.bgColor : choice.bgColorAlt;
|
||||
style += `background-color: ${bgColor} !important;`;
|
||||
if (choice.fontStyle === 'bold') {
|
||||
style += 'font-weight: bold;';
|
||||
} else if (choice.fontStyle === 'italic') {
|
||||
style += 'font-style: italic;';
|
||||
}
|
||||
}
|
||||
}
|
||||
const styleAttr = style ? `style="${style}"` : '';
|
||||
const titleAttr = titleValue ? ` title="${titleValue}"` : '';
|
||||
const content = renderCellContent(columnName, record, value);
|
||||
return `<td class="${className}" ${styleAttr}${titleAttr}>${content}</td>`;
|
||||
}).join('');
|
||||
|
||||
row.innerHTML = cellsHtml;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter records based on search term
|
||||
function filterRecords(records, searchTerm) {
|
||||
if (!searchTerm || !searchTerm.trim()) {
|
||||
return records;
|
||||
}
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
const searchFields = [
|
||||
'Job_Number',
|
||||
'Job_Full_Name',
|
||||
'Job_Address',
|
||||
'Estimator',
|
||||
'Office_Rep',
|
||||
'Project_Manager',
|
||||
'Company_Client',
|
||||
'Contact_Person'
|
||||
];
|
||||
|
||||
return records.filter(record => {
|
||||
return searchFields.some(field => {
|
||||
const value = record[field];
|
||||
if (!value) return false;
|
||||
return String(value).toLowerCase().includes(term);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Setup search input listener
|
||||
function setupSearchListener() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (!searchInput) return;
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
currentSearchTerm = e.target.value;
|
||||
renderTableRows();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle checkbox changes
|
||||
document.addEventListener('change', async (e) => {
|
||||
if (e.target.type === 'checkbox' && e.target.dataset.field) {
|
||||
|
||||
Reference in New Issue
Block a user