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:
parent
f87a3700c5
commit
f9a8bcf405
@ -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"
|
||||
}
|
||||
@ -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 %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user