821fafd990
- 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
588 lines
25 KiB
HTML
588 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Estimator Job List - Settings</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: '#4c51bf',
|
|
secondary: '#5b21b6',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
body {
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.settings-container {
|
|
max-width: 1400px;
|
|
}
|
|
|
|
.color-preview {
|
|
width: 30px;
|
|
height: 30px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.choice-item {
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
margin-bottom: 8px;
|
|
background: #f9fafb;
|
|
}
|
|
|
|
.section-header {
|
|
font-weight: 600;
|
|
color: #374151;
|
|
margin-bottom: 12px;
|
|
padding-bottom: 8px;
|
|
border-bottom: 2px solid #e5e7eb;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen bg-gradient-to-br from-primary to-secondary p-6 font-sans">
|
|
|
|
<!-- Header -->
|
|
<div class="bg-white rounded-lg shadow-2xl p-6 mb-6 settings-container mx-auto">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-800">Estimator Job List Settings</h1>
|
|
<p class="text-gray-600 mt-1">Configure table appearance and behavior</p>
|
|
</div>
|
|
<button id="closeBtn" class="text-gray-500 hover:text-gray-700 text-3xl font-bold">×</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Indicator -->
|
|
<div id="loadingIndicator" class="text-center text-white">
|
|
<div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
|
<p class="mt-4">Loading settings...</p>
|
|
</div>
|
|
|
|
<!-- Settings Container -->
|
|
<div id="settingsContainer" class="hidden settings-container mx-auto">
|
|
|
|
<!-- Global Settings -->
|
|
<div class="bg-white rounded-lg shadow-2xl p-6 mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Global Settings</h2>
|
|
|
|
<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">Row Height (px)</label>
|
|
<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">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Font Size (px)</label>
|
|
<input type="number" id="fontSize" min="10" max="24"
|
|
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">Striped Rows</label>
|
|
<label class="flex items-center space-x-2 mt-2">
|
|
<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>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Column Settings -->
|
|
<div class="bg-white rounded-lg shadow-2xl p-6 mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-4">Column Settings</h2>
|
|
<p class="text-gray-600 mb-4">Configure individual column properties, widths, and dropdown choices</p>
|
|
|
|
<div id="columnsList" class="space-y-4">
|
|
<!-- Column settings will be dynamically generated here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="bg-white rounded-lg shadow-2xl p-6 flex justify-between items-center">
|
|
<button id="resetBtn" class="px-6 py-3 bg-gray-500 text-white font-semibold rounded-lg hover:bg-gray-600 transition-colors">
|
|
Reset to Defaults
|
|
</button>
|
|
<div class="space-x-4">
|
|
<button id="cancelBtn" class="px-6 py-3 bg-gray-300 text-gray-800 font-semibold rounded-lg hover:bg-gray-400 transition-colors">
|
|
Cancel
|
|
</button>
|
|
<button id="saveBtn" class="px-6 py-3 bg-gradient-to-br from-primary to-secondary text-white font-semibold rounded-lg hover:opacity-95 transition-opacity shadow-lg">
|
|
Save Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/pocketbase/dist/pocketbase.umd.js"></script>
|
|
<script>
|
|
const pb = new PocketBase('https://pocketbase.ccllc.pro');
|
|
|
|
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 = [
|
|
'Job_Number', 'Job_Full_Name', 'Active', 'Added_To_Calendar', 'Attachment_List',
|
|
'Company_Client', 'Contact_Person', 'Docs_Uploaded', 'Due_Date', 'Due_Date_Counter',
|
|
'Due_Date_Source', 'Due_Time', 'Email', 'Estimate_Approved', 'Estimate_Draft',
|
|
'Estimate_Reviewed', 'Estimator', 'Flag', 'Has_Attachments', 'Job_Address',
|
|
'Job_Codes', 'Job_Division', 'Job_Folder_Link', 'Job_Name', 'Job_Number_Parent',
|
|
'Job_QB_Link', 'Job_Status', 'Job_Type', 'Note_Ref', 'Notes', 'Office_Rep',
|
|
'PSwift_Uploaded', 'Phone_Number', 'Project_Manager', 'QB_Created',
|
|
'Schedule_Confidence', 'Start_Date', 'Submission_Date', 'Tax_Exempt',
|
|
'Tax_Exempt_Docs_Uploaded', 'Voxer_Created', 'Voxer_Link', 'last_note_at',
|
|
'latest_note_summary', 'note_count', 'note_thread_text'
|
|
];
|
|
|
|
// Default settings
|
|
const DEFAULT_SETTINGS = {
|
|
rowHeight: 40,
|
|
fontSize: 14,
|
|
stripedRows: true,
|
|
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,
|
|
choices: col === 'Job_Status' ? [
|
|
{ value: 'Estimating', bgColor: '#fef3c7', bgColorAlt: '#fde68a', fontStyle: 'normal' },
|
|
{ value: 'Est Sent', bgColor: '#dbeafe', bgColorAlt: '#bfdbfe', fontStyle: 'normal' },
|
|
{ value: 'Est Hold', bgColor: '#fee2e2', bgColorAlt: '#fecaca', fontStyle: 'normal' },
|
|
{ value: 'Follow Up', bgColor: '#e0e7ff', bgColorAlt: '#c7d2fe', fontStyle: 'normal' },
|
|
{ value: 'Awarded', bgColor: '#dcfce7', bgColorAlt: '#bbf7d0', fontStyle: 'bold' },
|
|
{ value: 'In Progress', bgColor: '#d1fae5', bgColorAlt: '#a7f3d0', fontStyle: 'normal' },
|
|
{ value: 'Billed In Progress', bgColor: '#cffafe', bgColorAlt: '#a5f3fc', fontStyle: 'normal' }
|
|
] : []
|
|
}))
|
|
};
|
|
|
|
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 {
|
|
document.getElementById('loadingIndicator').classList.remove('hidden');
|
|
|
|
console.log('Loading settings for app:', APP_NAME);
|
|
|
|
// Try to find existing settings record
|
|
const records = await pb.collection('app_preferences_settings').getFullList({
|
|
filter: `app_name = "${APP_NAME}"`
|
|
});
|
|
|
|
console.log('Found records:', records.length);
|
|
|
|
if (records.length > 0) {
|
|
settingsRecordId = records[0].id;
|
|
console.log('Settings record ID:', settingsRecordId);
|
|
console.log('Raw app_json_prefs:', records[0].app_json_prefs);
|
|
|
|
// Try to parse the JSON
|
|
try {
|
|
if (records[0].app_json_prefs && typeof records[0].app_json_prefs === 'string') {
|
|
currentSettings = JSON.parse(records[0].app_json_prefs);
|
|
console.log('Parsed settings successfully');
|
|
} else if (records[0].app_json_prefs && typeof records[0].app_json_prefs === 'object') {
|
|
// Already an object, no need to parse
|
|
currentSettings = records[0].app_json_prefs;
|
|
console.log('Settings already an object');
|
|
} else {
|
|
console.log('No settings found, using defaults');
|
|
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Error parsing settings JSON:', parseError);
|
|
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
|
}
|
|
} else {
|
|
console.log('No existing record, creating new one');
|
|
// Create new record with defaults
|
|
const newRecord = await pb.collection('app_preferences_settings').create({
|
|
app_name: APP_NAME,
|
|
app_json_prefs: JSON.stringify(DEFAULT_SETTINGS),
|
|
app_txt_prefs: 'Default settings'
|
|
});
|
|
settingsRecordId = newRecord.id;
|
|
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
|
console.log('Created new record:', settingsRecordId);
|
|
}
|
|
|
|
syncColumnOrderNumbers();
|
|
renderSettings();
|
|
document.getElementById('loadingIndicator').classList.add('hidden');
|
|
document.getElementById('settingsContainer').classList.remove('hidden');
|
|
} catch (error) {
|
|
console.error('Error loading settings:', error);
|
|
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');
|
|
}
|
|
}
|
|
|
|
// Render settings UI
|
|
function renderSettings() {
|
|
// Global settings
|
|
document.getElementById('rowHeight').value = currentSettings.rowHeight || 40;
|
|
document.getElementById('fontSize').value = currentSettings.fontSize || 14;
|
|
document.getElementById('stripedRows').checked = currentSettings.stripedRows !== false;
|
|
|
|
// Column settings
|
|
const columnsList = document.getElementById('columnsList');
|
|
columnsList.innerHTML = '';
|
|
|
|
currentSettings.columns.forEach((col, index) => {
|
|
const colDiv = document.createElement('div');
|
|
colDiv.className = 'border border-gray-200 rounded-lg p-4 bg-gray-50';
|
|
colDiv.innerHTML = `
|
|
<div class="flex justify-between items-center mb-3">
|
|
<h3 class="text-lg font-semibold text-gray-800">${col.name}</h3>
|
|
<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-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">
|
|
<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>
|
|
</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">Dropdown Choices</label>
|
|
<button class="text-sm text-primary hover:text-secondary font-medium" data-col-index="${index}" data-action="add-choice">
|
|
+ Add Choice
|
|
</button>
|
|
</div>
|
|
<div id="choices-${index}" class="space-y-2">
|
|
${renderChoices(col.choices || [], index)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
columnsList.appendChild(colDiv);
|
|
});
|
|
|
|
attachEventListeners();
|
|
}
|
|
|
|
// Render choices for a column
|
|
function renderChoices(choices, colIndex) {
|
|
if (!choices || choices.length === 0) {
|
|
return '<p class="text-sm text-gray-500 italic">No choices defined</p>';
|
|
}
|
|
|
|
return choices.map((choice, choiceIndex) => `
|
|
<div class="choice-item">
|
|
<div class="grid grid-cols-12 gap-3 items-center">
|
|
<div class="col-span-3">
|
|
<input type="text" value="${choice.value}"
|
|
data-col-index="${colIndex}" data-choice-index="${choiceIndex}" data-field="choice-value"
|
|
class="w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="Value">
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<label class="text-xs text-gray-600 block mb-1">Main Color</label>
|
|
<div class="flex items-center space-x-2">
|
|
<input type="color" value="${choice.bgColor}"
|
|
data-col-index="${colIndex}" data-choice-index="${choiceIndex}" data-field="choice-bgColor"
|
|
class="w-8 h-8 rounded cursor-pointer">
|
|
<span class="text-xs text-gray-500">${choice.bgColor}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<label class="text-xs text-gray-600 block mb-1">Alt Color</label>
|
|
<div class="flex items-center space-x-2">
|
|
<input type="color" value="${choice.bgColorAlt}"
|
|
data-col-index="${colIndex}" data-choice-index="${choiceIndex}" data-field="choice-bgColorAlt"
|
|
class="w-8 h-8 rounded cursor-pointer">
|
|
<span class="text-xs text-gray-500">${choice.bgColorAlt}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<label class="text-xs text-gray-600 block mb-1">Font Style</label>
|
|
<select data-col-index="${colIndex}" data-choice-index="${choiceIndex}" data-field="choice-fontStyle"
|
|
class="w-full px-2 py-1 border border-gray-300 rounded text-sm">
|
|
<option value="normal" ${choice.fontStyle === 'normal' ? 'selected' : ''}>Normal</option>
|
|
<option value="bold" ${choice.fontStyle === 'bold' ? 'selected' : ''}>Bold</option>
|
|
<option value="italic" ${choice.fontStyle === 'italic' ? 'selected' : ''}>Italic</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<div class="flex items-center space-x-2 h-full">
|
|
<div class="color-preview" style="background-color: ${choice.bgColor}; font-style: ${choice.fontStyle}; font-weight: ${choice.fontStyle === 'bold' ? 'bold' : 'normal'};">
|
|
<span class="text-xs flex items-center justify-center h-full">A</span>
|
|
</div>
|
|
<div class="color-preview" style="background-color: ${choice.bgColorAlt}; font-style: ${choice.fontStyle}; font-weight: ${choice.fontStyle === 'bold' ? 'bold' : 'normal'};">
|
|
<span class="text-xs flex items-center justify-center h-full">A</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-1 text-right">
|
|
<button class="text-red-500 hover:text-red-700 font-bold text-lg"
|
|
data-col-index="${colIndex}" data-choice-index="${choiceIndex}" data-action="remove-choice">
|
|
×
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// Attach event listeners
|
|
function attachEventListeners() {
|
|
// Column field changes
|
|
document.querySelectorAll('[data-col-index][data-field]').forEach(el => {
|
|
const handler = (e) => {
|
|
const colIndex = parseInt(e.target.dataset.colIndex);
|
|
const field = e.target.dataset.field;
|
|
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
|
|
|
console.log('Field changed:', field, 'value:', value, 'colIndex:', colIndex);
|
|
|
|
if (field === 'choice-value' || field === 'choice-bgColor' || field === 'choice-bgColorAlt' || field === 'choice-fontStyle') {
|
|
const choiceIndex = parseInt(e.target.dataset.choiceIndex);
|
|
const choiceField = field.replace('choice-', '');
|
|
if (!currentSettings.columns[colIndex].choices) {
|
|
currentSettings.columns[colIndex].choices = [];
|
|
}
|
|
currentSettings.columns[colIndex].choices[choiceIndex][choiceField] = value;
|
|
console.log('Updated choice:', currentSettings.columns[colIndex].choices[choiceIndex]);
|
|
} 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();
|
|
}
|
|
}
|
|
};
|
|
|
|
el.removeEventListener('change', handler);
|
|
el.removeEventListener('input', handler);
|
|
el.addEventListener('change', handler);
|
|
el.addEventListener('input', handler);
|
|
});
|
|
|
|
// 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 === '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 = [];
|
|
}
|
|
currentSettings.columns[colIndex].choices.push({
|
|
value: 'New Choice',
|
|
bgColor: '#ffffff',
|
|
bgColorAlt: '#f3f4f6',
|
|
fontStyle: 'normal'
|
|
});
|
|
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();
|
|
}
|
|
};
|
|
|
|
// 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 () => {
|
|
await saveSettings(false);
|
|
});
|
|
|
|
// Reset to defaults
|
|
document.getElementById('resetBtn').addEventListener('click', () => {
|
|
if (confirm('Are you sure you want to reset all settings to defaults? This will reset all columns to their original order.')) {
|
|
reinitializeToDefaults();
|
|
}
|
|
});
|
|
|
|
// Cancel/Close buttons
|
|
document.getElementById('cancelBtn').addEventListener('click', () => {
|
|
window.location.href = '/estimatortable.html';
|
|
});
|
|
|
|
document.getElementById('closeBtn').addEventListener('click', () => {
|
|
window.location.href = '/estimatortable.html';
|
|
});
|
|
|
|
// Initialize
|
|
if (pb.authStore.isValid) {
|
|
console.log('User is authenticated:', pb.authStore.model?.email);
|
|
loadSettings();
|
|
} else {
|
|
console.log('User not authenticated');
|
|
alert('Please login first');
|
|
window.location.href = '/estimatortable.html';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|