auto-claude: subtask-4-4 - Add progress bar JavaScript for form completion tracking

- Enhanced updateProgress() function to track section-by-section completion
- Added calculateSectionCompletion() helper for per-section field analysis
- Added updateSectionProgress() for section-level progress indicators
- Implemented real-time progress tracking with input event listeners
- Added debounced input handlers for text/email/textarea fields
- Added keyboard navigation support for progress dots (accessibility)
- Progress bar color changes based on completion percentage:
  - Primary (blue) for <50%
  - Warning (yellow) for 50-79%
  - Success (green) for >=80%
- Section dots and numbers turn green when section is 70%+ complete
- Properly handles conditional fields visibility in completion calculation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-09 08:56:14 +01:00
parent f87a3700c5
commit f9a8bcf405
2 changed files with 231 additions and 23 deletions

View File

@ -3,7 +3,7 @@
"spec": "001-audyt-it",
"state": "building",
"subtasks": {
"completed": 14,
"completed": 15,
"total": 28,
"in_progress": 1,
"failed": 0
@ -18,8 +18,8 @@
"max": 1
},
"session": {
"number": 15,
"number": 16,
"started_at": "2026-01-09T08:11:54.054044"
},
"last_update": "2026-01-09T08:50:11.901646"
"last_update": "2026-01-09T08:54:23.577781"
}

View File

@ -2063,36 +2063,199 @@ function updateProgressDots(activeSectionNum) {
// Calculate and update form progress
function updateProgress() {
const form = document.getElementById('itAuditForm');
const inputs = form.querySelectorAll('input:not([type="hidden"]), select');
let filledCount = 0;
let totalCount = 0;
const totalSections = 9;
let completedSections = 0;
let totalFields = 0;
let filledFields = 0;
inputs.forEach(input => {
if (input.offsetParent !== null) { // Check if visible
totalCount++;
if (input.value && input.value.trim() !== '') {
filledCount++;
// Calculate completion for each section
for (let sectionNum = 1; sectionNum <= totalSections; sectionNum++) {
const section = document.querySelector(`.form-section[data-section="${sectionNum}"]`);
if (!section) continue;
const sectionContent = document.getElementById('sectionContent' + sectionNum);
if (!sectionContent) continue;
const sectionResult = calculateSectionCompletion(sectionContent, sectionNum);
totalFields += sectionResult.total;
filledFields += sectionResult.filled;
// Update section number badge and progress dot
const sectionNumber = document.getElementById('sectionNumber' + sectionNum);
const progressDot = document.querySelector(`.progress-section-dot[data-section="${sectionNum}"]`);
if (sectionResult.isComplete) {
completedSections++;
if (sectionNumber) sectionNumber.classList.add('complete');
if (progressDot) {
progressDot.classList.add('complete');
}
} else {
if (sectionNumber) sectionNumber.classList.remove('complete');
if (progressDot) {
progressDot.classList.remove('complete');
}
}
}
// Calculate overall percentage
const percentage = totalFields > 0 ? Math.round((filledFields / totalFields) * 100) : 0;
// Update progress bar
const progressPercentage = document.getElementById('progressPercentage');
const progressFill = document.getElementById('progressFill');
if (progressPercentage) progressPercentage.textContent = percentage + '%';
if (progressFill) progressFill.style.width = percentage + '%';
// Update progress bar color based on completion
if (progressFill) {
if (percentage >= 80) {
progressFill.style.background = 'var(--success)';
} else if (percentage >= 50) {
progressFill.style.background = 'var(--warning)';
} else {
progressFill.style.background = 'var(--primary)';
}
}
}
// Calculate completion for a single section
function calculateSectionCompletion(sectionContent, sectionNum) {
let totalFields = 0;
let filledFields = 0;
// Count visible toggle switches in this section
const toggles = sectionContent.querySelectorAll('.toggle-switch');
toggles.forEach(toggle => {
const parent = toggle.closest('.form-group');
if (parent && !parent.classList.contains('hidden')) {
totalFields++;
if (toggle.classList.contains('active')) {
filledFields++;
}
}
});
// Also count toggle switches
document.querySelectorAll('.toggle-switch.active').forEach(() => {
filledCount++;
});
// Count visible select fields
const selects = sectionContent.querySelectorAll('select');
selects.forEach(select => {
const parent = select.closest('.form-group');
const conditionalParent = select.closest('.conditional-fields');
// Count chip selections
Object.values(chipSelections).forEach(selection => {
if (selection.length > 0) {
filledCount++;
// Check if field is visible (not in hidden conditional fields)
if (conditionalParent && conditionalParent.classList.contains('hidden')) {
return;
}
if (parent && parent.classList.contains('hidden')) {
return;
}
totalFields++;
if (select.value && select.value.trim() !== '') {
filledFields++;
}
});
const percentage = totalCount > 0 ? Math.round((filledCount / Math.max(totalCount, 1)) * 100) : 0;
// Count visible text/email inputs
const textInputs = sectionContent.querySelectorAll('input[type="text"], input[type="email"]');
textInputs.forEach(input => {
const parent = input.closest('.form-group');
const conditionalParent = input.closest('.conditional-fields');
document.getElementById('progressPercentage').textContent = percentage + '%';
document.getElementById('progressFill').style.width = percentage + '%';
// Check if field is visible
if (conditionalParent && conditionalParent.classList.contains('hidden')) {
return;
}
if (parent && parent.classList.contains('hidden')) {
return;
}
totalFields++;
if (input.value && input.value.trim() !== '') {
filledFields++;
}
});
// Count visible checkbox groups
const checkboxes = sectionContent.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
const parent = checkbox.closest('.form-group');
if (parent && !parent.classList.contains('hidden')) {
totalFields++;
if (checkbox.checked) {
filledFields++;
}
}
});
// Count chip selections for this section
const chipSelects = sectionContent.querySelectorAll('.chip-select');
chipSelects.forEach(chipSelect => {
const parent = chipSelect.closest('.form-group');
const conditionalParent = chipSelect.closest('.conditional-fields');
// Check if chip select is visible
if (conditionalParent && conditionalParent.classList.contains('hidden')) {
return;
}
if (parent && parent.classList.contains('hidden')) {
return;
}
totalFields++;
const selectedChips = chipSelect.querySelectorAll('.chip-option.selected');
if (selectedChips.length > 0) {
filledFields++;
}
});
// Count textareas
const textareas = sectionContent.querySelectorAll('textarea');
textareas.forEach(textarea => {
const parent = textarea.closest('.form-group');
const conditionalParent = textarea.closest('.conditional-fields');
if (conditionalParent && conditionalParent.classList.contains('hidden')) {
return;
}
if (parent && parent.classList.contains('hidden')) {
return;
}
totalFields++;
if (textarea.value && textarea.value.trim() !== '') {
filledFields++;
}
});
// Section is complete if at least 70% of visible fields are filled
// or all fields are filled, whichever makes more sense
const isComplete = totalFields > 0 && filledFields >= Math.ceil(totalFields * 0.7);
return {
total: totalFields,
filled: filledFields,
isComplete: isComplete
};
}
// Update section progress indicator when navigating
function updateSectionProgress(sectionNum) {
const sectionContent = document.getElementById('sectionContent' + sectionNum);
if (!sectionContent) return;
const result = calculateSectionCompletion(sectionContent, sectionNum);
const progressDot = document.querySelector(`.progress-section-dot[data-section="${sectionNum}"]`);
const sectionNumber = document.getElementById('sectionNumber' + sectionNum);
if (result.isComplete) {
if (progressDot) progressDot.classList.add('complete');
if (sectionNumber) sectionNumber.classList.add('complete');
} else {
if (progressDot) progressDot.classList.remove('complete');
if (sectionNumber) sectionNumber.classList.remove('complete');
}
}
// Save draft
@ -2424,4 +2587,49 @@ document.querySelectorAll('.progress-section-dot').forEach(dot => {
dot.classList.add('active');
});
});
// Real-time progress tracking for form inputs
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('itAuditForm');
if (!form) return;
// Add change listeners for select elements
form.querySelectorAll('select').forEach(select => {
select.addEventListener('change', updateProgress);
});
// Add input listeners for text and email fields with debounce
let debounceTimer;
form.querySelectorAll('input[type="text"], input[type="email"]').forEach(input => {
input.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(updateProgress, 300);
});
});
// Add change listeners for checkboxes
form.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', updateProgress);
});
// Add input listeners for textareas with debounce
form.querySelectorAll('textarea').forEach(textarea => {
textarea.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(updateProgress, 300);
});
});
});
// Keyboard navigation support for progress dots
document.querySelectorAll('.progress-section-dot').forEach(dot => {
dot.setAttribute('tabindex', '0');
dot.setAttribute('role', 'button');
dot.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
dot.click();
}
});
});
{% endblock %}