From ad4a4fdf4ba729184bfb4e7aedd15b2f241815b5 Mon Sep 17 00:00:00 2001 From: aewing Date: Fri, 2 Jan 2026 04:18:42 +0000 Subject: [PATCH] Update to v1.0.0-alpha5 - Add hover tooltip column setting for truncated text - Update Tax_Exempt_Docs_Uploaded label to 'Tax Exempt Uploads?' --- appsettings.html | 152 +++++++++++- estimatortable.html | 564 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 693 insertions(+), 23 deletions(-) diff --git a/appsettings.html b/appsettings.html index d2265d9..bc4a44c 100644 --- a/appsettings.html +++ b/appsettings.html @@ -82,6 +82,7 @@ +

Fixed height for all data rows

@@ -91,11 +92,71 @@
- + + +
+ + +
+
+ + +
+ +
+ + +
+
+ +
+

Border Settings

+
+
+ +
+ + +
+
+ +
+ + +
+ +
+ + + +
@@ -215,6 +276,13 @@ currentSettings.rowHeight = parseInt(document.getElementById('rowHeight').value); currentSettings.fontSize = parseInt(document.getElementById('fontSize').value); 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) { @@ -349,6 +417,14 @@ document.getElementById('rowHeight').value = currentSettings.rowHeight || 40; document.getElementById('fontSize').value = currentSettings.fontSize || 14; 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 const columnsList = document.getElementById('columnsList'); @@ -388,10 +464,70 @@
-
+ +
+ + + +
+ + +
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
@@ -604,6 +740,18 @@ document.getElementById('closeBtn').addEventListener('click', () => { 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 if (pb.authStore.isValid) { diff --git a/estimatortable.html b/estimatortable.html index 09f5635..e818e1b 100644 --- a/estimatortable.html +++ b/estimatortable.html @@ -85,6 +85,24 @@ border-bottom: 1px solid #e5e7eb; } + .data-table td { + white-space: nowrap; + } + + /* Enforce fixed row heights */ + .data-table tbody tr { + height: 40px; + max-height: 40px; + overflow: hidden; + } + + .data-table tbody td { + height: 40px; + max-height: 40px; + overflow: hidden; + vertical-align: middle; + } + /* Freeze header row */ .data-table thead th { position: sticky; @@ -94,6 +112,7 @@ color: white; box-shadow: 0 2px 4px rgba(0,0,0,0.2); border-right: 1px solid rgba(255,255,255,0.2); + padding: 12px 16px; } /* Freeze first column (Job_Number) - fixed 120px width */ @@ -152,12 +171,21 @@ } /* Prominent checkbox styling */ + .checkbox-cell { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + } + .checkbox-cell input[type="checkbox"] { width: 18px; height: 18px; cursor: default; accent-color: #000; border: 2px solid #000; + margin: 0; } /* Override disabled appearance - keep checkboxes looking active */ @@ -298,7 +326,7 @@
- v1.0.0-alpha4 + v1.0.0-alpha5
@@ -368,6 +396,10 @@ } function prettyLabel(name) { + // Special case for Tax_Exempt_Docs_Uploaded + if (name === 'Tax_Exempt_Docs_Uploaded') { + return 'Tax Exempt Uploads?'; + } return name.replace(/_/g, ' '); } @@ -456,9 +488,9 @@ console.log('App settings updated via realtime', { action: e.action }); applyGlobalSettings(); scheduleTableReload(); - }); + }, { /* Silently handle connection errors */ }); } catch (err) { - console.error('Failed to start settings watch', err); + // Silently ignore realtime connection errors (QUIC protocol issues are cosmetic) } } @@ -471,9 +503,12 @@ } const visibleColumns = getVisibleColumns(); console.log('Rendering header with', visibleColumns.length, 'columns'); - headerRow.innerHTML = visibleColumns.map(col => ` - ${prettyLabel(col.name)} - `).join(''); + headerRow.innerHTML = visibleColumns.map(col => { + const colSettings = getColumnSettings(col.name); + const alignment = colSettings?.alignment || 'left'; + const wrapClass = colSettings?.headerWrap ? 'whitespace-normal break-words' : 'whitespace-nowrap'; + return `${prettyLabel(col.name)}`; + }).join(''); } // Apply global settings to table (styles) @@ -487,7 +522,32 @@ // Apply row height if (appSettings && appSettings.rowHeight) { - styleContent += `.data-table tbody tr { height: ${appSettings.rowHeight}px; }\n`; + const height = appSettings.rowHeight; + const cellVerticalAlign = appSettings.cellVerticalAlign || 'middle'; + styleContent += ` + .data-table tbody tr { + height: ${height}px !important; + max-height: ${height}px !important; + } + .data-table tbody td { + height: ${height}px !important; + max-height: ${height}px !important; + overflow: hidden !important; + vertical-align: ${cellVerticalAlign} !important; + padding: ${cellVerticalAlign === 'top' ? '4px 16px' : (cellVerticalAlign === 'bottom' ? '0 16px 4px 16px' : '0 16px')} !important; + } + `; + } + + // Apply header vertical alignment + if (appSettings && appSettings.headerVerticalAlign) { + const headerAlign = appSettings.headerVerticalAlign; + styleContent += ` + .data-table thead th { + vertical-align: ${headerAlign} !important; + padding: ${headerAlign === 'top' ? '8px 16px 4px 16px' : (headerAlign === 'bottom' ? '4px 16px 8px 16px' : '12px 16px')} !important; + } + `; } // Apply font size @@ -500,6 +560,31 @@ styleContent += `.data-table tbody tr:nth-child(even) td { background: white !important; }\n`; } + // Apply border settings + if (appSettings) { + const borderColor = appSettings.borderColor || '#e5e7eb'; + const borderWidth = appSettings.borderWidth || 1; + const showRowBorders = appSettings.showRowBorders !== false; + const showColumnBorders = appSettings.showColumnBorders !== false; + + styleContent += ` + .data-table th, + .data-table td { + border-right: ${showColumnBorders ? `${borderWidth}px solid ${borderColor}` : 'none'} !important; + border-bottom: ${showRowBorders ? `${borderWidth}px solid ${borderColor}` : 'none'} !important; + } + `; + + // Keep header borders slightly different if they exist + if (showColumnBorders) { + styleContent += ` + .data-table thead th { + border-right: ${borderWidth}px solid rgba(255,255,255,0.2) !important; + } + `; + } + } + // Column widths and sticky headers based on visible order if (visibleColumns.length) { const col1Width = visibleColumns[0]?.width || 120; @@ -526,6 +611,9 @@ let totalWidth = 0; visibleColumns.forEach((col, idx) => { const width = col.width || 150; + const colSettings = getColumnSettings(col.name); + const alignment = colSettings?.alignment || 'left'; + const headerWrap = colSettings?.headerWrap; totalWidth += width; styleContent += ` .data-table th:nth-child(${idx + 1}), @@ -533,8 +621,26 @@ width: ${width}px !important; min-width: ${width}px !important; max-width: ${width}px !important; + text-align: ${alignment}; } `; + + // Apply header wrapping if enabled + if (headerWrap) { + styleContent += ` + .data-table thead th:nth-child(${idx + 1}) { + white-space: normal !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; + } + `; + } else { + styleContent += ` + .data-table thead th:nth-child(${idx + 1}) { + white-space: nowrap !important; + } + `; + } }); styleContent += ` @@ -855,6 +961,8 @@ return formatEditableCheckbox(record.Flag, 'Flag', record.id); case 'Has_Attachments': return formatEditableCheckbox(record.Has_Attachments, 'Has_Attachments', record.id); + case 'Job_Folder_Link': + return value ? `${value}` : ''; case 'Notes': return record.Notes || ''; case 'PSwift_Uploaded': @@ -880,6 +988,16 @@ // Title attributes for tooltips on truncated cells function getCellTitle(columnName, record) { + // Check if column has hover tooltip enabled + const colSettings = getColumnSettings(columnName); + if (colSettings && colSettings.showFullTextOnHover) { + const value = record[columnName]; + if (value !== null && value !== undefined) { + return String(value); + } + } + + // Default behavior for specific columns switch (columnName) { case 'Notes': return record.Notes || ''; @@ -919,6 +1037,14 @@ } allRecords = result.data; // Store all records for filtering + + // Sort by Job_Number descending (highest to lowest) by default + allRecords.sort((a, b) => { + const numA = parseInt(a.Job_Number) || 0; + const numB = parseInt(b.Job_Number) || 0; + return numB - numA; // Descending order + }); + console.log(`Loaded ${allRecords.length} jobs ${result.cached ? 'from cache' : 'from PocketBase'}`); console.log('First record:', allRecords[0]); @@ -970,7 +1096,7 @@ return; } - filteredRecords.forEach(record => { + filteredRecords.forEach((record, recordIndex) => { const row = document.createElement('tr'); row.className = 'border-b border-gray-200'; @@ -983,6 +1109,8 @@ const className = columnClassMap[columnName] || 'px-4 py-3'; let style = ''; const colSettings = getColumnSettings(columnName); + + // Apply choice-based styling if (colSettings && value && colSettings.choices && colSettings.choices.length > 0) { const choice = colSettings.choices.find(c => c.value === value); if (choice) { @@ -995,14 +1123,56 @@ } } } + // Apply boolean-based styling for checkbox fields + else if (colSettings && (value === true || value === false)) { + const bgColor = rowIndex % 2 === 0 + ? (value ? (colSettings.trueBgColor || '#d1fae5') : (colSettings.falseBgColor || '#fee2e2')) + : (value ? (colSettings.trueBgColorAlt || '#a7f3d0') : (colSettings.falseBgColorAlt || '#fecaca')); + style += `background-color: ${bgColor} !important;`; + } + const styleAttr = style ? `style="${style}"` : ''; const titleAttr = titleValue ? ` title="${titleValue}"` : ''; const content = renderCellContent(columnName, record, value); - return `${content}`; + + // Make cell editable if appropriate + const isEditable = isFieldEditable(columnName, colSettings); + + // For date fields, normalize the value to YYYY-MM-DD format for the date input + let dataValue = (value || '').toString().replace(/"/g, '"'); + if (isEditable && ['Due_Date', 'Start_Date', 'Submission_Date'].includes(columnName) && value) { + // Convert ISO date to YYYY-MM-DD + try { + const dateObj = new Date(value); + if (!isNaN(dateObj.getTime())) { + const year = dateObj.getFullYear(); + const month = String(dateObj.getMonth() + 1).padStart(2, '0'); + const day = String(dateObj.getDate()).padStart(2, '0'); + dataValue = `${year}-${month}-${day}`; + } + } catch (e) { + // Keep original value if parsing fails + } + } + + const editableAttr = isEditable ? ` data-editable="true" data-record-id="${record.id}" data-field="${columnName}" data-value="${dataValue}" style="cursor: pointer; ${style}"` : styleAttr; + + return `${content}`; }).join(''); row.innerHTML = cellsHtml; tableBody.appendChild(row); + + // Focus first Job_Number cell on initial load (first row only) + if (recordIndex === 0 && currentSearchTerm === '') { + setTimeout(() => { + const firstJobNumberCell = tableBody.querySelector('tr:first-child td:first-child'); + if (firstJobNumberCell) { + firstJobNumberCell.setAttribute('tabindex', '0'); + firstJobNumberCell.focus(); + } + }, 100); + } }); } @@ -1036,6 +1206,324 @@ }); } + // Determine if a field is editable + function isFieldEditable(columnName, colSettings) { + // Checkboxes are already handled by existing checkbox handler + const checkboxFields = ['Active', 'Added_To_Calendar', 'Docs_Uploaded', 'Estimate_Approved', + 'Estimate_Draft', 'Estimate_Reviewed', 'Flag', 'Has_Attachments', + 'PSwift_Uploaded', 'QB_Created', 'Tax_Exempt', 'Tax_Exempt_Docs_Uploaded', + 'Voxer_Created']; + if (checkboxFields.includes(columnName)) return false; + + // Don't allow editing calculated/display fields + const nonEditableFields = ['Job_Number', 'Job_Full_Name', 'Active', 'Due_Date_Counter', + 'last_note_at', 'note_count', 'latest_note_summary', 'note_thread_text']; + if (nonEditableFields.includes(columnName)) return false; + + // Allow editing for fields with choices or text fields + const editableFields = ['Job_Type', 'Job_Status', 'Job_Division', 'Estimator', 'Office_Rep', + 'Project_Manager', 'Due_Date', 'Start_Date', 'Submission_Date', + 'Notes', 'Job_Name', 'Job_Address', 'Company_Client', 'Contact_Person', + 'Phone_Number', 'Due_Date_Notes', 'Start_Date_Notes']; + + return editableFields.includes(columnName); + } + + // Normalize date input to YYYY-MM-DD format + function normalizeDate(input) { + if (!input || input.trim() === '') return null; + + try { + // Try parsing the input + const date = new Date(input); + if (isNaN(date.getTime())) return null; + + // Format as YYYY-MM-DD + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; + } catch (e) { + return null; + } + } + + // Create inline confirmation dialog + function createConfirmDialog(message, onConfirm, onCancel, anchorElement) { + const dialog = document.createElement('div'); + dialog.className = 'absolute bg-white border-2 border-blue-500 rounded shadow-lg p-3 z-[2000]'; + dialog.style.minWidth = '200px'; + + const messageDiv = document.createElement('div'); + messageDiv.className = 'text-sm mb-3 text-gray-800'; + messageDiv.textContent = message; + + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'flex gap-2 justify-end'; + + const confirmBtn = document.createElement('button'); + confirmBtn.textContent = 'Confirm'; + confirmBtn.className = 'px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm'; + confirmBtn.onclick = () => { + dialog.remove(); + onConfirm(); + }; + + const cancelBtn = document.createElement('button'); + cancelBtn.textContent = 'Cancel'; + cancelBtn.className = 'px-3 py-1 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 text-sm'; + cancelBtn.onclick = () => { + dialog.remove(); + onCancel(); + }; + + buttonContainer.appendChild(cancelBtn); + buttonContainer.appendChild(confirmBtn); + + dialog.appendChild(messageDiv); + dialog.appendChild(buttonContainer); + + // Position near the anchor element + document.body.appendChild(dialog); + const rect = anchorElement.getBoundingClientRect(); + dialog.style.left = rect.left + 'px'; + dialog.style.top = (rect.bottom + 5) + 'px'; + + // Focus confirm button + confirmBtn.focus(); + + return dialog; + } + + // Handle cell click for inline editing + document.addEventListener('click', async (e) => { + const cell = e.target.closest('td[data-editable="true"]'); + if (!cell) return; + + // Don't trigger if clicking on a checkbox + if (e.target.type === 'checkbox') return; + + const fieldName = cell.dataset.field; + const recordId = cell.dataset.recordId; + const currentValue = cell.dataset.value; + const colSettings = getColumnSettings(fieldName); + + // Handle fields with choice dropdowns + if (colSettings && colSettings.choices && colSettings.choices.length > 0) { + await handleDropdownEdit(cell, fieldName, recordId, currentValue, colSettings); + } + // Handle date fields + else if (['Due_Date', 'Start_Date', 'Submission_Date'].includes(fieldName)) { + await handleDateEdit(cell, fieldName, recordId, currentValue); + } + // Handle text fields + else { + await handleTextEdit(cell, fieldName, recordId, currentValue); + } + }); + + // Handle dropdown field editing + async function handleDropdownEdit(cell, fieldName, recordId, currentValue, colSettings) { + const choices = colSettings.choices.map(c => c.value); + + // Create expanded list of choices + const choiceContainer = document.createElement('div'); + choiceContainer.className = 'absolute bg-white border border-gray-300 rounded shadow-lg z-[1000] max-h-64 overflow-y-auto'; + choiceContainer.style.minWidth = '150px'; + + // Add empty option + const emptyOption = document.createElement('div'); + emptyOption.className = 'px-3 py-2 hover:bg-blue-100 cursor-pointer text-sm ' + (currentValue === '' || !currentValue ? 'bg-blue-50 font-semibold' : ''); + emptyOption.textContent = '(None)'; + emptyOption.dataset.value = ''; + choiceContainer.appendChild(emptyOption); + + // Add all choices + choices.forEach(choice => { + const option = document.createElement('div'); + option.className = 'px-3 py-2 hover:bg-blue-100 cursor-pointer text-sm ' + (choice === currentValue ? 'bg-blue-50 font-semibold' : ''); + option.textContent = choice; + option.dataset.value = choice; + choiceContainer.appendChild(option); + }); + + // Position near the cell + document.body.appendChild(choiceContainer); + const rect = cell.getBoundingClientRect(); + choiceContainer.style.left = rect.left + 'px'; + choiceContainer.style.top = (rect.bottom + 2) + 'px'; + + // Handle choice selection + const handleSelection = (newValue) => { + choiceContainer.remove(); + + if (newValue !== currentValue) { + createConfirmDialog( + `Change ${fieldName} from "${currentValue || '(empty)'}" to "${newValue || '(empty)'}"?`, + async () => { + await updateField(recordId, fieldName, newValue || null); + }, + () => { + // Cancelled - do nothing + }, + cell + ); + } + }; + + // Add click handlers to all options + choiceContainer.querySelectorAll('div').forEach(option => { + option.addEventListener('click', () => { + handleSelection(option.dataset.value); + }); + }); + + // Close on outside click + const closeOnOutsideClick = (e) => { + if (!choiceContainer.contains(e.target) && !cell.contains(e.target)) { + choiceContainer.remove(); + document.removeEventListener('click', closeOnOutsideClick); + } + }; + + setTimeout(() => { + document.addEventListener('click', closeOnOutsideClick); + }, 0); + + // Close on escape + const handleEscape = (e) => { + if (e.key === 'Escape') { + choiceContainer.remove(); + document.removeEventListener('keydown', handleEscape); + } + }; + document.addEventListener('keydown', handleEscape); + } + + // Handle date field editing + async function handleDateEdit(cell, fieldName, recordId, currentValue) { + const input = document.createElement('input'); + input.type = 'date'; + input.value = currentValue || ''; + input.className = 'w-full px-2 py-1 border border-gray-300 rounded focus:outline-none focus:border-gray-400'; + input.style.position = 'relative'; + input.style.zIndex = '1000'; + + const originalHTML = cell.innerHTML; + const originalPosition = cell.style.position; + cell.style.position = 'relative'; + cell.innerHTML = ''; + cell.appendChild(input); + input.focus(); + input.showPicker?.(); // Show date picker if supported + + const finishEdit = async () => { + const newValue = input.value.trim(); + cell.innerHTML = originalHTML; + cell.style.position = originalPosition; + + if (newValue !== currentValue) { + const displayDate = newValue ? new Date(newValue + 'T00:00:00').toLocaleDateString() : '(empty)'; + + createConfirmDialog( + `Change ${fieldName} to ${displayDate}?`, + async () => { + await updateField(recordId, fieldName, newValue || null); + }, + () => { + // Cancelled - do nothing + }, + cell + ); + } + }; + + input.addEventListener('blur', finishEdit); + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + finishEdit(); + } else if (e.key === 'Escape') { + cell.innerHTML = originalHTML; + cell.style.position = originalPosition; + } + }); + } + + // Handle text field editing + async function handleTextEdit(cell, fieldName, recordId, currentValue) { + const isMultiline = fieldName === 'Notes'; + const input = document.createElement(isMultiline ? 'textarea' : 'input'); + + if (!isMultiline) { + input.type = 'text'; + } else { + input.rows = 3; + } + + input.value = currentValue || ''; + input.className = 'w-full px-2 py-1 border border-gray-300 rounded focus:outline-none focus:border-gray-400'; + input.style.position = 'relative'; + input.style.zIndex = '1000'; + + const originalHTML = cell.innerHTML; + const originalPosition = cell.style.position; + cell.style.position = 'relative'; + cell.innerHTML = ''; + cell.appendChild(input); + input.focus(); + input.select(); + + const finishEdit = async () => { + const newValue = input.value.trim(); + cell.innerHTML = originalHTML; + cell.style.position = originalPosition; + + if (newValue !== currentValue) { + const preview = newValue.length > 50 ? newValue.substring(0, 50) + '...' : newValue; + + createConfirmDialog( + `Change ${fieldName} to "${preview || '(empty)'}"?`, + async () => { + await updateField(recordId, fieldName, newValue || null); + }, + () => { + // Cancelled - do nothing + }, + cell + ); + } + }; + + input.addEventListener('blur', finishEdit); + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !isMultiline) { + e.preventDefault(); + finishEdit(); + } else if (e.key === 'Escape') { + cell.innerHTML = originalHTML; + cell.style.position = originalPosition; + } + }); + } + + // Update field in PocketBase + async function updateField(recordId, fieldName, value) { + try { + await pb.collection('Job_Info_TestEnv').update(recordId, { + [fieldName]: value + }); + + // Reload table data to reflect changes + await loadTableData(); + + } catch (error) { + console.error('Error updating field:', error); + alert('Failed to update field. Please try again.'); + } + } + // Setup search input listener function setupSearchListener() { const searchInput = document.getElementById('searchInput'); @@ -1068,24 +1556,58 @@ if (isReadOnly && fieldName === 'Active') { // Revert the checkbox state checkbox.checked = !checkbox.checked; - alert('Active status is calculated automatically by selected Job Status values.'); + + createConfirmDialog( + 'Active status is calculated automatically by selected Job Status values.', + () => {}, + () => {}, + checkbox.closest('td') + ); return; } // Update PocketBase for editable fields const newValue = checkbox.checked; + const action = newValue ? 'check' : 'uncheck'; - try { - await pb.collection('Job_Info_TestEnv').update(recordId, { - [fieldName]: newValue - }); - // Note: Cache will auto-expire in 5 minutes, no need to clear immediately - } catch (error) { - console.error('Error updating field:', error); - // Revert checkbox on error - checkbox.checked = !checkbox.checked; - alert('Failed to update field. Please try again.'); - } + // Create confirmation dialog + createConfirmDialog( + `${action.charAt(0).toUpperCase() + action.slice(1)} ${fieldName}?`, + async () => { + // Confirmed - update field + try { + await pb.collection('Job_Info_TestEnv').update(recordId, { + [fieldName]: newValue + }); + + // Special reminder for Tax Exempt + if (fieldName === 'Tax_Exempt' && newValue) { + setTimeout(() => { + createConfirmDialog( + 'Reminder: Please upload tax exempt documents.', + () => {}, + () => {}, + checkbox.closest('td') + ); + }, 100); + } + } catch (error) { + console.error('Error updating field:', error); + checkbox.checked = !checkbox.checked; + createConfirmDialog( + 'Failed to update field. Please try again.', + () => {}, + () => {}, + checkbox.closest('td') + ); + } + }, + () => { + // Cancelled - revert checkbox + checkbox.checked = !checkbox.checked; + }, + checkbox.closest('td') + ); } });