my personal site
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Enhance admin HTML functionality and improve form handling

- Added a new save status display element to provide feedback on save actions.
- Introduced a suppressDirty flag to prevent unnecessary unsaved changes warnings during form resets and population.
- Refactored form clearing and population logic to ensure proper handling of unsaved changes.
- Updated CSS for the save status element to improve positioning and visibility.

+49 -37
+49 -37
workers/gymtracker-ads-api/src/admin-html.ts
··· 173 173 <div class="form-actions"> 174 174 <button type="button" id="deleteBtn" class="delete-btn" disabled>Delete</button> 175 175 <span id="unsavedIndicator" class="unsaved-indicator" hidden>Unsaved changes</span> 176 + <span id="saveStatus" class="save-status"></span> 176 177 <div class="form-actions-right"> 177 178 <button type="button" id="cloneBtn">Clone</button> 178 179 <button type="submit" id="saveBtn" class="primary" form="adForm">Save</button> 179 - <span id="saveStatus" class="save-status"></span> 180 180 </div> 181 181 </div> 182 182 </footer> ··· 295 295 let scheduledAds = []; 296 296 let selectedIndex = -1; 297 297 let formDirty = false; 298 + let suppressDirty = false; 298 299 let previewDebounce = null; 299 300 let perAdStats = []; 300 301 let adsFilterStatus = 'all'; ··· 657 658 if (formDirty && !confirm('Discard unsaved changes?')) return; 658 659 formDirty = false; 659 660 selectedIndex = index; 661 + setStatus(saveStatus, '', undefined); 660 662 renderAdCards(); 661 663 renderCalendar(); 662 664 if (index === -1) { 663 665 clearForm(); 664 666 clearFieldErrors(); 665 - setStatus(saveStatus, '', undefined); 666 667 const ind = document.getElementById('unsavedIndicator'); 667 668 if (ind) ind.hidden = true; 668 669 } else { ··· 678 679 } 679 680 680 681 function markDirty() { 682 + if (suppressDirty) return; 681 683 formDirty = true; 682 684 const ind = document.getElementById('unsavedIndicator'); 683 685 if (ind) ind.hidden = false; ··· 732 734 } 733 735 734 736 function clearForm() { 735 - document.getElementById('id').value = ''; 736 - document.getElementById('tier').value = 'banner'; 737 - updateTierButtons(); 738 - document.getElementById('active').value = 'true'; 739 - updateActiveInactiveButtons(); 740 - document.getElementById('sponsor').value = ''; 741 - document.getElementById('headline').value = ''; 742 - document.getElementById('subline').value = ''; 743 - document.getElementById('cta').value = ''; 744 - document.getElementById('destination_url').value = ''; 745 - document.getElementById('image_url').value = ''; 746 - document.getElementById('logo_url').value = ''; 747 - document.getElementById('placement').value = 'home_feed'; 748 - document.getElementById('creative_version').value = ''; 749 - if (fpRange) fpRange.clear(); 737 + suppressDirty = true; 738 + try { 739 + document.getElementById('id').value = ''; 740 + document.getElementById('tier').value = 'banner'; 741 + updateTierButtons(); 742 + document.getElementById('active').value = 'true'; 743 + updateActiveInactiveButtons(); 744 + document.getElementById('sponsor').value = ''; 745 + document.getElementById('headline').value = ''; 746 + document.getElementById('subline').value = ''; 747 + document.getElementById('cta').value = ''; 748 + document.getElementById('destination_url').value = ''; 749 + document.getElementById('image_url').value = ''; 750 + document.getElementById('logo_url').value = ''; 751 + document.getElementById('placement').value = 'home_feed'; 752 + document.getElementById('creative_version').value = ''; 753 + if (fpRange) fpRange.clear(); 754 + } finally { 755 + suppressDirty = false; 756 + } 750 757 } 751 758 752 759 function isoToDatetimeLocal(iso) { ··· 764 771 } 765 772 766 773 function populateForm(data) { 767 - document.getElementById('id').value = data.id || ''; 768 - document.getElementById('tier').value = (data.tier || 'banner').toLowerCase(); 769 - updateTierButtons(); 770 - document.getElementById('active').value = data.active ? 'true' : 'false'; 771 - updateActiveInactiveButtons(); 772 - document.getElementById('sponsor').value = data.sponsor || ''; 773 - document.getElementById('headline').value = data.headline || ''; 774 - document.getElementById('subline').value = data.subline || ''; 775 - document.getElementById('cta').value = data.cta || ''; 776 - document.getElementById('destination_url').value = data.destination_url || ''; 777 - document.getElementById('image_url').value = data.image_url || ''; 778 - document.getElementById('logo_url').value = data.logo_url || ''; 779 - document.getElementById('placement').value = data.placement || 'home_feed'; 780 - document.getElementById('creative_version').value = data.creative_version || ''; 781 - if (fpRange) { 782 - const start = data.start_at ? new Date(data.start_at) : null; 783 - const end = data.end_at ? new Date(data.end_at) : null; 784 - fpRange.setDate(start && end ? [start, end] : start ? [start] : []); 774 + suppressDirty = true; 775 + try { 776 + document.getElementById('id').value = data.id || ''; 777 + document.getElementById('tier').value = (data.tier || 'banner').toLowerCase(); 778 + updateTierButtons(); 779 + document.getElementById('active').value = data.active ? 'true' : 'false'; 780 + updateActiveInactiveButtons(); 781 + document.getElementById('sponsor').value = data.sponsor || ''; 782 + document.getElementById('headline').value = data.headline || ''; 783 + document.getElementById('subline').value = data.subline || ''; 784 + document.getElementById('cta').value = data.cta || ''; 785 + document.getElementById('destination_url').value = data.destination_url || ''; 786 + document.getElementById('image_url').value = data.image_url || ''; 787 + document.getElementById('logo_url').value = data.logo_url || ''; 788 + document.getElementById('placement').value = data.placement || 'home_feed'; 789 + document.getElementById('creative_version').value = data.creative_version || ''; 790 + if (fpRange) { 791 + const start = data.start_at ? new Date(data.start_at) : null; 792 + const end = data.end_at ? new Date(data.end_at) : null; 793 + fpRange.setDate(start && end ? [start, end] : start ? [start] : []); 794 + } 795 + } finally { 796 + suppressDirty = false; 785 797 } 786 798 } 787 799 ··· 1154 1166 setStatus(saveStatus, data.error || res.statusText, false); 1155 1167 return; 1156 1168 } 1157 - setStatus(saveStatus, 'Saved', true); 1169 + setStatus(saveStatus, '', undefined); 1158 1170 formDirty = false; 1159 1171 const ind = document.getElementById('unsavedIndicator'); 1160 1172 if (ind) ind.hidden = true; ··· 1420 1432 .tier-btn:hover { color: var(--text); } 1421 1433 .tier-btn.active { background: rgba(20,184,166,0.12); color: var(--accent); } 1422 1434 .unsaved-indicator { font-size: 11px; color: var(--yellow); position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none; } 1423 - .save-status { font-size: 11px; color: var(--muted); } 1435 + .save-status { font-size: 11px; color: var(--muted); position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none; } 1424 1436 .save-status.status-ok { color: var(--green); } 1425 1437 .save-status.status-err { color: var(--red); } 1426 1438