Implement dynamic column reordering with numeric order field and realtime sync
- Add order field to all columns in DEFAULT_SETTINGS (1-47) - Settings page: reorder columns via numeric input or up/down arrows, autosave after 400ms - Estimator table: render columns in order specified by order field, refresh on settings updates - Normalize column orders on load and realtime updates with fallback for missing order - Add logging to trace column order changes through settings and estimator pages - Reset button reinitializes all settings to defaults with proper column ordering - Table reload prevents concurrent requests to avoid PocketBase auto-cancellation
This commit is contained in:
+145
-40
@@ -133,6 +133,8 @@
|
||||
const APP_NAME = 'Estimator Job List';
|
||||
let settingsRecordId = null;
|
||||
let currentSettings = null;
|
||||
let autosaveTimer = null;
|
||||
const AUTOSAVE_DELAY = 400;
|
||||
|
||||
// Column names from schema
|
||||
const COLUMNS = [
|
||||
@@ -153,8 +155,9 @@
|
||||
rowHeight: 40,
|
||||
fontSize: 14,
|
||||
stripedRows: true,
|
||||
columns: COLUMNS.map(col => ({
|
||||
columns: COLUMNS.map((col, idx) => ({
|
||||
name: col,
|
||||
order: idx + 1,
|
||||
readOnly: col === 'Active' || col === 'Due_Date_Counter' || col === 'Job_Number',
|
||||
width: col === 'Job_Number' ? 120 : col === 'Job_Full_Name' ? 250 : 150,
|
||||
visible: true,
|
||||
@@ -170,6 +173,79 @@
|
||||
}))
|
||||
};
|
||||
|
||||
function syncColumnOrderNumbers() {
|
||||
if (!currentSettings || !Array.isArray(currentSettings.columns)) return;
|
||||
currentSettings.columns = currentSettings.columns.map((col, idx) => ({
|
||||
...col,
|
||||
order: idx + 1
|
||||
}));
|
||||
}
|
||||
|
||||
async function reinitializeToDefaults() {
|
||||
try {
|
||||
// Create fresh settings from defaults
|
||||
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
||||
console.log('Reinitialized to defaults, saving...');
|
||||
|
||||
// Immediately save to PocketBase (not queued)
|
||||
const jsonString = JSON.stringify(currentSettings);
|
||||
await pb.collection('app_preferences_settings').update(settingsRecordId, {
|
||||
app_json_prefs: jsonString,
|
||||
app_txt_prefs: `Reset to defaults on ${new Date().toISOString()}`
|
||||
});
|
||||
|
||||
console.log('Settings reset to defaults and saved');
|
||||
renderSettings();
|
||||
alert('Settings reset to defaults successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error reinitializing settings:', error);
|
||||
alert('Error resetting to defaults: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function syncColumnOrderNumbers() {
|
||||
if (!currentSettings || !Array.isArray(currentSettings.columns)) return;
|
||||
currentSettings.columns = currentSettings.columns.map((col, idx) => ({
|
||||
...col,
|
||||
order: idx + 1
|
||||
}));
|
||||
}
|
||||
|
||||
function captureGlobalInputs() {
|
||||
currentSettings.rowHeight = parseInt(document.getElementById('rowHeight').value);
|
||||
currentSettings.fontSize = parseInt(document.getElementById('fontSize').value);
|
||||
currentSettings.stripedRows = document.getElementById('stripedRows').checked;
|
||||
}
|
||||
|
||||
async function saveSettings(isAuto = false) {
|
||||
try {
|
||||
captureGlobalInputs();
|
||||
syncColumnOrderNumbers();
|
||||
console.log('Saving columns with orders:', currentSettings.columns.map(c => ({ name: c.name, order: c.order, visible: c.visible })).slice(0, 5));
|
||||
const jsonString = JSON.stringify(currentSettings);
|
||||
await pb.collection('app_preferences_settings').update(settingsRecordId, {
|
||||
app_json_prefs: jsonString,
|
||||
app_txt_prefs: `Updated on ${new Date().toISOString()}`
|
||||
});
|
||||
console.log('Settings saved to PocketBase successfully');
|
||||
if (!isAuto) {
|
||||
alert('Settings saved successfully!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
if (!isAuto) {
|
||||
alert('Error saving settings: ' + error.message + '. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function queueAutosave() {
|
||||
if (autosaveTimer) {
|
||||
clearTimeout(autosaveTimer);
|
||||
}
|
||||
autosaveTimer = setTimeout(() => saveSettings(true), AUTOSAVE_DELAY);
|
||||
}
|
||||
|
||||
// Load settings from PocketBase
|
||||
async function loadSettings() {
|
||||
try {
|
||||
@@ -219,6 +295,7 @@
|
||||
console.log('Created new record:', settingsRecordId);
|
||||
}
|
||||
|
||||
syncColumnOrderNumbers();
|
||||
renderSettings();
|
||||
document.getElementById('loadingIndicator').classList.add('hidden');
|
||||
document.getElementById('settingsContainer').classList.remove('hidden');
|
||||
@@ -227,6 +304,7 @@
|
||||
console.error('Error details:', error.message, error.stack);
|
||||
alert('Error loading settings: ' + error.message + '. Using defaults.');
|
||||
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
||||
syncColumnOrderNumbers();
|
||||
renderSettings();
|
||||
document.getElementById('loadingIndicator').classList.add('hidden');
|
||||
document.getElementById('settingsContainer').classList.remove('hidden');
|
||||
@@ -250,19 +328,32 @@
|
||||
colDiv.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800">${col.name}</h3>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" ${col.visible ? 'checked' : ''} data-col-index="${index}" data-field="visible" class="w-4 h-4">
|
||||
<span class="text-sm text-gray-700">Visible</span>
|
||||
</label>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="flex flex-col text-xs text-gray-500">
|
||||
<button class="hover:text-primary" data-col-index="${index}" data-action="move-up" title="Move up">▲</button>
|
||||
<button class="hover:text-primary" data-col-index="${index}" data-action="move-down" title="Move down">▼</button>
|
||||
</div>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" ${col.visible ? 'checked' : ''} data-col-index="${index}" data-field="visible" class="w-4 h-4">
|
||||
<span class="text-sm text-gray-700">Visible</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Column Width (px)</label>
|
||||
<input type="number" value="${col.width}" min="50" max="500"
|
||||
data-col-index="${index}" data-field="width"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-primary text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Order</label>
|
||||
<input type="number" value="${col.order ?? index + 1}" min="1" max="${currentSettings.columns.length}"
|
||||
data-col-index="${index}" data-action="set-order"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-primary text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center space-x-2 mt-6">
|
||||
@@ -379,6 +470,9 @@
|
||||
} else {
|
||||
currentSettings.columns[colIndex][field] = field === 'width' ? parseInt(value) : value;
|
||||
console.log('Updated column field:', field, currentSettings.columns[colIndex][field]);
|
||||
if (field === 'visible' || field === 'readOnly' || field === 'width') {
|
||||
queueAutosave();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -388,15 +482,44 @@
|
||||
el.addEventListener('input', handler);
|
||||
});
|
||||
|
||||
// Add/Remove choice buttons
|
||||
document.querySelectorAll('[data-action]').forEach(btn => {
|
||||
// Add/Remove choice buttons and column move / set-order
|
||||
document.querySelectorAll('[data-action]').forEach(el => {
|
||||
const handler = (e) => {
|
||||
const action = e.target.dataset.action;
|
||||
const colIndex = parseInt(e.target.dataset.colIndex);
|
||||
|
||||
console.log('Action:', action, 'colIndex:', colIndex);
|
||||
|
||||
if (action === 'add-choice') {
|
||||
if (action === 'set-order') {
|
||||
const desired = Math.max(1, Math.min(currentSettings.columns.length, parseInt(e.target.value || '0', 10)));
|
||||
const targetIndex = desired - 1;
|
||||
if (targetIndex === colIndex) return;
|
||||
const [col] = currentSettings.columns.splice(colIndex, 1);
|
||||
currentSettings.columns.splice(targetIndex, 0, col);
|
||||
console.log('Moved column', col.name, 'from index', colIndex, 'to', targetIndex);
|
||||
syncColumnOrderNumbers();
|
||||
console.log('Column orders after sync:', currentSettings.columns.map(c => ({ name: c.name, order: c.order })).slice(0, 5));
|
||||
renderSettings();
|
||||
queueAutosave();
|
||||
} else if (action === 'move-up') {
|
||||
if (colIndex > 0) {
|
||||
const tmp = currentSettings.columns[colIndex - 1];
|
||||
currentSettings.columns[colIndex - 1] = currentSettings.columns[colIndex];
|
||||
currentSettings.columns[colIndex] = tmp;
|
||||
syncColumnOrderNumbers();
|
||||
renderSettings();
|
||||
queueAutosave();
|
||||
}
|
||||
} else if (action === 'move-down') {
|
||||
if (colIndex < currentSettings.columns.length - 1) {
|
||||
const tmp = currentSettings.columns[colIndex + 1];
|
||||
currentSettings.columns[colIndex + 1] = currentSettings.columns[colIndex];
|
||||
currentSettings.columns[colIndex] = tmp;
|
||||
syncColumnOrderNumbers();
|
||||
renderSettings();
|
||||
queueAutosave();
|
||||
}
|
||||
} else if (action === 'add-choice') {
|
||||
if (!currentSettings.columns[colIndex].choices) {
|
||||
currentSettings.columns[colIndex].choices = [];
|
||||
}
|
||||
@@ -408,54 +531,36 @@
|
||||
});
|
||||
console.log('Added choice, total:', currentSettings.columns[colIndex].choices.length);
|
||||
renderSettings();
|
||||
queueAutosave();
|
||||
} else if (action === 'remove-choice') {
|
||||
const choiceIndex = parseInt(e.target.dataset.choiceIndex);
|
||||
currentSettings.columns[colIndex].choices.splice(choiceIndex, 1);
|
||||
console.log('Removed choice at index:', choiceIndex);
|
||||
renderSettings();
|
||||
queueAutosave();
|
||||
}
|
||||
};
|
||||
|
||||
btn.removeEventListener('click', handler);
|
||||
btn.addEventListener('click', handler);
|
||||
// Use click for buttons; change (not input) for numeric order to avoid immediate rerender while typing
|
||||
el.removeEventListener('click', handler);
|
||||
el.removeEventListener('change', handler);
|
||||
if (el.tagName === 'INPUT' && el.type === 'number' && el.dataset.action === 'set-order') {
|
||||
el.addEventListener('change', handler);
|
||||
} else {
|
||||
el.addEventListener('click', handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save settings
|
||||
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
// Update global settings
|
||||
currentSettings.rowHeight = parseInt(document.getElementById('rowHeight').value);
|
||||
currentSettings.fontSize = parseInt(document.getElementById('fontSize').value);
|
||||
currentSettings.stripedRows = document.getElementById('stripedRows').checked;
|
||||
|
||||
console.log('Saving settings:', currentSettings);
|
||||
console.log('Settings record ID:', settingsRecordId);
|
||||
|
||||
const jsonString = JSON.stringify(currentSettings);
|
||||
console.log('JSON string length:', jsonString.length);
|
||||
|
||||
// Save to PocketBase
|
||||
const updated = await pb.collection('app_preferences_settings').update(settingsRecordId, {
|
||||
app_json_prefs: jsonString,
|
||||
app_txt_prefs: `Updated on ${new Date().toISOString()}`
|
||||
});
|
||||
|
||||
console.log('Settings saved successfully:', updated);
|
||||
alert('Settings saved successfully!');
|
||||
window.location.href = '/estimatortable.html';
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
console.error('Error details:', error.message, error.stack);
|
||||
alert('Error saving settings: ' + error.message + '. Please try again.');
|
||||
}
|
||||
await saveSettings(false);
|
||||
});
|
||||
|
||||
// Reset to defaults
|
||||
document.getElementById('resetBtn').addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to reset all settings to defaults?')) {
|
||||
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
||||
renderSettings();
|
||||
if (confirm('Are you sure you want to reset all settings to defaults? This will reset all columns to their original order.')) {
|
||||
reinitializeToDefaults();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user