Fix syntax error and reduce search section spacing

This commit is contained in:
2026-01-02 00:22:51 +00:00
parent 71e5070e6c
commit 2365bb9b4d
+176 -43
View File
@@ -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) {