Compare commits

...

3 Commits

Author SHA1 Message Date
aewing ad4a4fdf4b Update to v1.0.0-alpha5
- Add hover tooltip column setting for truncated text
- Update Tax_Exempt_Docs_Uploaded label to 'Tax Exempt Uploads?'
2026-01-02 04:18:42 +00:00
aewing 5a34170454 Fix user display, add logout, footer, optimize performance
- Move userDisplay inside tableContainer with proper positioning
- Add logout button next to settings
- Add footer with user email (left) and version (right)
- Fix auth model loading on page refresh
- Remove cache clearing on checkbox changes for better performance
- Add 150ms debounce to search input
- Expand search to include Notes fields
- Suppress QUIC protocol console errors from realtime connection
- Optimize checkbox change handler
2026-01-02 02:07:33 +00:00
aewing 2365bb9b4d Fix syntax error and reduce search section spacing 2026-01-02 00:22:51 +00:00
4 changed files with 952 additions and 112 deletions
-20
View File
@@ -1,20 +0,0 @@
# Azure AD / Microsoft Authentication
CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
TENANT_ID=your-tenant-id
REDIRECT_URI=https://your-domain.com/auth/callback
# PocketBase Configuration
PB_DB=https://pocketbase.ccllc.pro
PB_COLLECTION=Job_Info_TestEnv
PB_AUTH_COLLECTION=Users
# Redis/Valkey Configuration (Caching & Search)
# Connect to the same Redis/Valkey instance as Job-Info-Prod
REDIS_URL=redis://localhost:6379
# For production, use the same connection string as Job-Info-Prod
# Example: REDIS_URL=redis://redis-server:6379
# Example with auth: REDIS_URL=redis://:password@redis-server:6379
# Optional
PORT=3025
+152 -2
View File
@@ -82,6 +82,7 @@
<label class="block text-sm font-medium text-gray-700 mb-2">Row Height (px)</label> <label class="block text-sm font-medium text-gray-700 mb-2">Row Height (px)</label>
<input type="number" id="rowHeight" min="30" max="100" <input type="number" id="rowHeight" min="30" max="100"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
<p class="text-xs text-gray-500 mt-1">Fixed height for all data rows</p>
</div> </div>
<div> <div>
@@ -91,11 +92,71 @@
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-2">Striped Rows</label> <label class="block text-sm font-medium text-gray-700 mb-2">Row Options</label>
<label class="flex items-center space-x-2 mt-2"> <label class="flex items-center space-x-2 mt-2">
<input type="checkbox" id="stripedRows" class="w-5 h-5 text-primary rounded"> <input type="checkbox" id="stripedRows" class="w-5 h-5 text-primary rounded">
<span class="text-sm text-gray-700">Enable alternating row colors</span> <span class="text-sm text-gray-700">Enable alternating row colors</span>
</label> </label>
<label class="flex items-center space-x-2 mt-2">
<input type="checkbox" id="fixedRowHeight" class="w-5 h-5 text-primary rounded">
<span class="text-sm text-gray-700">Fixed row height (no flex)</span>
</label>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Data Cell Vertical Alignment</label>
<select id="cellVerticalAlign"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="top">Top</option>
<option value="middle">Middle</option>
<option value="bottom">Bottom</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Header Vertical Alignment</label>
<select id="headerVerticalAlign"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="top">Top</option>
<option value="middle">Middle</option>
<option value="bottom">Bottom</option>
</select>
</div>
</div>
<div class="mt-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3">Border Settings</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Border Color</label>
<div class="flex items-center space-x-2">
<input type="color" id="borderColor"
class="w-16 h-10 rounded cursor-pointer border border-gray-300">
<input type="text" id="borderColorText"
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary text-sm"
placeholder="#e5e7eb">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Border Width (px)</label>
<input type="number" id="borderWidth" min="0" max="5"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Border Options</label>
<label class="flex items-center space-x-2 mt-2">
<input type="checkbox" id="showRowBorders" class="w-5 h-5 text-primary rounded">
<span class="text-sm text-gray-700">Show row borders</span>
</label>
<label class="flex items-center space-x-2 mt-2">
<input type="checkbox" id="showColumnBorders" class="w-5 h-5 text-primary rounded">
<span class="text-sm text-gray-700">Show column borders</span>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -215,6 +276,13 @@
currentSettings.rowHeight = parseInt(document.getElementById('rowHeight').value); currentSettings.rowHeight = parseInt(document.getElementById('rowHeight').value);
currentSettings.fontSize = parseInt(document.getElementById('fontSize').value); currentSettings.fontSize = parseInt(document.getElementById('fontSize').value);
currentSettings.stripedRows = document.getElementById('stripedRows').checked; currentSettings.stripedRows = document.getElementById('stripedRows').checked;
currentSettings.fixedRowHeight = document.getElementById('fixedRowHeight').checked;
currentSettings.cellVerticalAlign = document.getElementById('cellVerticalAlign').value;
currentSettings.headerVerticalAlign = document.getElementById('headerVerticalAlign').value;
currentSettings.borderColor = document.getElementById('borderColor').value;
currentSettings.borderWidth = parseInt(document.getElementById('borderWidth').value);
currentSettings.showRowBorders = document.getElementById('showRowBorders').checked;
currentSettings.showColumnBorders = document.getElementById('showColumnBorders').checked;
} }
async function saveSettings(isAuto = false) { async function saveSettings(isAuto = false) {
@@ -250,6 +318,8 @@
console.log('Settings saved to PocketBase successfully'); console.log('Settings saved to PocketBase successfully');
if (!isAuto) { if (!isAuto) {
alert('Settings saved successfully!'); alert('Settings saved successfully!');
// Redirect back to estimator table
window.location.href = '/estimatortable.html';
} }
} catch (error) { } catch (error) {
console.error('Error saving settings:', error); console.error('Error saving settings:', error);
@@ -347,6 +417,14 @@
document.getElementById('rowHeight').value = currentSettings.rowHeight || 40; document.getElementById('rowHeight').value = currentSettings.rowHeight || 40;
document.getElementById('fontSize').value = currentSettings.fontSize || 14; document.getElementById('fontSize').value = currentSettings.fontSize || 14;
document.getElementById('stripedRows').checked = currentSettings.stripedRows !== false; document.getElementById('stripedRows').checked = currentSettings.stripedRows !== false;
document.getElementById('fixedRowHeight').checked = currentSettings.fixedRowHeight !== false;
document.getElementById('cellVerticalAlign').value = currentSettings.cellVerticalAlign || 'middle';
document.getElementById('headerVerticalAlign').value = currentSettings.headerVerticalAlign || 'middle';
document.getElementById('borderColor').value = currentSettings.borderColor || '#e5e7eb';
document.getElementById('borderColorText').value = currentSettings.borderColor || '#e5e7eb';
document.getElementById('borderWidth').value = currentSettings.borderWidth || 1;
document.getElementById('showRowBorders').checked = currentSettings.showRowBorders !== false;
document.getElementById('showColumnBorders').checked = currentSettings.showColumnBorders !== false;
// Column settings // Column settings
const columnsList = document.getElementById('columnsList'); const columnsList = document.getElementById('columnsList');
@@ -386,10 +464,70 @@
</div> </div>
<div> <div>
<label class="flex items-center space-x-2 mt-6"> <label class="block text-sm font-medium text-gray-700 mb-1">Text Alignment</label>
<select data-col-index="${index}" data-field="alignment"
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-primary text-sm">
<option value="left" ${(col.alignment || 'left') === 'left' ? 'selected' : ''}>Left</option>
<option value="center" ${col.alignment === 'center' ? 'selected' : ''}>Center</option>
<option value="right" ${col.alignment === 'right' ? 'selected' : ''}>Right</option>
</select>
</div>
<div class="flex flex-col justify-center space-y-1">
<label class="flex items-center space-x-2">
<input type="checkbox" ${col.readOnly ? 'checked' : ''} data-col-index="${index}" data-field="readOnly" class="w-4 h-4"> <input type="checkbox" ${col.readOnly ? 'checked' : ''} data-col-index="${index}" data-field="readOnly" class="w-4 h-4">
<span class="text-sm text-gray-700">Read-only</span> <span class="text-sm text-gray-700">Read-only</span>
</label> </label>
<label class="flex items-center space-x-2">
<input type="checkbox" ${col.headerWrap ? 'checked' : ''} data-col-index="${index}" data-field="headerWrap" class="w-4 h-4">
<span class="text-sm text-gray-700">Wrap header</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" ${col.showFullTextOnHover ? 'checked' : ''} data-col-index="${index}" data-field="showFullTextOnHover" class="w-4 h-4">
<span class="text-sm text-gray-700">Hover tooltip</span>
</label>
</div>
</div>
<div class="mt-4">
<div class="flex justify-between items-center mb-2">
<label class="block text-sm font-medium text-gray-700">Boolean Value Formatting (for checkboxes)</label>
</div>
<div class="grid grid-cols-2 gap-4 mb-3">
<div class="border border-gray-200 rounded p-3">
<label class="text-xs font-medium text-gray-600 block mb-2">True Value Style</label>
<div class="grid grid-cols-2 gap-2">
<div>
<label class="text-xs text-gray-600 block mb-1">Background</label>
<input type="color" value="${col.trueBgColor || '#d1fae5'}"
data-col-index="${index}" data-field="trueBgColor"
class="w-full h-8 rounded cursor-pointer">
</div>
<div>
<label class="text-xs text-gray-600 block mb-1">Alt Background</label>
<input type="color" value="${col.trueBgColorAlt || '#a7f3d0'}"
data-col-index="${index}" data-field="trueBgColorAlt"
class="w-full h-8 rounded cursor-pointer">
</div>
</div>
</div>
<div class="border border-gray-200 rounded p-3">
<label class="text-xs font-medium text-gray-600 block mb-2">False Value Style</label>
<div class="grid grid-cols-2 gap-2">
<div>
<label class="text-xs text-gray-600 block mb-1">Background</label>
<input type="color" value="${col.falseBgColor || '#fee2e2'}"
data-col-index="${index}" data-field="falseBgColor"
class="w-full h-8 rounded cursor-pointer">
</div>
<div>
<label class="text-xs text-gray-600 block mb-1">Alt Background</label>
<input type="color" value="${col.falseBgColorAlt || '#fecaca'}"
data-col-index="${index}" data-field="falseBgColorAlt"
class="w-full h-8 rounded cursor-pointer">
</div>
</div>
</div>
</div> </div>
</div> </div>
@@ -602,6 +740,18 @@
document.getElementById('closeBtn').addEventListener('click', () => { document.getElementById('closeBtn').addEventListener('click', () => {
window.location.href = '/estimatortable.html'; window.location.href = '/estimatortable.html';
}); });
// Sync border color picker with text input
document.getElementById('borderColor').addEventListener('input', (e) => {
document.getElementById('borderColorText').value = e.target.value;
});
document.getElementById('borderColorText').addEventListener('input', (e) => {
const value = e.target.value;
if (/^#[0-9A-F]{6}$/i.test(value)) {
document.getElementById('borderColor').value = value;
}
});
// Initialize // Initialize
if (pb.authStore.isValid) { if (pb.authStore.isValid) {
+797 -87
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -113,12 +113,12 @@ app.post('/api/admin/login', async (c) => {
return c.json({ message: 'Missing email or password' }, 400); return c.json({ message: 'Missing email or password' }, 400);
} }
// Authenticate as PocketBase superuser via `_superusers` collection // Authenticate as regular user
const authData = await pb.collection('_superusers').authWithPassword(email, password); const authData = await pb.collection('users').authWithPassword(email, password);
if (!authData || !authData.token) { if (!authData || !authData.token) {
return c.json({ message: 'Invalid credentials' }, 401); return c.json({ message: 'Invalid credentials' }, 401);
} }
return c.json({ ok: true, token: authData.token, email: authData.record?.email, role: 'superuser' }); return c.json({ ok: true, token: authData.token, email: authData.record?.email });
} catch (err) { } catch (err) {
console.error('admin_login_error', { message: (err as Error)?.message }); console.error('admin_login_error', { message: (err as Error)?.message });
return c.json({ message: 'Login failed' }, 401); return c.json({ message: 'Login failed' }, 401);